forked from appdecostudio/SwiftPolyglot
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Minor refactor of core functionality (appdecostudio#14)
* Move arguments check to a computed property * Pass process info property as initializer parameter * Define functions as type methods * Format * Move language codes parsing into a failable initializer
- Loading branch information
1 parent
ca08f0e
commit 573037a
Showing
3 changed files
with
169 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,130 +1,192 @@ | ||
import Foundation | ||
|
||
public struct SwiftPolyglot { | ||
private static let errorOnMissingArgument = "--errorOnMissing" | ||
|
||
private let arguments: [String] | ||
private let filePaths: [String] | ||
private let languageCodes: [String] | ||
private let runningOnAGitHubAction: Bool | ||
|
||
public init(arguments: [String], filePaths: [String]) { | ||
self.arguments = arguments | ||
self.filePaths = filePaths | ||
private var logErrorOnMissing: Bool { | ||
arguments.contains(Self.errorOnMissingArgument) | ||
} | ||
|
||
public func run() throws { | ||
guard !arguments.isEmpty else { | ||
public init(arguments: [String], filePaths: [String], runningOnAGitHubAction: Bool) throws { | ||
let languageCodes = arguments[0].split(separator: ",").map(String.init) | ||
|
||
guard | ||
!languageCodes.contains(Self.errorOnMissingArgument), | ||
!languageCodes.isEmpty | ||
else { | ||
throw SwiftPolyglotError.noLanguageCodes | ||
} | ||
|
||
let isRunningFromGitHubActions = ProcessInfo.processInfo.environment["GITHUB_ACTIONS"] == "true" | ||
let languages = arguments[0].split(separator: ",").map(String.init) | ||
let errorOnMissing = arguments.contains("--errorOnMissing") | ||
self.arguments = arguments | ||
self.filePaths = filePaths | ||
self.languageCodes = languageCodes | ||
self.runningOnAGitHubAction = runningOnAGitHubAction | ||
} | ||
|
||
public func run() throws { | ||
var missingTranslations = false | ||
|
||
func checkTranslations(in fileURL: URL, for languages: [String]) throws { | ||
guard let data = try? Data(contentsOf: fileURL), | ||
let jsonObject = try? JSONSerialization.jsonObject(with: data), | ||
let jsonDict = jsonObject as? [String: Any], | ||
let strings = jsonDict["strings"] as? [String: [String: Any]] | ||
try searchDirectory(for: languageCodes, missingTranslations: &missingTranslations) | ||
|
||
if missingTranslations, logErrorOnMissing { | ||
throw SwiftPolyglotError.missingTranslations | ||
} else if missingTranslations { | ||
print("Completed with missing translations.") | ||
} else { | ||
print("All translations are present.") | ||
} | ||
} | ||
|
||
private func checkDeviceVariations( | ||
devices: [String: [String: Any]], | ||
originalString: String, | ||
lang: String, | ||
fileURL: URL, | ||
missingTranslations: inout Bool | ||
) { | ||
for (device, value) in devices { | ||
guard let stringUnit = value["stringUnit"] as? [String: Any], | ||
let state = stringUnit["state"] as? String, state == "translated" | ||
else { | ||
if isRunningFromGitHubActions { | ||
print("::warning file=\(fileURL.path)::Could not process file at path: \(fileURL.path)") | ||
} else { | ||
print("Could not process file at path: \(fileURL.path)") | ||
} | ||
return | ||
logWarning( | ||
file: fileURL.path, | ||
message: "'\(originalString)' device '\(device)' is missing or not translated in \(lang) in file: \(fileURL.path)" | ||
) | ||
missingTranslations = true | ||
continue | ||
} | ||
} | ||
} | ||
|
||
for (originalString, translations) in strings { | ||
guard let localizations = translations["localizations"] as? [String: [String: Any]] else { | ||
logWarning(file: fileURL.path, message: "'\(originalString)' is not translated in any language in file: \(fileURL.path)") | ||
missingTranslations = true | ||
continue | ||
} | ||
|
||
for lang in languages { | ||
guard let languageDict = localizations[lang] else { | ||
logWarning(file: fileURL.path, message: "'\(originalString)' is missing translations for language: \(lang) in file: \(fileURL.path)") | ||
missingTranslations = true | ||
continue | ||
} | ||
|
||
if let variations = languageDict["variations"] as? [String: [String: [String: Any]]] { | ||
try checkVariations(variations: variations, originalString: originalString, lang: lang, fileURL: fileURL) | ||
} else if let stringUnit = languageDict["stringUnit"] as? [String: Any], | ||
let state = stringUnit["state"] as? String, state != "translated" | ||
{ | ||
logWarning(file: fileURL.path, message: "'\(originalString)' is missing or not translated in \(lang) in file: \(fileURL.path)") | ||
missingTranslations = true | ||
} | ||
} | ||
private func checkPluralizations( | ||
pluralizations: [String: [String: Any]], | ||
originalString: String, | ||
lang: String, | ||
fileURL: URL, | ||
missingTranslations: inout Bool | ||
) { | ||
for (pluralForm, value) in pluralizations { | ||
guard let stringUnit = value["stringUnit"] as? [String: Any], | ||
let state = stringUnit["state"] as? String, state == "translated" | ||
else { | ||
logWarning( | ||
file: fileURL.path, | ||
message: "'\(originalString)' plural form '\(pluralForm)' is missing or not translated in \(lang) in file: \(fileURL.path)" | ||
) | ||
missingTranslations = true | ||
continue | ||
} | ||
} | ||
} | ||
|
||
func checkVariations(variations: [String: [String: [String: Any]]], originalString: String, lang: String, fileURL: URL) throws { | ||
for (variationKey, variationDict) in variations { | ||
if variationKey == "plural" { | ||
checkPluralizations(pluralizations: variationDict, originalString: originalString, lang: lang, fileURL: fileURL) | ||
} else if variationKey == "device" { | ||
checkDeviceVariations(devices: variationDict, originalString: originalString, lang: lang, fileURL: fileURL) | ||
} else { | ||
throw SwiftPolyglotError.unsupportedVariation(variation: variationKey) | ||
} | ||
private func checkTranslations(in fileURL: URL, for languages: [String], missingTranslations: inout Bool) throws { | ||
guard let data = try? Data(contentsOf: fileURL), | ||
let jsonObject = try? JSONSerialization.jsonObject(with: data), | ||
let jsonDict = jsonObject as? [String: Any], | ||
let strings = jsonDict["strings"] as? [String: [String: Any]] | ||
else { | ||
if runningOnAGitHubAction { | ||
print("::warning file=\(fileURL.path)::Could not process file at path: \(fileURL.path)") | ||
} else { | ||
print("Could not process file at path: \(fileURL.path)") | ||
} | ||
return | ||
} | ||
|
||
func checkPluralizations(pluralizations: [String: [String: Any]], originalString: String, lang: String, fileURL: URL) { | ||
for (pluralForm, value) in pluralizations { | ||
guard let stringUnit = value["stringUnit"] as? [String: Any], | ||
let state = stringUnit["state"] as? String, state == "translated" | ||
else { | ||
logWarning(file: fileURL.path, message: "'\(originalString)' plural form '\(pluralForm)' is missing or not translated in \(lang) in file: \(fileURL.path)") | ||
for (originalString, translations) in strings { | ||
guard let localizations = translations["localizations"] as? [String: [String: Any]] else { | ||
logWarning( | ||
file: fileURL.path, | ||
message: "'\(originalString)' is not translated in any language in file: \(fileURL.path)" | ||
) | ||
missingTranslations = true | ||
continue | ||
} | ||
|
||
for lang in languages { | ||
guard let languageDict = localizations[lang] else { | ||
logWarning( | ||
file: fileURL.path, | ||
message: "'\(originalString)' is missing translations for language: \(lang) in file: \(fileURL.path)" | ||
) | ||
missingTranslations = true | ||
continue | ||
} | ||
} | ||
} | ||
|
||
func checkDeviceVariations(devices: [String: [String: Any]], originalString: String, lang: String, fileURL: URL) { | ||
for (device, value) in devices { | ||
guard let stringUnit = value["stringUnit"] as? [String: Any], | ||
let state = stringUnit["state"] as? String, state == "translated" | ||
else { | ||
logWarning(file: fileURL.path, message: "'\(originalString)' device '\(device)' is missing or not translated in \(lang) in file: \(fileURL.path)") | ||
if let variations = languageDict["variations"] as? [String: [String: [String: Any]]] { | ||
try checkVariations( | ||
variations: variations, | ||
originalString: originalString, | ||
lang: lang, | ||
fileURL: fileURL, | ||
missingTranslations: &missingTranslations | ||
) | ||
} else if let stringUnit = languageDict["stringUnit"] as? [String: Any], | ||
let state = stringUnit["state"] as? String, state != "translated" | ||
{ | ||
logWarning( | ||
file: fileURL.path, | ||
message: "'\(originalString)' is missing or not translated in \(lang) in file: \(fileURL.path)" | ||
) | ||
missingTranslations = true | ||
continue | ||
} | ||
} | ||
} | ||
} | ||
|
||
func searchDirectory() throws { | ||
for filePath in filePaths { | ||
if filePath.hasSuffix(".xcstrings") { | ||
let fileURL = URL(fileURLWithPath: filePath) | ||
try checkTranslations(in: fileURL, for: languages) | ||
} | ||
private func checkVariations( | ||
variations: [String: [String: [String: Any]]], | ||
originalString: String, | ||
lang: String, | ||
fileURL: URL, | ||
missingTranslations: inout Bool | ||
) throws { | ||
for (variationKey, variationDict) in variations { | ||
if variationKey == "plural" { | ||
checkPluralizations( | ||
pluralizations: variationDict, | ||
originalString: originalString, | ||
lang: lang, | ||
fileURL: fileURL, | ||
missingTranslations: &missingTranslations | ||
) | ||
} else if variationKey == "device" { | ||
checkDeviceVariations( | ||
devices: variationDict, | ||
originalString: originalString, | ||
lang: lang, | ||
fileURL: fileURL, | ||
missingTranslations: &missingTranslations | ||
) | ||
} else { | ||
throw SwiftPolyglotError.unsupportedVariation(variation: variationKey) | ||
} | ||
} | ||
|
||
func logWarning(file: String, message: String) { | ||
if isRunningFromGitHubActions { | ||
if errorOnMissing { | ||
print("::error file=\(file)::\(message)") | ||
} else { | ||
print("::warning file=\(file)::\(message)") | ||
} | ||
} | ||
|
||
private func logWarning(file: String, message: String) { | ||
if runningOnAGitHubAction { | ||
if logErrorOnMissing { | ||
print("::error file=\(file)::\(message)") | ||
} else { | ||
print(message) | ||
print("::warning file=\(file)::\(message)") | ||
} | ||
} else { | ||
print(message) | ||
} | ||
} | ||
|
||
try searchDirectory() | ||
|
||
if missingTranslations, errorOnMissing { | ||
throw SwiftPolyglotError.missingTranslations | ||
} else if missingTranslations { | ||
print("Completed with missing translations.") | ||
} else { | ||
print("All translations are present.") | ||
private func searchDirectory(for languages: [String], missingTranslations: inout Bool) throws { | ||
for filePath in filePaths { | ||
if filePath.hasSuffix(".xcstrings") { | ||
let fileURL = URL(fileURLWithPath: filePath) | ||
try checkTranslations(in: fileURL, for: languages, missingTranslations: &missingTranslations) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters