diff --git a/README.md b/README.md index b7415c7..a62a8d2 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,7 @@ Here's an overview of all options available for the sub command `interfaces`: - `path` (required), `override` and `verbose` (see [Options for all Sub Commands](#options-for-all-sub-commands) above) - `default-to-base` - `unstripped` +- `ignore-empty-strings` #### Default to Base (aka `-b`, `--default-to-base`) *optional* @@ -211,6 +212,16 @@ Example: $ bartycrouch interfaces -p "/path/to/project" -u ``` +#### Ignore empty strings (aka `-i`, `--ignore-empty-strings`) *optional* + +With this options set, strings that are empty, or only contain whitespace, will not appear in the localization files. + +Example: + +```shell +$ bartycrouch interfaces -p "/path/to/project" -i +``` + --- ### Options for `code` diff --git a/Sources/Code/CommandLineActor.swift b/Sources/Code/CommandLineActor.swift index 9973ae9..843a23f 100644 --- a/Sources/Code/CommandLineActor.swift +++ b/Sources/Code/CommandLineActor.swift @@ -34,8 +34,8 @@ public class CommandLineActor { customLocalizableName: customLocalizableName.value ) - case let .interfacesOptions(defaultToBaseOption, unstripped): - self.actOnInterfaces(path: path, override: override, verbose: verbose, defaultToBase: defaultToBaseOption.value, unstripped: unstripped.value) + case let .interfacesOptions(defaultToBaseOption, unstripped, ignoreEmptyStrings): + self.actOnInterfaces(path: path, override: override, verbose: verbose, defaultToBase: defaultToBaseOption.value, unstripped: unstripped.value, ignoreEmptyStrings: ignoreEmptyStrings.value) case let .translateOptions(idOption, secretOption, localeOption): guard let id = idOption.value else { @@ -90,7 +90,7 @@ public class CommandLineActor { ) } - private func actOnInterfaces(path: String, override: Bool, verbose: Bool, defaultToBase: Bool, unstripped: Bool) { + private func actOnInterfaces(path: String, override: Bool, verbose: Bool, defaultToBase: Bool, unstripped: Bool, ignoreEmptyStrings: Bool) { let inputFilePaths = StringsFilesSearch.shared.findAllIBFiles(within: path, withLocale: "Base") guard !inputFilePaths.isEmpty else { print("No input files found.", level: .warning); exit(EX_OK) } @@ -109,7 +109,7 @@ public class CommandLineActor { } self.incrementalInterfacesUpdate( - inputFilePath, outputStringsFilePaths, override: override, verbose: verbose, defaultToBase: defaultToBase, unstripped: unstripped + inputFilePath, outputStringsFilePaths, override: override, verbose: verbose, defaultToBase: defaultToBase, unstripped: unstripped, ignoreEmptyStrings: ignoreEmptyStrings ) } } @@ -320,7 +320,7 @@ public class CommandLineActor { } private func incrementalInterfacesUpdate( - _ inputFilePath: String, _ outputStringsFilePaths: [String], override: Bool, verbose: Bool, defaultToBase: Bool, unstripped: Bool + _ inputFilePath: String, _ outputStringsFilePaths: [String], override: Bool, verbose: Bool, defaultToBase: Bool, unstripped: Bool, ignoreEmptyStrings: Bool ) { let extractedStringsFilePath = inputFilePath + ".tmpstrings" @@ -339,7 +339,8 @@ public class CommandLineActor { withStringsFileAtPath: extractedStringsFilePath, addNewValuesAsEmpty: !defaultToBase, override: override, - keepWhitespaceSurroundings: unstripped + keepWhitespaceSurroundings: unstripped, + ignoreEmptyStrings: ignoreEmptyStrings ) if verbose { diff --git a/Sources/Code/CommandLineParser.swift b/Sources/Code/CommandLineParser.swift index 982401f..4a96b1e 100644 --- a/Sources/Code/CommandLineParser.swift +++ b/Sources/Code/CommandLineParser.swift @@ -34,7 +34,7 @@ public class CommandLineParser { customFunction: StringOption, customLocalizableName: StringOption ) - case interfacesOptions(defaultToBase: BoolOption, unstripped: BoolOption) + case interfacesOptions(defaultToBase: BoolOption, unstripped: BoolOption, ignoreEmptyStrings: BoolOption) case translateOptions(id: StringOption, secret: StringOption, locale: StringOption) case normalizeOptions( locale: StringOption, @@ -143,6 +143,13 @@ public class CommandLineParser { required: false, helpMessage: "Fails if Strings files contain duplicate keys. Designed to be used as part of a CI service." ) + + private let ignoreEmptyStrings = BoolOption( + shortFlag: "i", + longFlag: "ignore-empty-strings", + required: false, + helpMessage: "Ignores empty strings and strings consisting of whitespace only." + ) // MARK: - Initializers public init(arguments: [String] = CommandLine.arguments) { @@ -260,9 +267,9 @@ public class CommandLineParser { ) let commonOptions: CommonOptions = (path: path, override: override, verbose: verbose) - let subCommandOptions = SubCommandOptions.interfacesOptions(defaultToBase: defaultToBase, unstripped: unstripped) + let subCommandOptions = SubCommandOptions.interfacesOptions(defaultToBase: defaultToBase, unstripped: unstripped, ignoreEmptyStrings: ignoreEmptyStrings) - commandLine.addOptions(path, override, verbose, defaultToBase, unstripped) + commandLine.addOptions(path, override, verbose, defaultToBase, unstripped, ignoreEmptyStrings) return (commandLine, commonOptions, subCommandOptions) } diff --git a/Sources/Code/StringsFileUpdater.swift b/Sources/Code/StringsFileUpdater.swift index 92cc02b..4c471ee 100644 --- a/Sources/Code/StringsFileUpdater.swift +++ b/Sources/Code/StringsFileUpdater.swift @@ -35,7 +35,8 @@ public class StringsFileUpdater { withStringsFileAtPath otherStringFilePath: String, addNewValuesAsEmpty: Bool, ignoreBaseKeysAndComment ignores: [String] = defaultIgnoreKeys, override: Bool = false, updateCommentWithBase: Bool = true, keepExistingKeys: Bool = false, - overrideComments: Bool = false, sortByKeys: Bool = false, keepWhitespaceSurroundings: Bool = false + overrideComments: Bool = false, sortByKeys: Bool = false, keepWhitespaceSurroundings: Bool = false, + ignoreEmptyStrings: Bool = false ) { do { let newContentString = try String(contentsOfFile: otherStringFilePath) @@ -59,6 +60,7 @@ public class StringsFileUpdater { for newTranslation in newTranslations { // skip keys marked for ignore guard !newTranslation.value.containsAny(of: ignores) else { continue } + if ignoreEmptyStrings && newTranslation.value.isBlank { continue } // Skip keys that have been marked for ignore in comment if let newComment = newTranslation.comment, newComment.containsAny(of: ignores) { continue } diff --git a/Tests/Assets/Strings Files/NewExample.strings b/Tests/Assets/Strings Files/NewExample.strings index c2cedee..3ad1b8e 100644 Binary files a/Tests/Assets/Strings Files/NewExample.strings and b/Tests/Assets/Strings Files/NewExample.strings differ diff --git a/Tests/Assets/Strings Files/OldExample.strings b/Tests/Assets/Strings Files/OldExample.strings index a0223e0..a3341dc 100644 Binary files a/Tests/Assets/Strings Files/OldExample.strings and b/Tests/Assets/Strings Files/OldExample.strings differ diff --git a/Tests/Code/StringsFileUpdaterTests.swift b/Tests/Code/StringsFileUpdaterTests.swift index 032badc..2a1f27f 100644 --- a/Tests/Code/StringsFileUpdaterTests.swift +++ b/Tests/Code/StringsFileUpdaterTests.swift @@ -67,6 +67,7 @@ class StringsFileUpdaterTests: XCTestCase { ("cHL-Zc-L39.normalTitle", "Example Button 3", " Class = \"UIButton\"; normalTitle = \"Example Button 3\"; ObjectID = \"cHL-Zc-L39\"; "), ("test.key", "This is a test key", " Completely custom comment structure in one line "), ("test.key.ignored", "This is a test key to be ignored #bc-ignore!", " Completely custom comment structure in one line to be ignored "), + ("test.key.ignoreEmptyStrings", " ", " This key should be ignored when ignoreEmptyKeys is set "), ("abc-12-345.normalTitle", "😀", " Class = \"UIButton\"; normalTitle = \"😀\"; ObjectID = \"abc-12-345\"; "), ("em1-3S-vgp.text", "Refrakční vzdálenost v metrech", " Class = \"UILabel\"; text = \"Refraktionsentfernung in Meter\"; ObjectID = \"em1-3S-vgp\"; ") @@ -120,6 +121,8 @@ class StringsFileUpdaterTests: XCTestCase { "\"test.key\" = \"This is a test key\";", "", "/* Completely custom comment structure in one line to be ignored */", "\"test.key.ignored\" = \"This is a test key to be ignored #bc-ignore!\";", "", + "/* This key should be ignored when ignoreEmptyKeys is set */", + "\"test.key.ignoreEmptyStrings\" = \" \";", "", "/* Class = \"UIButton\"; normalTitle = \"😀\"; ObjectID = \"abc-12-345\"; */", "\"abc-12-345.normalTitle\" = \"😀\";", "", "/* Class = \"UILabel\"; text = \"Refraktionsentfernung in Meter\"; ObjectID = \"em1-3S-vgp\"; */", @@ -133,7 +136,7 @@ class StringsFileUpdaterTests: XCTestCase { XCTAssertEqual(oldLinesInFile[index], expectedLine) } - stringsFileUpdater.incrementallyUpdateKeys(withStringsFileAtPath: newStringsFilePath, addNewValuesAsEmpty: true, updateCommentWithBase: false) + stringsFileUpdater.incrementallyUpdateKeys(withStringsFileAtPath: newStringsFilePath, addNewValuesAsEmpty: true, updateCommentWithBase: false, ignoreEmptyStrings: true) let expectedLinesAfterIncrementalUpdate = [ "", "/* Class = \"UIButton\"; normalTitle = \"Example Button 1\"; ObjectID = \"35F-cl-mdI\"; */", @@ -180,6 +183,8 @@ class StringsFileUpdaterTests: XCTestCase { "\"test.key\" = \"This is a test key\";", "", "/* Completely custom comment structure in one line to be ignored */", "\"test.key.ignored\" = \"This is a test key to be ignored #bc-ignore!\";", "", + "/* This key should be ignored when ignoreEmptyKeys is set */", + "\"test.key.ignoreEmptyStrings\" = \" \";", "", "/* Class = \"UIButton\"; normalTitle = \"😀\"; ObjectID = \"abc-12-345\"; */", "\"abc-12-345.normalTitle\" = \"😀\";", "", "/* Class = \"UILabel\"; text = \"Refraktionsentfernung in Meter\"; ObjectID = \"em1-3S-vgp\"; */", @@ -197,7 +202,8 @@ class StringsFileUpdaterTests: XCTestCase { withStringsFileAtPath: newStringsFilePath, addNewValuesAsEmpty: true, updateCommentWithBase: false, - keepWhitespaceSurroundings: true + keepWhitespaceSurroundings: true, + ignoreEmptyStrings: true ) let expectedLinesAfterIncrementalUpdate = [ @@ -245,6 +251,8 @@ class StringsFileUpdaterTests: XCTestCase { "\"test.key\" = \"This is a test key\";", "", "/* Completely custom comment structure in one line to be ignored */", "\"test.key.ignored\" = \"This is a test key to be ignored #bc-ignore!\";", "", + "/* This key should be ignored when ignoreEmptyKeys is set */", + "\"test.key.ignoreEmptyStrings\" = \" \";", "", "/* Class = \"UIButton\"; normalTitle = \"😀\"; ObjectID = \"abc-12-345\"; */", "\"abc-12-345.normalTitle\" = \"😀\";", "", "/* Class = \"UILabel\"; text = \"Refraktionsentfernung in Meter\"; ObjectID = \"em1-3S-vgp\"; */", @@ -258,7 +266,7 @@ class StringsFileUpdaterTests: XCTestCase { XCTAssertEqual(oldLinesInFile[index], expectedLine) } - stringsFileUpdater.incrementallyUpdateKeys(withStringsFileAtPath: newStringsFilePath, addNewValuesAsEmpty: false) + stringsFileUpdater.incrementallyUpdateKeys(withStringsFileAtPath: newStringsFilePath, addNewValuesAsEmpty: false, ignoreEmptyStrings: true) let expectedLinesAfterIncrementalUpdate = [ "", "/* Class = \"UIButton\"; normalTitle = \"New Example Button 1\"; ObjectID = \"35F-cl-mdI\"; */",