From 2036bbe0259e551b8c1d57a9de6e7afce688c195 Mon Sep 17 00:00:00 2001 From: Michal Srutek Date: Sun, 5 Apr 2020 10:57:45 +0200 Subject: [PATCH 1/6] Code formatting --- .../LocalizeExample/Sources/AppDelegate.swift | 1 - .../Sources/ViewController.swift | 10 +- Localize.swift | 104 +++++++++--------- 3 files changed, 54 insertions(+), 61 deletions(-) diff --git a/Example/LocalizeExample/Sources/AppDelegate.swift b/Example/LocalizeExample/Sources/AppDelegate.swift index afb844d..9accffe 100644 --- a/Example/LocalizeExample/Sources/AppDelegate.swift +++ b/Example/LocalizeExample/Sources/AppDelegate.swift @@ -17,4 +17,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } } - diff --git a/Example/LocalizeExample/Sources/ViewController.swift b/Example/LocalizeExample/Sources/ViewController.swift index 4d88dbd..7a34936 100644 --- a/Example/LocalizeExample/Sources/ViewController.swift +++ b/Example/LocalizeExample/Sources/ViewController.swift @@ -9,14 +9,12 @@ import UIKit class ViewController: UIViewController { - override func viewDidLoad() { super.viewDidLoad() - + // Here simulates their usage in code - let _ = NSLocalizedString("UntranslatedKey", comment: "") - let _ = NSLocalizedString("IgnoredUntranslatedKey", comment: "") - let _ = NSLocalizedString("MissingKey", comment: "") + _ = NSLocalizedString("UntranslatedKey", comment: "") + _ = NSLocalizedString("IgnoredUntranslatedKey", comment: "") + _ = NSLocalizedString("MissingKey", comment: "") } } - diff --git a/Localize.swift b/Localize.swift index 5e7ead4..46e420a 100755 --- a/Localize.swift +++ b/Localize.swift @@ -2,14 +2,12 @@ import Foundation - // WHAT // 1. Find Missing keys in other Localisation files // 2. Find potentially untranslated keys // 3. Find Duplicate keys // 4. Find Unused keys and generate script to delete them all at once - // MARK: Start Of Configurable Section /* @@ -44,7 +42,7 @@ let patterns = [ For instance, Keys that you concatenate will not be detected by the parsing so you want to add them here in order not to create false positives :) */ -let ignoredFromUnusedKeys = [String]() +let ignoredFromUnusedKeys: [String] = [] /* example let ignoredFromUnusedKeys = [ "NotificationNoOne", @@ -86,13 +84,13 @@ if enabled == false { // Detect list of supported languages automatically func listSupportedLanguages() -> [String] { - var sl = [String]() + var sl: [String] = [] let path = FileManager.default.currentDirectoryPath + relativeLocalizableFolders if !FileManager.default.fileExists(atPath: path) { print("Invalid configuration: \(path) does not exist.") exit(1) } - let enumerator: FileManager.DirectoryEnumerator? = FileManager.default.enumerator(atPath: path) + let enumerator = FileManager.default.enumerator(atPath: path) let extensionName = "lproj" print("Found these languages:") while let element = enumerator?.nextObject() as? String { @@ -105,23 +103,22 @@ func listSupportedLanguages() -> [String] { return sl } - let supportedLanguages = listSupportedLanguages() -var ignoredFromSameTranslation = [String:[String]]() +var ignoredFromSameTranslation: [String: [String]] = [:] let path = FileManager.default.currentDirectoryPath + relativeLocalizableFolders var numberOfWarnings = 0 var numberOfErrors = 0 struct LocalizationFiles { var name = "" - var keyValue = [String:String]() - var linesNumbers = [String:Int]() - + var keyValue: [String: String] = [:] + var linesNumbers: [String: Int] = [:] + init(name: String) { self.name = name process() } - + mutating func process() { if sanitizeFiles { removeCommentsFromFile() @@ -130,29 +127,28 @@ struct LocalizationFiles { } let location = singleLanguage ? "\(path)/Localizable.strings" : "\(path)/\(name).lproj/Localizable.strings" if let string = try? String(contentsOfFile: location, encoding: .utf8) { - let lines = string.components(separatedBy: CharacterSet.newlines) - keyValue = [String:String]() + let lines = string.components(separatedBy: .newlines) + keyValue = [:] let pattern = "\"(.*)\" = \"(.+)\";" let regex = try? NSRegularExpression(pattern: pattern, options: []) - var ignoredTranslation = [String]() - + var ignoredTranslation: [String] = [] + for (lineNumber, line) in lines.enumerated() { - let range = NSRange(location:0, length:(line as NSString).length) - - + let range = NSRange(location: 0, length: (line as NSString).length) + // Ignored pattern let ignoredPattern = "\"(.*)\" = \"(.+)\"; *\\/\\/ *ignore-same-translation-warning" let ignoredRegex = try? NSRegularExpression(pattern: ignoredPattern, options: []) - if let ignoredMatch = ignoredRegex?.firstMatch(in:line, + if let ignoredMatch = ignoredRegex?.firstMatch(in: line, options: [], range: range) { - let key = (line as NSString).substring(with: ignoredMatch.range(at:1)) + let key = (line as NSString).substring(with: ignoredMatch.range(at: 1)) ignoredTranslation.append(key) } if let firstMatch = regex?.firstMatch(in: line, options: [], range: range) { - let key = (line as NSString).substring(with: firstMatch.range(at:1)) - let value = (line as NSString).substring(with: firstMatch.range(at:2)) - if let _ = keyValue[key] { + let key = (line as NSString).substring(with: firstMatch.range(at: 1)) + let value = (line as NSString).substring(with: firstMatch.range(at: 2)) + if let _ = keyValue[key] { let str = "\(path)/\(name).lproj" + "/Localizable.strings:\(linesNumbers[key]!): " + "error: [Redundance] \"\(key)\" " @@ -161,7 +157,7 @@ struct LocalizationFiles { numberOfErrors += 1 } else { keyValue[key] = value - linesNumbers[key] = lineNumber+1 + linesNumbers[key] = lineNumber + 1 } } } @@ -169,54 +165,54 @@ struct LocalizationFiles { ignoredFromSameTranslation[name] = ignoredTranslation } } - + func rebuildFileString(from lines: [String]) -> String { return lines.reduce("") { (r: String, s: String) -> String in - return (r == "") ? (r + s) : (r + "\n" + s) + (r == "") ? (r + s) : (r + "\n" + s) } } - + func removeEmptyLinesFromFile() { let location = "\(path)/\(name).lproj/Localizable.strings" if let string = try? String(contentsOfFile: location, encoding: .utf8) { - var lines = string.components(separatedBy: CharacterSet.newlines) - lines = lines.filter { $0.trimmingCharacters(in: CharacterSet.whitespaces) != "" } + var lines = string.components(separatedBy: .newlines) + lines = lines.filter { $0.trimmingCharacters(in: .whitespaces) != "" } let s = rebuildFileString(from: lines) - try? s.write(toFile:location, atomically:false, encoding:String.Encoding.utf8) + try? s.write(toFile: location, atomically: false, encoding: .utf8) } } - + func removeCommentsFromFile() { let location = "\(path)/\(name).lproj/Localizable.strings" if let string = try? String(contentsOfFile: location, encoding: .utf8) { - var lines = string.components(separatedBy: CharacterSet.newlines) + var lines = string.components(separatedBy: .newlines) lines = lines.filter { !$0.hasPrefix("//") } let s = rebuildFileString(from: lines) - try? s.write(toFile:location, atomically:false, encoding:String.Encoding.utf8) + try? s.write(toFile: location, atomically: false, encoding: .utf8) } } - + func sortLinesAlphabetically() { let location = "\(path)/\(name).lproj/Localizable.strings" if let string = try? String(contentsOfFile: location, encoding: .utf8) { - let lines = string.components(separatedBy: CharacterSet.newlines) - + let lines = string.components(separatedBy: .newlines) + var s = "" - for (i,l) in sortAlphabetically(lines).enumerated() { + for (i, l) in sortAlphabetically(lines).enumerated() { s += l - if (i != lines.count - 1) { + if i != lines.count - 1 { s += "\n" } } - try? s.write(toFile:location, atomically:false, encoding:String.Encoding.utf8) + try? s.write(toFile: location, atomically: false, encoding: .utf8) } } - - func removeEmptyLinesFromLines(_ lines:[String]) -> [String] { - return lines.filter { $0.trimmingCharacters(in: CharacterSet.whitespaces) != "" } + + func removeEmptyLinesFromLines(_ lines: [String]) -> [String] { + return lines.filter { $0.trimmingCharacters(in: .whitespaces) != "" } } - - func sortAlphabetically(_ lines:[String]) -> [String] { + + func sortAlphabetically(_ lines: [String]) -> [String] { return lines.sorted() } } @@ -229,24 +225,25 @@ let localizationFiles = supportedLanguages .map { LocalizationFiles(name: $0) } // MARK: - Detect Unused Keys + let sourcesPath = FileManager.default.currentDirectoryPath + relativeSourceFolder let fileManager = FileManager.default -let enumerator = fileManager.enumerator(atPath:sourcesPath) -var localizedStrings = [String]() +let enumerator = fileManager.enumerator(atPath: sourcesPath) +var localizedStrings: [String] = [] while let swiftFileLocation = enumerator?.nextObject() as? String { - // checks the extension // TODO OBJC? - if swiftFileLocation.hasSuffix(".swift") || swiftFileLocation.hasSuffix(".m") || swiftFileLocation.hasSuffix(".mm") { + // checks the extension + if swiftFileLocation.hasSuffix(".swift") || swiftFileLocation.hasSuffix(".m") || swiftFileLocation.hasSuffix(".mm") { let location = "\(sourcesPath)/\(swiftFileLocation)" if let string = try? String(contentsOfFile: location, encoding: .utf8) { for p in patterns { let regex = try? NSRegularExpression(pattern: p, options: []) - let range = NSRange(location:0, length:(string as NSString).length) //Obj c wa + let range = NSRange(location: 0, length: (string as NSString).length) // Obj c wa regex?.enumerateMatches(in: string, options: [], range: range, - using: { (result, _, _) in + using: { result, _, _ in if let r = result { - let value = (string as NSString).substring(with:r.range(at:r.numberOfRanges-1)) + let value = (string as NSString).substring(with: r.range(at: r.numberOfRanges - 1)) localizedStrings.append(value) } }) @@ -272,7 +269,7 @@ for v in unused { replaceCommand += "|" } replaceCommand += v - if counter == unused.count-1 { + if counter == unused.count - 1 { replaceCommand += ")\" = \".*\";" } counter += 1 @@ -280,8 +277,8 @@ for v in unused { print(replaceCommand) - // MARK: - Compare each translation file against master (en) + for file in localizationFiles { for k in masterLocalizationfile.keyValue.keys { if let v = file.keyValue[k] { @@ -310,4 +307,3 @@ print("Number of errors : \(numberOfErrors)") if numberOfErrors > 0 { exit(1) } - From ba13291fe9cc836e70c51abbdf867ca27fdfd185 Mon Sep 17 00:00:00 2001 From: Michal Srutek Date: Sun, 5 Apr 2020 11:57:06 +0200 Subject: [PATCH 2/6] Fix example project path --- Localize.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 Localize.swift diff --git a/Localize.swift b/Localize.swift old mode 100755 new mode 100644 index 46e420a..648be4c --- a/Localize.swift +++ b/Localize.swift @@ -24,7 +24,7 @@ let relativeLocalizableFolders = "/Resources/Languages" This is the path of your source folder which will be used in searching for the localization keys you actually use in your project */ -let relativeSourceFolder = "/FormaCar" +let relativeSourceFolder = "/Sources" /* Those are the regex patterns to recognize localizations. From 900f439c95c43bb4002bb2f822e9604417dea0f7 Mon Sep 17 00:00:00 2001 From: Michal Srutek Date: Sun, 5 Apr 2020 12:05:25 +0200 Subject: [PATCH 3/6] Update for Swift 5, remove Main storyboard --- .../LocalizeExample.xcodeproj/project.pbxproj | 9 ++++--- Example/LocalizeExample/Resources/Info.plist | 2 -- .../Resources/Storyboards/Main.storyboard | 24 ------------------- .../LocalizeExample/Sources/AppDelegate.swift | 2 +- 4 files changed, 5 insertions(+), 32 deletions(-) delete mode 100644 Example/LocalizeExample/Resources/Storyboards/Main.storyboard diff --git a/Example/LocalizeExample/LocalizeExample.xcodeproj/project.pbxproj b/Example/LocalizeExample/LocalizeExample.xcodeproj/project.pbxproj index 2b26cc2..63a4d0a 100644 --- a/Example/LocalizeExample/LocalizeExample.xcodeproj/project.pbxproj +++ b/Example/LocalizeExample/LocalizeExample.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 994630681FAC68410004A9A2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 9946306A1FAC68410004A9A2 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 99EB54961FAC6D3300AD728C /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; - 99EB54971FAC6D3300AD728C /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 99EB54981FAC6D3300AD728C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 99EB549B1FAC6D3300AD728C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 99EB549C1FAC6D3300AD728C /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; @@ -76,7 +75,6 @@ isa = PBXGroup; children = ( 99EB54961FAC6D3300AD728C /* LaunchScreen.storyboard */, - 99EB54971FAC6D3300AD728C /* Main.storyboard */, ); path = Storyboards; sourceTree = ""; @@ -122,6 +120,7 @@ TargetAttributes = { 994630641FAC68410004A9A2 = { CreatedOnToolsVersion = 9.0; + LastSwiftMigration = 1140; ProvisioningStyle = Automatic; }; }; @@ -169,7 +168,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/../../Localize.swift"; + shellScript = "${SRCROOT}/../../Localize.swift\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -318,7 +317,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.freshos.LocalizeExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -333,7 +332,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.freshos.LocalizeExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; diff --git a/Example/LocalizeExample/Resources/Info.plist b/Example/LocalizeExample/Resources/Info.plist index 566b58b..1900826 100644 --- a/Example/LocalizeExample/Resources/Info.plist +++ b/Example/LocalizeExample/Resources/Info.plist @@ -26,8 +26,6 @@ UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities armv7 diff --git a/Example/LocalizeExample/Resources/Storyboards/Main.storyboard b/Example/LocalizeExample/Resources/Storyboards/Main.storyboard deleted file mode 100644 index 03c13c2..0000000 --- a/Example/LocalizeExample/Resources/Storyboards/Main.storyboard +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/LocalizeExample/Sources/AppDelegate.swift b/Example/LocalizeExample/Sources/AppDelegate.swift index 9accffe..9c89fe4 100644 --- a/Example/LocalizeExample/Sources/AppDelegate.swift +++ b/Example/LocalizeExample/Sources/AppDelegate.swift @@ -13,7 +13,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { return true } } From 06ce8a2b69397060de1ff6cff592f31b1ed8951e Mon Sep 17 00:00:00 2001 From: Michal Srutek Date: Sun, 5 Apr 2020 12:20:03 +0200 Subject: [PATCH 4/6] Add check for untranslated --- .../Sources/ViewController.swift | 3 +- Localize.swift | 29 +++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Example/LocalizeExample/Sources/ViewController.swift b/Example/LocalizeExample/Sources/ViewController.swift index 7a34936..57f0f3b 100644 --- a/Example/LocalizeExample/Sources/ViewController.swift +++ b/Example/LocalizeExample/Sources/ViewController.swift @@ -8,7 +8,7 @@ import UIKit -class ViewController: UIViewController { +final class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() @@ -16,5 +16,6 @@ class ViewController: UIViewController { _ = NSLocalizedString("UntranslatedKey", comment: "") _ = NSLocalizedString("IgnoredUntranslatedKey", comment: "") _ = NSLocalizedString("MissingKey", comment: "") + _ = NSLocalizedString("MissingKeyFromMain", comment: "This key is not present in master translation file") } } diff --git a/Localize.swift b/Localize.swift index 648be4c..c786486 100644 --- a/Localize.swift +++ b/Localize.swift @@ -64,6 +64,12 @@ let sanitizeFiles = false */ let singleLanguage = false +/* + Determines if we should show errors if there's a key within the app + that does not appear in master translations. +*/ +let checkForUntranslated = true + // MARK: End Of Configurable Section // MARK: - @@ -219,7 +225,7 @@ struct LocalizationFiles { // MARK: - Load Localisation Files in memory -let masterLocalizationfile = LocalizationFiles(name: masterLanguage) +let masterLocalizationFile = LocalizationFiles(name: masterLanguage) let localizationFiles = supportedLanguages .filter { $0 != masterLanguage } .map { LocalizationFiles(name: $0) } @@ -252,16 +258,17 @@ while let swiftFileLocation = enumerator?.nextObject() as? String { } } -var masterKeys = Set(masterLocalizationfile.keyValue.keys) +var masterKeys = Set(masterLocalizationFile.keyValue.keys) let usedKeys = Set(localizedStrings) let ignored = Set(ignoredFromUnusedKeys) let unused = masterKeys.subtracting(usedKeys).subtracting(ignored) +let untranslated = usedKeys.subtracting(masterKeys) // Here generate Xcode regex Find and replace script to remove dead keys all at once! var replaceCommand = "\"(" var counter = 0 for v in unused { - var str = "\(path)/\(masterLocalizationfile.name).lproj/Localizable.strings:\(masterLocalizationfile.linesNumbers[v]!): " + var str = "\(path)/\(masterLocalizationFile.name).lproj/Localizable.strings:\(masterLocalizationFile.linesNumbers[v]!): " str += "error: [Unused Key] \"\(v)\" is never used" print(str) numberOfErrors += 1 @@ -280,9 +287,9 @@ print(replaceCommand) // MARK: - Compare each translation file against master (en) for file in localizationFiles { - for k in masterLocalizationfile.keyValue.keys { + for k in masterLocalizationFile.keyValue.keys { if let v = file.keyValue[k] { - if v == masterLocalizationfile.keyValue[k] { + if v == masterLocalizationFile.keyValue[k] { if !ignoredFromSameTranslation[file.name]!.contains(k) { let str = "\(path)/\(file.name).lproj/Localizable.strings" + ":\(file.linesNumbers[k]!): " @@ -293,7 +300,7 @@ for file in localizationFiles { } } } else { - var str = "\(path)/\(file.name).lproj/Localizable.strings:\(masterLocalizationfile.linesNumbers[k]!): " + var str = "\(path)/\(file.name).lproj/Localizable.strings:\(masterLocalizationFile.linesNumbers[k]!): " str += "error: [Missing] \"\(k)\" missing from \(file.name.uppercased()) file" print(str) numberOfErrors += 1 @@ -301,6 +308,16 @@ for file in localizationFiles { } } +if checkForUntranslated { + for key in untranslated { + var str = "\(path)/\(masterLocalizationFile.name).lproj/Localizable.strings:1: " + str += "error: [Missing Translation] \(key) is not translated" + + print(str) + numberOfErrors += 1 + } +} + print("Number of warnings : \(numberOfWarnings)") print("Number of errors : \(numberOfErrors)") From 2d46f0cbeebdf676269124f0f299170f2b529bf7 Mon Sep 17 00:00:00 2001 From: Michal Srutek Date: Sun, 5 Apr 2020 13:48:19 +0200 Subject: [PATCH 5/6] Add redundance check --- .../Languages/en.lproj/Localizable.strings | 4 +- .../Languages/es.lproj/Localizable.strings | 2 + .../Languages/fr.lproj/Localizable.strings | 5 +- .../Sources/ViewController.swift | 1 + Localize.swift | 84 +++++++++++-------- 5 files changed, 58 insertions(+), 38 deletions(-) mode change 100644 => 100755 Localize.swift diff --git a/Example/LocalizeExample/Resources/Languages/en.lproj/Localizable.strings b/Example/LocalizeExample/Resources/Languages/en.lproj/Localizable.strings index 581f9be..243ee66 100644 --- a/Example/LocalizeExample/Resources/Languages/en.lproj/Localizable.strings +++ b/Example/LocalizeExample/Resources/Languages/en.lproj/Localizable.strings @@ -1,4 +1,6 @@ "IgnoredUntranslatedKey" = "OK"; "MissingKey" = "Hey I'm not present in Spanish!"; "NeverUsedKey" = "Hey I'm never used so you can just get rid of me!"; -"UntranslatedKey" = "Hey I'm always in english so you need to translate me!"; \ No newline at end of file +"UntranslatedKey" = "Hey I'm always in english so you need to translate me!"; +"DuplicatedKey" = "Hey, I'm here more than once!"; +"DuplicatedKey" = "Hey, I'm here more than once!"; diff --git a/Example/LocalizeExample/Resources/Languages/es.lproj/Localizable.strings b/Example/LocalizeExample/Resources/Languages/es.lproj/Localizable.strings index be619a8..9cae10b 100644 --- a/Example/LocalizeExample/Resources/Languages/es.lproj/Localizable.strings +++ b/Example/LocalizeExample/Resources/Languages/es.lproj/Localizable.strings @@ -1,3 +1,5 @@ "IgnoredUntranslatedKey" = "OK"; //ignore-same-translation-warning "NeverUsedKey" = "Hello I'm never used so you can just get rid of me!"; "UntranslatedKey" = "Hey I'm always in english so you need to translate me!"; +"DuplicatedKey" = "Hey, I'm more than once in the master translations!"; +"RedundantKey" = "Hi, I'm a redundant key only appearing in Spanish"; diff --git a/Example/LocalizeExample/Resources/Languages/fr.lproj/Localizable.strings b/Example/LocalizeExample/Resources/Languages/fr.lproj/Localizable.strings index 12a8de2..de33d5f 100644 --- a/Example/LocalizeExample/Resources/Languages/fr.lproj/Localizable.strings +++ b/Example/LocalizeExample/Resources/Languages/fr.lproj/Localizable.strings @@ -1,4 +1,5 @@ "IgnoredUntranslatedKey" = "OK"; //ignore-same-translation-warning -"MissingKey" = "Hey! je n'existe pas en espagnol !"; +"MissingKey" = "Hey! je n'existe pas en espagnol!"; "NeverUsedKey" = "Bonjour I'm never used so you can just get rid of me!"; -"UntranslatedKey" = "Hey I'm always in english so you need to translate me!"; \ No newline at end of file +"DuplicatedKey" = "Hey, I'm more than once in the master translations!"; +"UntranslatedKey" = "Hey I'm always in english so you need to translate me!"; diff --git a/Example/LocalizeExample/Sources/ViewController.swift b/Example/LocalizeExample/Sources/ViewController.swift index 57f0f3b..3d1ccc8 100644 --- a/Example/LocalizeExample/Sources/ViewController.swift +++ b/Example/LocalizeExample/Sources/ViewController.swift @@ -16,6 +16,7 @@ final class ViewController: UIViewController { _ = NSLocalizedString("UntranslatedKey", comment: "") _ = NSLocalizedString("IgnoredUntranslatedKey", comment: "") _ = NSLocalizedString("MissingKey", comment: "") + _ = NSLocalizedString("DuplicatedKey", comment: "") _ = NSLocalizedString("MissingKeyFromMain", comment: "This key is not present in master translation file") } } diff --git a/Localize.swift b/Localize.swift old mode 100644 new mode 100755 index c786486..625caf1 --- a/Localize.swift +++ b/Localize.swift @@ -132,44 +132,49 @@ struct LocalizationFiles { sortLinesAlphabetically() } let location = singleLanguage ? "\(path)/Localizable.strings" : "\(path)/\(name).lproj/Localizable.strings" - if let string = try? String(contentsOfFile: location, encoding: .utf8) { - let lines = string.components(separatedBy: .newlines) - keyValue = [:] - let pattern = "\"(.*)\" = \"(.+)\";" - let regex = try? NSRegularExpression(pattern: pattern, options: []) - var ignoredTranslation: [String] = [] - - for (lineNumber, line) in lines.enumerated() { - let range = NSRange(location: 0, length: (line as NSString).length) - - // Ignored pattern - let ignoredPattern = "\"(.*)\" = \"(.+)\"; *\\/\\/ *ignore-same-translation-warning" - let ignoredRegex = try? NSRegularExpression(pattern: ignoredPattern, options: []) - if let ignoredMatch = ignoredRegex?.firstMatch(in: line, - options: [], - range: range) { - let key = (line as NSString).substring(with: ignoredMatch.range(at: 1)) - ignoredTranslation.append(key) - } - if let firstMatch = regex?.firstMatch(in: line, options: [], range: range) { - let key = (line as NSString).substring(with: firstMatch.range(at: 1)) - let value = (line as NSString).substring(with: firstMatch.range(at: 2)) - if let _ = keyValue[key] { - let str = "\(path)/\(name).lproj" - + "/Localizable.strings:\(linesNumbers[key]!): " - + "error: [Redundance] \"\(key)\" " - + "is redundant in \(name.uppercased()) file" - print(str) - numberOfErrors += 1 - } else { - keyValue[key] = value - linesNumbers[key] = lineNumber + 1 - } + guard let string = try? String(contentsOfFile: location, encoding: .utf8) else { + return + } + + let lines = string.components(separatedBy: .newlines) + keyValue = [:] + + let pattern = "\"(.*)\" = \"(.+)\";" + let regex = try? NSRegularExpression(pattern: pattern, options: []) + var ignoredTranslation: [String] = [] + + for (lineNumber, line) in lines.enumerated() { + let range = NSRange(location: 0, length: (line as NSString).length) + + // Ignored pattern + let ignoredPattern = "\"(.*)\" = \"(.+)\"; *\\/\\/ *ignore-same-translation-warning" + let ignoredRegex = try? NSRegularExpression(pattern: ignoredPattern, options: []) + if let ignoredMatch = ignoredRegex?.firstMatch(in: line, + options: [], + range: range) { + let key = (line as NSString).substring(with: ignoredMatch.range(at: 1)) + ignoredTranslation.append(key) + } + + if let firstMatch = regex?.firstMatch(in: line, options: [], range: range) { + let key = (line as NSString).substring(with: firstMatch.range(at: 1)) + let value = (line as NSString).substring(with: firstMatch.range(at: 2)) + + if keyValue[key] != nil { + let str = "\(path)/\(name).lproj" + + "/Localizable.strings:\(linesNumbers[key]!): " + + "error: [Duplication] \"\(key)\" " + + "is duplicated in \(name.uppercased()) file" + print(str) + numberOfErrors += 1 + } else { + keyValue[key] = value + linesNumbers[key] = lineNumber + 1 } } - print(ignoredFromSameTranslation) - ignoredFromSameTranslation[name] = ignoredTranslation } + print(ignoredFromSameTranslation) + ignoredFromSameTranslation[name] = ignoredTranslation } func rebuildFileString(from lines: [String]) -> String { @@ -306,6 +311,15 @@ for file in localizationFiles { numberOfErrors += 1 } } + + let redundantKeys = file.keyValue.keys.filter { !masterLocalizationFile.keyValue.keys.contains($0) } + + for k in redundantKeys { + let str = "\(path)/\(file.name).lproj/Localizable.strings:\(file.linesNumbers[k]!): " + + "error: [Redundant key] \"\(k)\" redundant in \(file.name.uppercased()) file" + + print(str) + } } if checkForUntranslated { From 79c8b5094902ffa013ebcf446ab7f0ed8c251c42 Mon Sep 17 00:00:00 2001 From: Michal Srutek Date: Sun, 5 Apr 2020 14:04:40 +0200 Subject: [PATCH 6/6] Update readme --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7926bc9..187622e 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Because **Localization** files tend to **rot over time** and become a hassle to ## Try it! -Localize is part of [freshOS](http://freshos.org) iOS toolset. Try it in an example App ! Download Starter Project +Localize is part of [freshOS](https://freshos.github.io/) iOS toolset. Try it out in the included example app! ## How By using a **script** running automatically, you have a **safety net** keeping them **sane**, checking for **translation issues** and preventing then to **rot over time.** @@ -29,19 +29,21 @@ Automatically (On build) - [x] Checks for **Unused Keys** - [x] Checks for **Missing Keys** - [x] Checks for **Untranslated** (which you can ignore with a flag) + - [x] Checks for **Redundant Keys** + - [x] Checks for **Duplicate Keys** ## Installation -Add the following `Run Script` in XCode, this will run the script at every build. -Use the path of where you copied Localize.swift script +Add the following `Run Script` in your project's `Build Phases` in XCode, this will run the script at every build. +Use the path of where you copied `Localize.swift` script. ```shell -${SRCROOT}/{REPLACE ME}} # Ex: ${SRCROOT}/Libs/Localize.swift +${SRCROOT}/{REPLACE ME}} # e.g. ${SRCROOT}/Libs/Localize.swift ``` Run and Enjoy \o/ ## Configuration -Configure the top section of the `Localize.swift` according to your project +Configure the top section of the `Localize.swift` according to your project. ## More @@ -90,7 +92,7 @@ let ignoredFromUnusedKeys = [ Sacha Durand Saint Omer, sachadso@gmail.com ## Contributors -[JuliusBahr](https://github.com/JuliusBahr), [ezisazis](https://github.com/ezisazis/) +[JuliusBahr](https://github.com/JuliusBahr), [ezisazis](https://github.com/ezisazis/), [michalsrutek](https://github.com/michalsrutek/) ## Contributing @@ -102,7 +104,7 @@ Localize is available under the MIT license. See [LICENSE](https://github.com/s4 # Backers -Like the project? Offer coffee or support us with a monthly donation and help us continue our activities :) +Like the project? Offer coffee or support us with a monthly donation and help us continue our activities :) @@ -136,7 +138,7 @@ Like the project? Offer coffee or support us with a monthly donation and help us ### Sponsors -Become a sponsor and get your logo on our README on Github with a link to your site :) +Become a sponsor and get your logo on our README on Github with a link to your site :)