diff --git a/Sources/Shark/CLI/XcodeProjectHelper.swift b/Sources/Shark/CLI/XcodeProjectHelper.swift index 6c1e649..039d335 100644 --- a/Sources/Shark/CLI/XcodeProjectHelper.swift +++ b/Sources/Shark/CLI/XcodeProjectHelper.swift @@ -58,12 +58,15 @@ struct XcodeProjectHelper { .compactMap { $0.file } .flatMap(paths(for:)) .reduce(into: ResourcePaths(), { result, path in + //print("Dealing with \(path)...") if !self.options.shouldExclude(path: path) { switch path.pathExtension { case "xcassets": result.assetsPaths.append(path) case "strings" where path.pathComponents.contains("\(locale).lproj"): result.localizationPaths.append(path) + case "xcstrings": + result.localizationPaths.append(path) case "ttf", "otf", "ttc": result.fontPaths.append(path) case "storyboard": diff --git a/Sources/Shark/Codegen/LocalizationEnumBuilder.swift b/Sources/Shark/Codegen/LocalizationEnumBuilder.swift index fd75d0e..4200158 100644 --- a/Sources/Shark/Codegen/LocalizationEnumBuilder.swift +++ b/Sources/Shark/Codegen/LocalizationEnumBuilder.swift @@ -117,15 +117,40 @@ enum LocalizationBuilderError: LocalizedError { } enum LocalizationEnumBuilder { + static func localizationsEnumString(forFilesAtPaths paths: [String], topLevelName: String, options: Options) throws -> String? { - let termsDictionaries = try paths.compactMap({ path -> [String: String]? in - guard FileManager.default.fileExists(atPath: path) else { return nil } + + let paths = paths.filter { FileManager.default.fileExists(atPath: $0) } + // We now support both `.strings` and `.xcstrings` files. + let stringsPaths = paths.filter { $0.hasSuffix(".strings") } + let xcstringsPaths = paths.filter { $0.hasSuffix(".xcstrings") } + + let stringsDictionaries = try stringsPaths.compactMap { path -> [String: String]? in guard let termsDictionary = NSDictionary(contentsOfFile: path) as? [String: String] else { throw LocalizationBuilderError.invalidLocalizableStringsFile(path: path) } return termsDictionary - }) + } + + let xcstringsDictionaries = try xcstringsPaths.compactMap { path -> [String: String]? in + let url = URL(fileURLWithPath: path) + let fileContents = try Data(contentsOf: url) + let stringCatalog = try JSONDecoder().decode(StringCatalog.self, from: fileContents) + + var terms: [String: String] = [:] + for (string, entry) in stringCatalog.strings { + guard let localizations = entry.localizations, + let sourceLocalization = localizations[stringCatalog.sourceLanguage], + let value = sourceLocalization.stringUnit?.value else { + terms[string] = string + continue + } + terms[string] = value + } + return terms + } + let termsDictionaries = stringsDictionaries + xcstringsDictionaries guard termsDictionaries.isEmpty == false else { return nil } let rootNode = Node(value: LocalizationValue.namespace(name: topLevelName)) diff --git a/Sources/Shark/Types/StringCatalogModels.swift b/Sources/Shark/Types/StringCatalogModels.swift new file mode 100644 index 0000000..fbf92e5 --- /dev/null +++ b/Sources/Shark/Types/StringCatalogModels.swift @@ -0,0 +1,44 @@ +struct StringCatalog: Decodable { + let sourceLanguage: String + let strings: [String: StringCatalogEntry] +} + +struct StringCatalogEntry: Decodable { + let comment: String? + let localizations: [String: Localization]? +} + +struct Localization: Decodable { + let stringUnit: StringUnit? + let variations: Variations? +} + +struct Variations: Decodable { + let plural: PluralVariation? +} + +struct PluralVariation: Decodable { + let zero: Variation? + let one: Variation? + let two: Variation? + let few: Variation? + let many: Variation? + let other: Variation + + var all: [Variation] { [zero, one, two, few, many, other].compactMap { $0 } } +} + +struct Variation: Decodable { + + enum TranslationState: Decodable { + case translated + case needs_review + } + + let stringUnit: StringUnit + let state: TranslationState +} + +struct StringUnit: Decodable { + let value: String +}