From 711bc07d0ed0a89bf78095c61dbdcee0462fc2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Sar=C4=B1?= Date: Wed, 22 Jul 2020 18:30:55 +0300 Subject: [PATCH 01/10] Create separate CMakeLists.txt for each target --- Examples/CMakeLists.txt | 16 ---------------- Examples/math/CMakeLists.txt | 5 +++++ Examples/repeat/CMakeLists.txt | 4 ++++ Examples/roll/CMakeLists.txt | 6 ++++++ 4 files changed, 15 insertions(+), 16 deletions(-) delete mode 100644 Examples/CMakeLists.txt create mode 100644 Examples/math/CMakeLists.txt create mode 100644 Examples/repeat/CMakeLists.txt create mode 100644 Examples/roll/CMakeLists.txt diff --git a/Examples/CMakeLists.txt b/Examples/CMakeLists.txt deleted file mode 100644 index 06c366e6b..000000000 --- a/Examples/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -add_executable(math - math/main.swift) -target_link_libraries(math PRIVATE - ArgumentParser - $<$:m>) - -add_executable(repeat - repeat/main.swift) -target_link_libraries(repeat PRIVATE - ArgumentParser) - -add_executable(roll - roll/main.swift - roll/SplitMix64.swift) -target_link_libraries(roll PRIVATE - ArgumentParser) diff --git a/Examples/math/CMakeLists.txt b/Examples/math/CMakeLists.txt new file mode 100644 index 000000000..b67404527 --- /dev/null +++ b/Examples/math/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(math + main.swift) +target_link_libraries(math PRIVATE + ArgumentParser + $<$:m>) diff --git a/Examples/repeat/CMakeLists.txt b/Examples/repeat/CMakeLists.txt new file mode 100644 index 000000000..4cf01e4fc --- /dev/null +++ b/Examples/repeat/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(repeat + main.swift) +target_link_libraries(repeat PRIVATE + ArgumentParser) diff --git a/Examples/roll/CMakeLists.txt b/Examples/roll/CMakeLists.txt new file mode 100644 index 000000000..553373011 --- /dev/null +++ b/Examples/roll/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(roll + main.swift + SplitMix64.swift) +target_link_libraries(roll PRIVATE + ArgumentParser) + From 2697310286c04f7e55734bfe360777f1fcdae4ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Sar=C4=B1?= Date: Wed, 22 Jul 2020 18:33:09 +0300 Subject: [PATCH 02/10] Update Package.swift for localization support --- Package.swift | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/Package.swift b/Package.swift index a8caf6ccb..99c44f685 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.2 +// swift-tools-version:5.3 //===----------------------------------------------------------*- swift -*-===// // // This source file is part of the Swift Argument Parser open source project @@ -14,6 +14,7 @@ import PackageDescription var package = Package( name: "swift-argument-parser", + defaultLocalization: "en", products: [ .library( name: "ArgumentParser", @@ -23,23 +24,29 @@ var package = Package( targets: [ .target( name: "ArgumentParser", - dependencies: []), + dependencies: [], + exclude: ["CMakeLists.txt"], + resources: [.process("Resources")]), .target( name: "ArgumentParserTestHelpers", - dependencies: ["ArgumentParser"]), - + dependencies: ["ArgumentParser"], + exclude: ["CMakeLists.txt"]), + .target( name: "roll", dependencies: ["ArgumentParser"], - path: "Examples/roll"), + path: "Examples/roll", + exclude: ["CMakeLists.txt"]), .target( name: "math", dependencies: ["ArgumentParser"], - path: "Examples/math"), + path: "Examples/math", + exclude: ["CMakeLists.txt"]), .target( name: "repeat", dependencies: ["ArgumentParser"], - path: "Examples/repeat"), + path: "Examples/repeat", + exclude: ["CMakeLists.txt"]), .target( name: "changelog-authors", @@ -48,10 +55,12 @@ var package = Package( .testTarget( name: "ArgumentParserEndToEndTests", - dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"]), + dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], + exclude: ["CMakeLists.txt"]), .testTarget( name: "ArgumentParserUnitTests", - dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"]), + dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], + exclude: ["CMakeLists.txt"]), .testTarget( name: "ArgumentParserExampleTests", dependencies: ["ArgumentParserTestHelpers"]), @@ -63,5 +72,6 @@ var package = Package( package.targets.append( .testTarget( name: "ArgumentParserPackageManagerTests", - dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"])) + dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], + exclude: ["CMakeLists.txt"])) #endif From 81dc9fc66660f54fe87950acba891d12f3c2ec7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Sar=C4=B1?= Date: Wed, 22 Jul 2020 18:56:23 +0300 Subject: [PATCH 03/10] Add NSLocalizedString to strings --- .../ArgumentParser/Usage/HelpGenerator.swift | 40 +++++++------- .../ArgumentParser/Usage/MessageInfo.swift | 8 +-- .../ArgumentParser/Usage/UsageGenerator.swift | 54 +++++++++---------- 3 files changed, 52 insertions(+), 50 deletions(-) diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index 44d9b03fc..f82bd3677 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +@_implementationOnly import Foundation + internal struct HelpGenerator { static var helpIndent = 2 static var labelColumnWidth = 26 @@ -68,11 +70,11 @@ internal struct HelpGenerator { var description: String { switch self { case .positionalArguments: - return "Arguments" + return NSLocalizedString("Arguments", bundle: .module, comment: "Description") case .subcommands: - return "Subcommands" + return NSLocalizedString("Subcommands", bundle: .module, comment: "Description") case .options: - return "Options" + return NSLocalizedString("Options", bundle: .module, comment: "Description") } } } @@ -114,7 +116,7 @@ internal struct HelpGenerator { var usageString = UsageGenerator(toolName: toolName, definition: [currentArgSet]).synopsis if !currentCommand.configuration.subcommands.isEmpty { if usageString.last != " " { usageString += " " } - usageString += "" + usageString += NSLocalizedString("", bundle: .module, comment: "Subcommand placeholder") } self.abstract = currentCommand.configuration.abstract @@ -157,7 +159,7 @@ internal struct HelpGenerator { // If this argument is composite, we have a group of arguments to // output together. var groupedArgs = [arg] - let defaultValue = arg.help.defaultValue.map { "(default: \($0))" } ?? "" + let defaultValue = arg.help.defaultValue.map { NSLocalizedString(String(format: "(default: %@)", $0), bundle: .module, comment: "Default value") } ?? "" while i < args.count - 1 && args[i + 1].help.keys == arg.help.keys { groupedArgs.append(args[i + 1]) i += 1 @@ -181,7 +183,7 @@ internal struct HelpGenerator { .compactMap { $0 } .joined(separator: " ") } else { - let defaultValue = arg.help.defaultValue.flatMap { $0.isEmpty ? nil : "(default: \($0))" } ?? "" + let defaultValue = arg.help.defaultValue.flatMap { $0.isEmpty ? nil : NSLocalizedString(String(format: "(default: %@)", $0), bundle: .module, comment: "Default value") } ?? "" synopsis = arg.synopsisForHelp ?? "" description = [arg.help.help?.abstract, defaultValue] .compactMap { $0 } @@ -201,7 +203,7 @@ internal struct HelpGenerator { } if commandStack.contains(where: { !$0.configuration.version.isEmpty }) { - optionElements.append(.init(label: "--version", abstract: "Show the version.")) + optionElements.append(.init(label: "--version", abstract: NSLocalizedString("Show the version.", bundle: .module, comment: "Help text"))) } let helpLabels = commandStack @@ -210,7 +212,7 @@ internal struct HelpGenerator { .map { $0.synopsisString } .joined(separator: ", ") if !helpLabels.isEmpty { - optionElements.append(.init(label: helpLabels, abstract: "Show help information.")) + optionElements.append(.init(label: helpLabels, abstract: NSLocalizedString("Show help information.", bundle: .module, comment: "Help text"))) } let configuration = commandStack.last!.configuration @@ -219,7 +221,7 @@ internal struct HelpGenerator { guard command.configuration.shouldDisplay else { return nil } var label = command._commandName if command == configuration.defaultSubcommand { - label += " (default)" + label += NSLocalizedString(" (default)", bundle: .module, comment: "Default value") } return Section.Element( label: label, @@ -235,7 +237,7 @@ internal struct HelpGenerator { func usageMessage(screenWidth: Int? = nil) -> String { let screenWidth = screenWidth ?? HelpGenerator.systemScreenWidth - return "Usage: \(usage.rendered(screenWidth: screenWidth))" + return NSLocalizedString(String(format: "Usage: %@", usage.rendered(screenWidth: screenWidth)), bundle: .module, comment: "Usage help") } var includesSubcommands: Bool { @@ -252,24 +254,24 @@ internal struct HelpGenerator { .joined(separator: "\n") let renderedAbstract = abstract.isEmpty ? "" - : "OVERVIEW: \(abstract)".wrapped(to: screenWidth) + "\n\n" + : NSLocalizedString(String(format: "OVERVIEW: %@", abstract), bundle: .module, comment: "Abstract").wrapped(to: screenWidth) + "\n\n" var helpSubcommandMessage: String = "" if includesSubcommands { var names = commandStack.map { $0._commandName } names.insert("help", at: 1) - helpSubcommandMessage = """ + helpSubcommandMessage = NSLocalizedString(String(format: """ - See '\(names.joined(separator: " ")) ' for detailed help. - """ + See '%@ ' for detailed help. + """, names.joined(separator: " ")), bundle: .module, comment: "Help text") } - return """ - \(renderedAbstract)\ - USAGE: \(usage.rendered(screenWidth: screenWidth)) + return NSLocalizedString(String(format: """ + %1$@\ + USAGE: %2$@ - \(renderedSections)\(helpSubcommandMessage) - """ + %3$@%4$@ + """, renderedAbstract, usage.rendered(screenWidth: screenWidth), renderedSections, helpSubcommandMessage), bundle: .module, comment: "Help text") } } diff --git a/Sources/ArgumentParser/Usage/MessageInfo.swift b/Sources/ArgumentParser/Usage/MessageInfo.swift index 4c4948b51..ede115209 100644 --- a/Sources/ArgumentParser/Usage/MessageInfo.swift +++ b/Sources/ArgumentParser/Usage/MessageInfo.swift @@ -33,7 +33,7 @@ enum MessageInfo { let versionString = commandStack .map { $0.configuration.version } .last(where: { !$0.isEmpty }) - ?? "Unspecified version" + ?? NSLocalizedString("Unspecified version", bundle: .module, comment: "Error message") self = .help(text: versionString) return default: @@ -55,7 +55,7 @@ enum MessageInfo { let commandNames = commandStack.map { $0._commandName }.joined(separator: " ") let usage = HelpGenerator(commandStack: commandStack).usageMessage() - + "\n See '\(commandNames) --help' for more information." + + NSLocalizedString(String(format: "\n See '%@ --help' for more information.", commandNames), bundle: .module, comment: "Help text") // Parsing errors and user-thrown validation errors have the usage // string attached. Other errors just get the error message. @@ -109,10 +109,10 @@ enum MessageInfo { case .help(text: let text): return text case .validation(message: let message, usage: let usage): - let errorMessage = message.isEmpty ? "" : "Error: \(message)\n" + let errorMessage = message.isEmpty ? "" : NSLocalizedString(String(format: "Error: %@\n", message), bundle: .module, comment: "Error message") return errorMessage + usage case .other(let message, _): - return message.isEmpty ? "" : "Error: \(message)" + return message.isEmpty ? "" : NSLocalizedString(String(format: "Error: %@", message), bundle: .module, comment: "Error message") } } diff --git a/Sources/ArgumentParser/Usage/UsageGenerator.swift b/Sources/ArgumentParser/Usage/UsageGenerator.swift index be84a058e..1078f9723 100644 --- a/Sources/ArgumentParser/Usage/UsageGenerator.swift +++ b/Sources/ArgumentParser/Usage/UsageGenerator.swift @@ -18,7 +18,7 @@ struct UsageGenerator { extension UsageGenerator { init(definition: ArgumentSet) { - let toolName = CommandLine.arguments[0].split(separator: "/").last.map(String.init) ?? "" + let toolName = CommandLine.arguments[0].split(separator: "/").last.map(String.init) ?? NSLocalizedString("", bundle: .module, comment: "Command placeholder") self.init(toolName: toolName, definition: definition) } @@ -41,7 +41,7 @@ extension UsageGenerator { case 0: return toolName case let x where x > 12: - return "\(toolName) " + return NSLocalizedString(String(format: "%@ ", toolName), bundle: .module, comment: "Command options help") default: return "\(toolName) \(definition.synopsis.joined(separator: " "))" } @@ -85,7 +85,7 @@ extension ArgumentDefinition { switch update { case .unary: - return "\(name.synopsisString) <\(synopsisValueName ?? "value")>" + return "\(name.synopsisString) <\(synopsisValueName ?? NSLocalizedString("value", bundle: .module, comment: "Placeholder"))>" case .nullary: return name.synopsisString } @@ -182,11 +182,11 @@ extension ErrorMessageGenerator { case .unableToParseValue(let o, let n, let v, forKey: let k, originalError: let e): return unableToParseValueMessage(origin: o, name: n, value: v, key: k, error: e) case .invalidOption(let str): - return "Invalid option: \(str)" + return NSLocalizedString(String(format: "Invalid option: %@", str), bundle: .module, comment: "Error message") case .nonAlphanumericShortOption(let c): - return "Invalid option: -\(c)" + return NSLocalizedString(String(format: "Invalid option: -%@", c as! CVarArg), bundle: .module, comment: "Error message") case .missingSubcommand: - return "Missing required subcommand." + return NSLocalizedString("Missing required subcommand.", bundle: .module, comment: "Error message") case .userValidationError(let error): switch error { case let error as LocalizedError: @@ -235,16 +235,16 @@ extension ErrorMessageGenerator { extension ErrorMessageGenerator { var notImplementedMessage: String { - return "Internal error. Parsing command-line arguments hit unimplemented code path." + return NSLocalizedString("Internal error. Parsing command-line arguments hit unimplemented code path.", bundle: .module, comment: "Error message") } var invalidState: String { - return "Internal error. Invalid state while parsing command-line arguments." + return NSLocalizedString("Internal error. Invalid state while parsing command-line arguments.", bundle: .module, comment: "Error message") } func unknownOptionMessage(origin: InputOrigin.Element, name: Name) -> String { if case .short = name { - return "Unknown option '\(name.synopsisString)'" + return NSLocalizedString(String(format: "Unknown option '%@'", name.synopsisString), bundle: .module, comment: "Error message") } // An empirically derived magic number @@ -266,21 +266,21 @@ extension ErrorMessageGenerator { }) if let suggestion = suggestion { - return "Unknown option '\(name.synopsisString)'. Did you mean '\(suggestion.synopsisString)'?" + return NSLocalizedString(String(format: "Unknown option '%1$@'. Did you mean '%2$@'?", name.synopsisString, suggestion.synopsisString), bundle: .module, comment: "Error message") } - return "Unknown option '\(name.synopsisString)'" + return NSLocalizedString(String(format: "Unknown option '%@'.", name.synopsisString), bundle: .module, comment: "Error message") } func missingValueForOptionMessage(origin: InputOrigin, name: Name) -> String { if let valueName = valueName(for: name) { - return "Missing value for '\(name.synopsisString) <\(valueName)>'" + return NSLocalizedString(String(format: "Missing value for '%1$@ <%2$@>'", name.synopsisString, valueName), bundle: .module, comment: "Error message") } else { - return "Missing value for '\(name.synopsisString)'" + return String(format: "Missing value for '%@'", name.synopsisString) } } func unexpectedValueForOptionMessage(origin: InputOrigin.Element, name: Name, value: String) -> String? { - return "The option '\(name.synopsisString)' does not take any value, but '\(value)' was specified." + return NSLocalizedString(String(format: "The option '1$%@' does not take any value, but '%2$@' was specified.", name.synopsisString, value), bundle: .module, comment: "Error message") } func unexpectedExtraValuesMessage(values: [(InputOrigin, String)]) -> String? { @@ -288,10 +288,10 @@ extension ErrorMessageGenerator { case 0: return nil case 1: - return "Unexpected argument '\(values.first!.1)'" + return NSLocalizedString(String(format: "Unexpected argument '%@'", values.first!.1), bundle: .module, comment: "Error message") default: let v = values.map { $0.1 }.joined(separator: "', '") - return "\(values.count) unexpected arguments: '\(v)'" + return NSLocalizedString(String(format: "%1$@ unexpected arguments: '%2$@'", values.count, v), bundle: .module, comment: "Error message") } } @@ -303,15 +303,15 @@ extension ErrorMessageGenerator { let stringIndex = argument.index(argument.startIndex, offsetBy: offsetIndex+2) argument = "\'\(argument[stringIndex])\' in \(argument)" } - return "flag \(argument)" + return NSLocalizedString(String(format: "flag \(argument)", argument), bundle: .module, comment: "Info text") } // Note that the RHS of these coalescing operators cannot be reached at this time. - let dupeString = elementString(duplicate, arguments) ?? "position \(duplicate)" - let origString = elementString(previous, arguments) ?? "position \(previous)" + let dupeString = elementString(duplicate, arguments) ?? NSLocalizedString(String(format: "position %@", duplicate as! CVarArg), bundle: .module, comment: "Position message") + let origString = elementString(previous, arguments) ?? NSLocalizedString(String(format: "position %@", previous as! CVarArg), bundle: .module, comment: "Position message") //TODO: review this message once environment values are supported. - return "Value to be set with \(dupeString) had already been set with \(origString)" + return NSLocalizedString(String(format: "Value to be set with %1$@ had already been set with %2$@", dupeString, origString), bundle: .module, comment: "Error message, uses above string as argument") } func noValueMessage(key: InputKey) -> String? { @@ -321,12 +321,12 @@ extension ErrorMessageGenerator { } switch possibilities.count { case 0: - return "Missing expected argument" + return NSLocalizedString("Missing expected argument", bundle: .module, comment: "Error message") case 1: - return "Missing expected argument '\(possibilities.first!)'" + return NSLocalizedString(String(format: "Missing expected argument '%@'", possibilities.first!), bundle: .module, comment: "Error message") default: let p = possibilities.joined(separator: "', '") - return "Missing one of: '\(p)'" + return NSLocalizedString(String(format: "Missing one of: '%@'", p), bundle: .module, comment: "Error message") } } @@ -350,13 +350,13 @@ extension ErrorMessageGenerator { switch (name, valueName) { case let (n?, v?): - return "The value '\(value)' is invalid for '\(n.synopsisString) <\(v)>'\(customErrorMessage)" + return NSLocalizedString(String(format: "The value '%1$@' is invalid for '%2$@ <%3$@>'%4$@", value, n.synopsisString, v, customErrorMessage), bundle: .module, comment: "Error message") case let (_, v?): - return "The value '\(value)' is invalid for '<\(v)>'\(customErrorMessage)" + return NSLocalizedString(String(format: "The value '%1$@' is invalid for '<%2$@>'%3$@", value, v, customErrorMessage), bundle: .module, comment: "Error message") case let (n?, _): - return "The value '\(value)' is invalid for '\(n.synopsisString)'\(customErrorMessage)" + return NSLocalizedString(String(format: "The value '%1$@' is invalid for '%2$@'%3$@", value, n.synopsisString, customErrorMessage), bundle: .module, comment: "Error message") case (nil, nil): - return "The value '\(value)' is invalid.\(customErrorMessage)" + return NSLocalizedString(String(format: "The value '%1$@' is invalid.%2$@", value, customErrorMessage), bundle: .module, comment: "Error message") } } } From 087454c642222cac58f71e2ec9fd6ccb65fbad7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Sar=C4=B1?= Date: Wed, 22 Jul 2020 18:56:40 +0300 Subject: [PATCH 04/10] Add English .strings file --- Sources/ArgumentParser/CMakeLists.txt | 4 +++- .../Resources/en.lproj/Localizable.strings | Bin 0 -> 7450 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 Sources/ArgumentParser/Resources/en.lproj/Localizable.strings diff --git a/Sources/ArgumentParser/CMakeLists.txt b/Sources/ArgumentParser/CMakeLists.txt index 3489a40b8..558be123f 100644 --- a/Sources/ArgumentParser/CMakeLists.txt +++ b/Sources/ArgumentParser/CMakeLists.txt @@ -25,7 +25,9 @@ add_library(ArgumentParser Parsing/ParsedValues.swift Parsing/ParserError.swift Parsing/SplitArguments.swift - + + Resources/en.lproj/Localizable.strings + Usage/HelpCommand.swift Usage/HelpGenerator.swift Usage/MessageInfo.swift diff --git a/Sources/ArgumentParser/Resources/en.lproj/Localizable.strings b/Sources/ArgumentParser/Resources/en.lproj/Localizable.strings new file mode 100644 index 0000000000000000000000000000000000000000..4f5727cda68a5e3cfd6405d08dbfa3f7c52053a6 GIT binary patch literal 7450 zcmeHLSx*yD6h6=ViV3kni7jz?QbdRn<-tHi@P(Hmg-$3GyRiIu_51GW?L9M{X=l2@ zgJ~$!Is1O@{rhJ_)+Lh9(vWkhOJ7>j!|z)>xA3ch=XKeT6M?o0FnoN z56TtLQ^qH0%N0I#xdxv|ZXls9zre8xNXzl=;Juf(37DnWGSaeOR@NbOKTXjWk=V_k zOUli|u+r(xE~IxPhLt^hw_$UzYzL4FNV)>vHQ-%iSr*Ll-UV$3{OBptck!u%n~l_- z>|;H4t4yI#~q>(BN}|Z!P^e_$H2e9?_@lxz#@d0#7kOK zGd4XHedg`QgBko2IX|=3`tV_lSh5}(!<=Zi$k5}gIgLdRG-dqySo)Rq^EcSfJob6k zkb8KefqY|5vR9<@i~UCX&NQ^M>eqo*zY(_>S+DwKbp;!#w3fv=SY0Rem3`A~Y)?tQ zw>-bXf)?l^=j(fXhdy=aI`bXgG>2G>(YfnC?Bu?;SzF6dmDhz;z>2uVw{|CX*K7^) zajo1*e$ARD*1vghE2Bu?K7t+HLFUKsatsTZ{Ygth?iaGy>I^)iA5Suo3a2ipRtV8{8!*1*0T%dULK z_a6TLlu_RK7*@i#I^&KM;d2OoGPBp5ha%a8PNRFw7U0pK1|v5eIK6ddGh4i8YJ;s! z`{W{J6t}Y?ujCn`e*)c(VDFB6fmhY1uN{PXR^*L)uE^WLyAVl2r{z&p&lA*PRy?Xw z9eUTs;L7zwMs9LZ@6%u__ut9b^VXs2x=3*?JpMF#a8JuSZ9LW%>NiAs|0(J> zZ$GEA?Ek4Q`*h5$=@Nf!oORjfZSDHmyJos!QT(#;oXl6}+!}UU-q7e-{Q>&+@tbFV z-lV9;Iy=|oV=29-<3E2px3ST1e=o~9eX0t3YS@psOR1fl4HZ#Oafb?>aaKNy(wlxM zuccCz)#=CJRF$p<`i^sx`_371k2Mv!>fx5m&GXXjtjxs+ya_RQiC?G4RpK!UK1X@S zRCioP&pP%L_jEKW>q}Pl(_JpxSpL)QC1h<9YeoW`F^JJEXq#JTvZ(1AN1}{aW!jnF6xP! wbJ3r=LpCi#*7EI7ZUg?#VV+j!Nm#zEVfrq@&MfZBv9UJXD`t(G Date: Sun, 23 Aug 2020 23:43:29 +0300 Subject: [PATCH 05/10] Add Turkish localization --- .../Resources/tr.lproj/Localizable.strings | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 Sources/ArgumentParser/Resources/tr.lproj/Localizable.strings diff --git a/Sources/ArgumentParser/Resources/tr.lproj/Localizable.strings b/Sources/ArgumentParser/Resources/tr.lproj/Localizable.strings new file mode 100644 index 000000000..b6ba00750 --- /dev/null +++ b/Sources/ArgumentParser/Resources/tr.lproj/Localizable.strings @@ -0,0 +1,145 @@ + +/* Default value */ +" (default)" = " (öntanımlı)"; + +/* Command placeholder */ +"" = ""; + +/* Subcommand placeholder */ +"" = ""; + +/* Description */ +"Arguments" = "Değişkenler"; + +/* Error message */ +"Internal error. Invalid state while parsing command-line arguments." = "İç hata. Komut satırı değişkenleri ayrıştırılırken geçersiz durum alındı."; + +/* Error message */ +"Internal error. Parsing command-line arguments hit unimplemented code path." = "İç hata. Komut satırı değişkenleri ayrıştırımı henüz eklenmemiş kod yoluna çarptı."; + +/* Error message */ +"Missing expected argument" = "Beklenen değişken eksik."; + +/* Error message */ +"Missing required subcommand." = "Beklenen altkomut eksik."; + +/* Description */ +"Options" = "Seçenekler"; + +/* Help text */ +"Show help information." = "Yardım bilgisini göster."; + +/* Help text */ +"Show the version." = "Sürümü göster."; + +/* Description */ +"Subcommands" = "Altkomutlar"; + +/* Error message */ +"Unspecified version" = "Belirtilmemiş sürüm"; + +/* Placeholder */ +"value" = "değer"; + +/* Default value */ +"(default: %@)" = "(öntanımlı: %@)"; + +/* Usage help */ +"Usage: %@" = "Kullanım: %@"; + +/* Abstract */ +"OVERVIEW: %@" = "GENEL BAKIŞ: %@"; + +/* Help text */ +" + + See '%@ ' for detailed help. +" = " + + Ayrıntılı yardım için '%@ ' yazın. +"; + +/* Help text */ +" +%1$@\ +USAGE: %2$@ + +%3$@%4$@ +" = " +%1$@\ +KULLANIM: %2$@ + +%3$@%4$@ +"; + +/* Help text */ +"\n See '%@ --help' for more information." = "\n Daha fazla bilgi için '%@ --help' yazın."; + +/* Error message */ +"Error: %@\n" = "Hata: %@\n"; + +/* Error message */ +"Error: %@" = "Hata: %@"; + +/* Command options help */ +"%@ " = "%@ "; + +/* Error message */ +"Invalid option: %@" = "Geçersiz seçenek: %@"; + +/* Error message */ +"Invalid option: -%@" = "Geçersiz seçenek: -%@"; + +/* Error message */ +"Internal error. Parsing command-line arguments hit unimplemented code path." = "İç hata. Komut satırı değişkenleri ayrıştırımı henüz eklenmemiş kod yoluna çarptı."; + +/* Error message */ +"Internal error. Invalid state while parsing command-line arguments." = "İç hata. Komut satırı değişkenleri ayrıştırılırken geçersiz durum alındı."; + +/* Error message */ +"Unknown option '%@'" = "Bilinmeyen seçenek '%@'"; + +/* Error message */ +"Unknown option '%1$@'. Did you mean '%2$@'?" = "Bilinmeyen seçenek '%1$@'. Şunu mu demek istediniz: '%2$@'?"; + +/* Error message */ +"Unknown option '%@'."; + +/* Error message */ +"Missing value for '%1$@ <%2$@>'" = "'%1$@ <%2$@>' için eksik değer"; + +/* Error message */ +"Missing value for '%@'" = "'%@' için eksik değer"; + +/* Error message */ +"The option '1$%@' does not take any value, but '%2$@' was specified." = "'1$%@' seçeneği herhangi bir değer almaz, ancak '%2$@' belirtilmiş."; + +/* Error message */ +"Unexpected argument '%@'" = "Beklenmedik değişken '%@'"; + +/* Error message */ +"%1$@ unexpected arguments: '%2$@'" = "%1$@ beklenmedik değişken: '%2$@'"; + +/* Position message */ +"position %@" = "%@ konumu"; + +/* Error message, uses above string as argument */ +"Value to be set with %1$@ had already been set with %2$@" = "%1$@ ile ayarlanacak değer %2$@ ile zaten ayarlanmış"; + +/* Error message */ +"Missing expected argument '%@'" = "Beklenen değişken '%@' eksik"; + +/* Error message */ +"Missing one of: '%@'" = "Şunlardan biri eksik: '%@'"; + +/* Error message */ +"The value '%1$@' is invalid for '%2$@ <%3$@>'%4$@" = "'%1$@' değeri '%2$@ <%3$@>' için geçersiz (%4$@)"; + +/* Error message */ +"The value '%1$@' is invalid for '<%2$@>'%3$@" = "'%1$@' değeri '<%2$@>' için geçersiz (%3$@)"; + +/* Error message */ +"The value '%1$@' is invalid for '%2$@'%3$@" = "'%1$@' değeri '%2$@' için geçersiz (%3$@)"; + +/* Error message */ +"The value '%1$@' is invalid.%2$@" = "'%1$@' değeri geçersiz (%2$@)."; From b5830121ed823d0a0009f553d0292821168e517f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Sar=C4=B1?= Date: Mon, 24 Aug 2020 01:44:35 +0300 Subject: [PATCH 06/10] Update string changes --- .../Resources/en.lproj/Localizable.strings | Bin 7450 -> 8008 bytes .../Resources/tr.lproj/Localizable.strings | 35 +++++++++++------- .../ArgumentParser/Usage/UsageGenerator.swift | 16 ++++---- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/Sources/ArgumentParser/Resources/en.lproj/Localizable.strings b/Sources/ArgumentParser/Resources/en.lproj/Localizable.strings index 4f5727cda68a5e3cfd6405d08dbfa3f7c52053a6..afcb5471a6f145784380aa586d7a42522c80092f 100644 GIT binary patch delta 942 zcmbPbb;54LCGLqi0-IOyc(K%TF*q|MGUPF+Gn6nW09mC#I-enhAr(lcG9*Jq6&Q+v zG6g`G&rk%GNdc-ZX2<}l$pJz=1}=sGAe{;{p%_Ri098Y1T?So-bf8Ec5ElVuL8j^g z&CO@X1)7#q59F3GWCFQ)K;dGbL=liz02H%jutBoG4rpXKP^JWEr2<$r$dY`9GzKf6 z)e2Ck%HROxDFJ!5K&%906Y^X=$j>zM9bwNIfP;?&-%S?eGe(Ov^x#!M1TT^UK%PW| zZZXi)X~6I+0@CUXpm0+sJ`(HEV-d+3T1O(tv-QY{iRQk=^lSi7h^k3;vY}Ay9Ci94>O@1e$GdV$oWwMsE5s" = "%@ "; @@ -97,19 +90,35 @@ KULLANIM: %2$@ "Internal error. Invalid state while parsing command-line arguments." = "İç hata. Komut satırı değişkenleri ayrıştırılırken geçersiz durum alındı."; /* Error message */ -"Unknown option '%@'" = "Bilinmeyen seçenek '%@'"; +" +Can't autodetect a supported shell. +Please use --generate-completion-script= with one of: + %@ +" = " +Desteklenen bir kabuk kendiliğinden algılanamıyor. +Lütfen aşağıdakilerden biri ile --generate-completion-script= kullanın: + %@ +"; /* Error message */ -"Unknown option '%1$@'. Did you mean '%2$@'?" = "Bilinmeyen seçenek '%1$@'. Şunu mu demek istediniz: '%2$@'?"; +" +Can't generate completion scripts for '%@'. +Please use --generate-completion-script= with one of: + %@ +" = " +'%@' için tamamlama betikleri oluşturulamıyor. +Lütfen aşağıdakilerden biri ile --generate-completion-script= kullanın: + %@ +"; /* Error message */ -"Unknown option '%@'."; +"Unknown option '%@'." = "Bilinmeyen seçenek '%@'."; /* Error message */ -"Missing value for '%1$@ <%2$@>'" = "'%1$@ <%2$@>' için eksik değer"; +"Unknown option '%1$@'. Did you mean '%2$@'?" = "Bilinmeyen seçenek '%1$@'. Şunu mu demek istediniz: '%2$@'?"; /* Error message */ -"Missing value for '%@'" = "'%@' için eksik değer"; +"Missing value for '%1$@ <%2$@>'" = "'%1$@ <%2$@>' için eksik değer"; /* Error message */ "The option '1$%@' does not take any value, but '%2$@' was specified." = "'1$%@' seçeneği herhangi bir değer almaz, ancak '%2$@' belirtilmiş."; diff --git a/Sources/ArgumentParser/Usage/UsageGenerator.swift b/Sources/ArgumentParser/Usage/UsageGenerator.swift index 799783e96..649b37c8f 100644 --- a/Sources/ArgumentParser/Usage/UsageGenerator.swift +++ b/Sources/ArgumentParser/Usage/UsageGenerator.swift @@ -245,24 +245,24 @@ extension ErrorMessageGenerator { } var unsupportedAutodetectedShell: String { - """ + NSLocalizedString(String(format: """ Can't autodetect a supported shell. Please use --generate-completion-script= with one of: - \(CompletionShell.allCases.map { $0.rawValue }.joined(separator: " ")) - """ + %@ + """, CompletionShell.allCases.map { $0.rawValue }.joined(separator: " ")), bundle: .module, comment: "Error message") } func unsupportedShell(_ shell: String) -> String { - """ - Can't generate completion scripts for '\(shell)'. + NSLocalizedString(String(format: """ + Can't generate completion scripts for '%@'. Please use --generate-completion-script= with one of: - \(CompletionShell.allCases.map { $0.rawValue }.joined(separator: " ")) - """ + %@ + """, shell, CompletionShell.allCases.map { $0.rawValue }.joined(separator: " ")), bundle: .module, comment: "Error message") } func unknownOptionMessage(origin: InputOrigin.Element, name: Name) -> String { if case .short = name { - return NSLocalizedString(String(format: "Unknown option '%@'", name.synopsisString), bundle: .module, comment: "Error message") + return NSLocalizedString(String(format: "Unknown option '%@'.", name.synopsisString), bundle: .module, comment: "Error message") } // An empirically derived magic number From ffbb12b9aa28f9f57a6795e55def9bf38d097047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Sar=C4=B1?= Date: Mon, 24 Aug 2020 01:59:39 +0300 Subject: [PATCH 07/10] Minor adjustments --- .../Resources/en.lproj/Localizable.strings | Bin 8008 -> 8024 bytes .../Resources/tr.lproj/Localizable.strings | 10 +++++----- .../ArgumentParser/Usage/UsageGenerator.swift | 6 +++--- .../MathExampleTests.swift | 2 +- .../RepeatExampleTests.swift | 2 +- .../ErrorMessageTests.swift | 12 ++++++------ 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Sources/ArgumentParser/Resources/en.lproj/Localizable.strings b/Sources/ArgumentParser/Resources/en.lproj/Localizable.strings index afcb5471a6f145784380aa586d7a42522c80092f..4242090835478e1269231a87944f867f0f566464 100644 GIT binary patch delta 79 zcmX?Mcf)Q&oY3SCqHK)nlM4kp8I2|{; kullan /* Error message */ " -Can't generate completion scripts for '%@'. +Can't generate completion scripts for '%1$@'. Please use --generate-completion-script= with one of: - %@ + %2$@ " = " -'%@' için tamamlama betikleri oluşturulamıyor. +'%1$@' için tamamlama betikleri oluşturulamıyor. Lütfen aşağıdakilerden biri ile --generate-completion-script= kullanın: - %@ + %2$@ "; /* Error message */ @@ -121,7 +121,7 @@ Lütfen aşağıdakilerden biri ile --generate-completion-script= kullan "Missing value for '%1$@ <%2$@>'" = "'%1$@ <%2$@>' için eksik değer"; /* Error message */ -"The option '1$%@' does not take any value, but '%2$@' was specified." = "'1$%@' seçeneği herhangi bir değer almaz, ancak '%2$@' belirtilmiş."; +"The option '%1$@' does not take any value, but '%2$@' was specified." = "'%1$@' seçeneği herhangi bir değer almaz, ancak '%2$@' belirtilmiş."; /* Error message */ "Unexpected argument '%@'" = "Beklenmedik değişken '%@'"; diff --git a/Sources/ArgumentParser/Usage/UsageGenerator.swift b/Sources/ArgumentParser/Usage/UsageGenerator.swift index 649b37c8f..a78cf3893 100644 --- a/Sources/ArgumentParser/Usage/UsageGenerator.swift +++ b/Sources/ArgumentParser/Usage/UsageGenerator.swift @@ -254,9 +254,9 @@ extension ErrorMessageGenerator { func unsupportedShell(_ shell: String) -> String { NSLocalizedString(String(format: """ - Can't generate completion scripts for '%@'. + Can't generate completion scripts for '%1$@'. Please use --generate-completion-script= with one of: - %@ + %2$@ """, shell, CompletionShell.allCases.map { $0.rawValue }.joined(separator: " ")), bundle: .module, comment: "Error message") } @@ -298,7 +298,7 @@ extension ErrorMessageGenerator { } func unexpectedValueForOptionMessage(origin: InputOrigin.Element, name: Name, value: String) -> String? { - return NSLocalizedString(String(format: "The option '1$%@' does not take any value, but '%2$@' was specified.", name.synopsisString, value), bundle: .module, comment: "Error message") + return NSLocalizedString(String(format: "The option '%1$@' does not take any value, but '%2$@' was specified.", name.synopsisString, value), bundle: .module, comment: "Error message") } func unexpectedExtraValuesMessage(values: [(InputOrigin, String)]) -> String? { diff --git a/Tests/ArgumentParserExampleTests/MathExampleTests.swift b/Tests/ArgumentParserExampleTests/MathExampleTests.swift index 31380b871..c2a6ce205 100644 --- a/Tests/ArgumentParserExampleTests/MathExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/MathExampleTests.swift @@ -151,7 +151,7 @@ final class MathExampleTests: XCTestCase { AssertExecuteCommand( command: "math --foo", expected: """ - Error: Unknown option '--foo' + Error: Unknown option '--foo'. Usage: math add [--hex-output] [ ...] See 'math add --help' for more information. """, diff --git a/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift b/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift index 4e6b3d46a..88825dbf9 100644 --- a/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift @@ -81,7 +81,7 @@ final class RepeatExampleTests: XCTestCase { AssertExecuteCommand( command: "repeat --version hello", expected: """ - Error: Unknown option '--version' + Error: Unknown option '--version'. Usage: repeat [--count ] [--include-counter] See 'repeat --help' for more information. """, diff --git a/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift b/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift index 20eb687d7..3099904e6 100644 --- a/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift +++ b/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift @@ -32,19 +32,19 @@ extension ErrorMessageTests { } func testUnknownOption_1() { - AssertErrorMessage(Bar.self, ["--name", "a", "--format", "b", "--verbose"], "Unknown option '--verbose'") + AssertErrorMessage(Bar.self, ["--name", "a", "--format", "b", "--verbose"], "Unknown option '--verbose'.") } func testUnknownOption_2() { - AssertErrorMessage(Bar.self, ["--name", "a", "--format", "b", "-q"], "Unknown option '-q'") + AssertErrorMessage(Bar.self, ["--name", "a", "--format", "b", "-q"], "Unknown option '-q'.") } func testUnknownOption_3() { - AssertErrorMessage(Bar.self, ["--name", "a", "--format", "b", "-bar"], "Unknown option '-bar'") + AssertErrorMessage(Bar.self, ["--name", "a", "--format", "b", "-bar"], "Unknown option '-bar'.") } func testUnknownOption_4() { - AssertErrorMessage(Bar.self, ["--name", "a", "-foz", "b"], "Unknown option '-o'") + AssertErrorMessage(Bar.self, ["--name", "a", "-foz", "b"], "Unknown option '-o'.") } func testMissingValue_1() { @@ -127,11 +127,11 @@ extension ErrorMessageTests { } func testMispelledArgument_3() { - AssertErrorMessage(Qwz.self, ["--not-similar"], "Unknown option '--not-similar'") + AssertErrorMessage(Qwz.self, ["--not-similar"], "Unknown option '--not-similar'.") } func testMispelledArgument_4() { - AssertErrorMessage(Qwz.self, ["-x"], "Unknown option '-x'") + AssertErrorMessage(Qwz.self, ["-x"], "Unknown option '-x'.") } } From d2dc8c2588ba4fadaac2caf10438b61d454d7444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Sar=C4=B1?= Date: Mon, 24 Aug 2020 11:33:13 +0300 Subject: [PATCH 08/10] Uppercase according to the system locale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This prevents inaccurate capitalizations like i -> I in Turkish (should be İ instead). --- Sources/ArgumentParser/Usage/HelpGenerator.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index e53f02b0b..1f5c60fd9 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -88,7 +88,8 @@ internal struct HelpGenerator { guard !elements.isEmpty else { return "" } let renderedElements = elements.map { $0.rendered(screenWidth: screenWidth) }.joined() - return "\(String(describing: header).uppercased()):\n" + let locale = Locale.current + return "\(String(describing: header).uppercased(with: locale)):\n" + renderedElements } } From a54340e110323c3e6ff113d7c11dabf3a5668543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Sar=C4=B1?= Date: Wed, 26 Aug 2020 00:31:06 +0300 Subject: [PATCH 09/10] Add a test --- Tests/ArgumentParserUnitTests/CMakeLists.txt | 3 ++ .../LocalizationTests.swift | 45 +++++++++++++++++++ .../Resources/en.lproj/Localizable.strings | 5 +++ .../Resources/tr.lproj/Localizable.strings | 5 +++ 4 files changed, 58 insertions(+) create mode 100644 Tests/ArgumentParserUnitTests/LocalizationTests.swift create mode 100644 Tests/ArgumentParserUnitTests/Resources/en.lproj/Localizable.strings create mode 100644 Tests/ArgumentParserUnitTests/Resources/tr.lproj/Localizable.strings diff --git a/Tests/ArgumentParserUnitTests/CMakeLists.txt b/Tests/ArgumentParserUnitTests/CMakeLists.txt index 2599ff895..0b9066e0b 100644 --- a/Tests/ArgumentParserUnitTests/CMakeLists.txt +++ b/Tests/ArgumentParserUnitTests/CMakeLists.txt @@ -2,12 +2,15 @@ add_library(UnitTests ParsableArgumentsValidationTests.swift ErrorMessageTests.swift HelpGenerationTests.swift + LocalizationTests.swift NameSpecificationTests.swift SplitArgumentTests.swift StringSnakeCaseTests.swift StringWrappingTests.swift TreeTests.swift UsageGenerationTests.swift) +add_subdirectory(Resources/en.lproj) +add_subdirectory(Resources/tr.lproj) target_link_libraries(UnitTests PRIVATE ArgumentParser ArgumentParserTestHelpers) diff --git a/Tests/ArgumentParserUnitTests/LocalizationTests.swift b/Tests/ArgumentParserUnitTests/LocalizationTests.swift new file mode 100644 index 000000000..bd38bf509 --- /dev/null +++ b/Tests/ArgumentParserUnitTests/LocalizationTests.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +import ArgumentParserTestHelpers +@testable import ArgumentParser + +final class LocalizationTests: XCTestCase { +} + +extension LocalizationTests { + struct A: ParsableArguments { + @Option + var one: String = "42" + @Option(help: ArgumentHelp(NSLocalizedString("The second option", comment: "Help text"))) + var two: String + @Option(help: ArgumentHelp(NSLocalizedString("The third option", comment: "Help text"))) + var three: String + } + + func testHelpMessageLocalization() { + let locale = Locale.current.languageCode + + if locale == "tr" { + AssertHelp(for: A.self, equals: """ + KULLANIM: a [--one ] --two [--three ] + + SEÇENEKLER: + --one (öntanımlı: 42) + --two İkinci seçenek + --three Üçüncü seçenek + -h, --help Yardım bilgisini göster. + """) + + } + } +} diff --git a/Tests/ArgumentParserUnitTests/Resources/en.lproj/Localizable.strings b/Tests/ArgumentParserUnitTests/Resources/en.lproj/Localizable.strings new file mode 100644 index 000000000..bb6634d8c --- /dev/null +++ b/Tests/ArgumentParserUnitTests/Resources/en.lproj/Localizable.strings @@ -0,0 +1,5 @@ +/* Help text */ +"The second option" = "The second option"; + +/* Help text */ +"The third option" = "The third option"; diff --git a/Tests/ArgumentParserUnitTests/Resources/tr.lproj/Localizable.strings b/Tests/ArgumentParserUnitTests/Resources/tr.lproj/Localizable.strings new file mode 100644 index 000000000..23f54d3c9 --- /dev/null +++ b/Tests/ArgumentParserUnitTests/Resources/tr.lproj/Localizable.strings @@ -0,0 +1,5 @@ +/* Help text */ +"The second option" = "İkinci seçenek"; + +/* Help text */ +"The third option" = "Üçüncü seçenek"; From c6b49cf1c7eb9d81496a5d270ed5e599f66ba123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Sar=C4=B1?= Date: Wed, 26 Aug 2020 00:32:11 +0300 Subject: [PATCH 10/10] Attempt at a better uppercasing logic --- Sources/ArgumentParser/Usage/HelpGenerator.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index 1f5c60fd9..f037bd23c 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -89,8 +89,14 @@ internal struct HelpGenerator { let renderedElements = elements.map { $0.rendered(screenWidth: screenWidth) }.joined() let locale = Locale.current - return "\(String(describing: header).uppercased(with: locale)):\n" - + renderedElements + + if locale.languageCode == "en" { + return "\(String(describing: header).uppercased()):\n" + + renderedElements + } else { + return "\(String(describing: header).uppercased(with: locale)):\n" + + renderedElements + } } }