Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor refactor of core functionality #14

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions Sources/SwiftPolyglot/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ else {
exit(EXIT_FAILURE)
}

let swiftPolyglot: SwiftPolyglot = .init(
arguments: Array(CommandLine.arguments.dropFirst()),
filePaths: filePaths
)

do {
let swiftPolyglot: SwiftPolyglot = try .init(
arguments: Array(CommandLine.arguments.dropFirst()),
filePaths: filePaths,
runningOnAGitHubAction: ProcessInfo.processInfo.environment["GITHUB_ACTIONS"] == "true"
)

try swiftPolyglot.run()
} catch {
print(error.localizedDescription)
Expand Down
240 changes: 151 additions & 89 deletions Sources/SwiftPolyglotCore/SwiftPolyglot.swift
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)
}
}
}
}
20 changes: 12 additions & 8 deletions Tests/SwiftPolyglotCoreTests/SwiftPolyglotCoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ final class SwiftPolyglotCoreTests: XCTestCase {
return
}

let swiftPolyglot: SwiftPolyglot = .init(
let swiftPolyglot: SwiftPolyglot = try .init(
arguments: ["ca,de,en,es"],
filePaths: [stringCatalogFilePath]
filePaths: [stringCatalogFilePath],
runningOnAGitHubAction: false
)

XCTAssertNoThrow(try swiftPolyglot.run())
Expand All @@ -34,9 +35,10 @@ final class SwiftPolyglotCoreTests: XCTestCase {
return
}

let swiftPolyglot: SwiftPolyglot = .init(
let swiftPolyglot: SwiftPolyglot = try .init(
arguments: ["ca,de,en,es"],
filePaths: [stringCatalogFilePath]
filePaths: [stringCatalogFilePath],
runningOnAGitHubAction: false
)

XCTAssertNoThrow(try swiftPolyglot.run())
Expand All @@ -54,9 +56,10 @@ final class SwiftPolyglotCoreTests: XCTestCase {
return
}

let swiftPolyglot: SwiftPolyglot = .init(
let swiftPolyglot: SwiftPolyglot = try .init(
arguments: ["ca,de,en,es", "--errorOnMissing"],
filePaths: [stringCatalogFilePath]
filePaths: [stringCatalogFilePath],
runningOnAGitHubAction: false
)

XCTAssertThrowsError(try swiftPolyglot.run())
Expand All @@ -74,9 +77,10 @@ final class SwiftPolyglotCoreTests: XCTestCase {
return
}

let swiftPolyglot: SwiftPolyglot = .init(
let swiftPolyglot: SwiftPolyglot = try .init(
arguments: ["de,en", "--errorOnMissing"],
filePaths: [stringCatalogFilePath]
filePaths: [stringCatalogFilePath],
runningOnAGitHubAction: false
)

XCTAssertThrowsError(try swiftPolyglot.run())
Expand Down
Loading