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) + 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 diff --git a/Sources/ArgumentParser/CMakeLists.txt b/Sources/ArgumentParser/CMakeLists.txt index bace0ad5e..d4532252b 100644 --- a/Sources/ArgumentParser/CMakeLists.txt +++ b/Sources/ArgumentParser/CMakeLists.txt @@ -30,7 +30,10 @@ add_library(ArgumentParser Parsing/ParsedValues.swift Parsing/ParserError.swift Parsing/SplitArguments.swift - + + Resources/en.lproj/Localizable.strings + Resources/tr.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 000000000..424209083 Binary files /dev/null and b/Sources/ArgumentParser/Resources/en.lproj/Localizable.strings differ diff --git a/Sources/ArgumentParser/Resources/tr.lproj/Localizable.strings b/Sources/ArgumentParser/Resources/tr.lproj/Localizable.strings new file mode 100644 index 000000000..7e0f99f26 --- /dev/null +++ b/Sources/ArgumentParser/Resources/tr.lproj/Localizable.strings @@ -0,0 +1,154 @@ +/* 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." = "Gerekli 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."; + +/* 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 */ +" +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 */ +" +Can't generate completion scripts for '%1$@'. +Please use --generate-completion-script= with one of: + %2$@ +" = " +'%1$@' için tamamlama betikleri oluşturulamıyor. +Lütfen aşağıdakilerden biri ile --generate-completion-script= kullanın: + %2$@ +"; + +/* 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 */ +"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ş."; + +/* 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$@)."; diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index d907038ef..f037bd23c 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") } } } @@ -86,8 +88,15 @@ internal struct HelpGenerator { guard !elements.isEmpty else { return "" } let renderedElements = elements.map { $0.rendered(screenWidth: screenWidth) }.joined() - return "\(String(describing: header).uppercased()):\n" - + renderedElements + let locale = Locale.current + + if locale.languageCode == "en" { + return "\(String(describing: header).uppercased()):\n" + + renderedElements + } else { + return "\(String(describing: header).uppercased(with: locale)):\n" + + renderedElements + } } } @@ -119,7 +128,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 @@ -162,7 +171,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 @@ -186,7 +195,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 } @@ -206,7 +215,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 @@ -215,7 +224,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 @@ -224,7 +233,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, @@ -240,7 +249,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 { @@ -257,7 +266,7 @@ 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 { @@ -267,18 +276,18 @@ internal struct HelpGenerator { } 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 accf643d6..63c9d7646 100644 --- a/Sources/ArgumentParser/Usage/MessageInfo.swift +++ b/Sources/ArgumentParser/Usage/MessageInfo.swift @@ -35,7 +35,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 @@ -71,7 +71,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. diff --git a/Sources/ArgumentParser/Usage/UsageGenerator.swift b/Sources/ArgumentParser/Usage/UsageGenerator.swift index d8d18d89b..6d70c0a65 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 } @@ -185,11 +185,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: @@ -238,31 +238,31 @@ 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") } 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 '%1$@'. Please use --generate-completion-script= with one of: - \(CompletionShell.allCases.map { $0.rawValue }.joined(separator: " ")) - """ + %2$@ + """, 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 "Unknown option '\(name.synopsisString)'" + return NSLocalizedString(String(format: "Unknown option '%@'.", name.synopsisString), bundle: .module, comment: "Error message") } // An empirically derived magic number @@ -284,21 +284,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 NSLocalizedString(String(format: "Missing value for '%@'", name.synopsisString), bundle: .module, comment: "Error message") } } 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? { @@ -306,10 +306,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") } } @@ -321,15 +321,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? { @@ -339,12 +339,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") } } @@ -368,13 +368,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") } } } 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/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/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'.") } } 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";