xcstrings #10
@ -17,7 +17,10 @@ class StringsFileGenerator {
|
||||
defaultLang: String,
|
||||
tags: [String],
|
||||
outputPath: String,
|
||||
inputFilenameWithoutExt: String) {
|
||||
inputFilenameWithoutExt: String,
|
||||
isXcString: Bool = false) {
|
||||
|
||||
if !isXcString {
|
||||
var stringsFilesContent = [String: String]()
|
||||
for lang in langs {
|
||||
stringsFilesContent[lang] = Self.generateStringsFileContent(lang: lang,
|
||||
@ -40,6 +43,24 @@ class StringsFileGenerator {
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let fileContent: String = Self.generateXcStringsFileContent(
|
||||
langs: langs,
|
||||
defaultLang: defaultLang,
|
||||
tags: tags,
|
||||
sections: sections
|
||||
)
|
||||
|
||||
let stringsFilePath = "\(outputPath)/Localizable.xcstrings"
|
||||
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
|
||||
do {
|
||||
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
|
||||
} catch let error {
|
||||
let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath)
|
||||
print(error.description)
|
||||
Stringium.exit(withError: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func generateStringsFileContent(lang: String,
|
||||
@ -92,6 +113,95 @@ class StringsFileGenerator {
|
||||
return stringsFileContent
|
||||
}
|
||||
|
||||
// MARK: - XcStrings Generation
|
||||
|
||||
static func generateXcStringsFileContent(langs: [String],
|
||||
defaultLang: String,
|
||||
tags inputTags: [String],
|
||||
sections: [Section]) -> String {
|
||||
let rootObject = generateRootObject(langs: langs, defaultLang: defaultLang, tags: inputTags, sections: sections)
|
||||
let file = generateXcStringsFileContentFromRootObject(rootObject: rootObject)
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
static func generateXcStringsFileContentFromRootObject(rootObject: Root) -> String {
|
||||
do {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = [.prettyPrinted]
|
||||
|
||||
let json = try encoder.encode(rootObject)
|
||||
|
||||
if let jsonString = String(data: json, encoding: .utf8) {
|
||||
return jsonString
|
||||
}
|
||||
|
||||
} catch {
|
||||
debugPrint("Failed to encode: \(error)")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
static func generateRootObject(langs: [String],
|
||||
defaultLang: String,
|
||||
tags inputTags: [String],
|
||||
sections: [Section]) -> Root {
|
||||
|
||||
var xcStringDefinitionTab: [XCStringDefinition] = []
|
||||
|
||||
sections.forEach { section in
|
||||
// Check that at least one string will be generated
|
||||
guard section.hasOneOrMoreMatchingTags(tags: inputTags) else {
|
||||
return // Go to next section
|
||||
}
|
||||
|
||||
section.definitions.forEach { definition in
|
||||
var skipDefinition = false
|
||||
|
||||
var localizationTab: [XCStringLocalization] = []
|
||||
|
||||
if definition.hasOneOrMoreMatchingTags(inputTags: inputTags) == false {
|
||||
skipDefinition = true
|
||||
}
|
||||
|
||||
if !skipDefinition {
|
||||
|
||||
for (lang, value) in definition.translations {
|
||||
let localization = XCStringLocalization(
|
||||
lang: lang,
|
||||
content: XCStringLocalizationLangContent(
|
||||
stringUnit: DefaultStringUnit(state: "translated", value: value)
|
||||
)
|
||||
)
|
||||
|
||||
localizationTab.append(localization)
|
||||
}
|
||||
|
||||
let xcStringDefinition = XCStringDefinition(
|
||||
title: definition.name,
|
||||
content: XCStringDefinitionContent(
|
||||
extractionState: "manual",
|
||||
localizations: XCStringLocalizationContainer(
|
||||
localizations: localizationTab
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
xcStringDefinitionTab.append(xcStringDefinition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let xcStringContainer = XCStringDefinitionContainer(strings: xcStringDefinitionTab)
|
||||
|
||||
return Root(
|
||||
sourceLanguage: defaultLang,
|
||||
strings: xcStringContainer,
|
||||
version: "1.0"
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Extension file
|
||||
|
||||
static func writeExtensionFiles(sections: [Section],
|
||||
|
93
Sources/ResgenSwift/Strings/Model/XcString.swift
Normal file
93
Sources/ResgenSwift/Strings/Model/XcString.swift
Normal file
@ -0,0 +1,93 @@
|
||||
//
|
||||
// XcString.swift
|
||||
//
|
||||
//
|
||||
// Created by Quentin Bandera on 12/04/2024.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DynamicKey: CodingKey {
|
||||
var intValue: Int?
|
||||
init?(intValue: Int) {
|
||||
self.intValue = intValue
|
||||
self.stringValue = "\(intValue)"
|
||||
}
|
||||
|
||||
var stringValue: String
|
||||
init?(stringValue: String) {
|
||||
self.stringValue = stringValue
|
||||
}
|
||||
}
|
||||
|
||||
struct Root: Codable, Equatable {
|
||||
let sourceLanguage: String
|
||||
let strings: XCStringDefinitionContainer
|
||||
let version: String
|
||||
}
|
||||
|
||||
struct XCStringDefinitionContainer: Codable, Equatable {
|
||||
let strings: [XCStringDefinition]
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: DynamicKey.self)
|
||||
|
||||
for str in strings {
|
||||
if let codingKey = DynamicKey(stringValue: str.title) {
|
||||
try container.encode(str.content, forKey: codingKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct XCStringDefinition: Codable, Equatable {
|
||||
let title: String // json key -> custom encoding methods
|
||||
let content: XCStringDefinitionContent
|
||||
}
|
||||
|
||||
struct XCStringDefinitionContent: Codable, Equatable {
|
||||
let extractionState: String
|
||||
var localizations: XCStringLocalizationContainer
|
||||
|
||||
}
|
||||
|
||||
struct XCStringLocalizationContainer: Codable, Equatable {
|
||||
let localizations: [XCStringLocalization]
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: DynamicKey.self)
|
||||
|
||||
for loca in localizations {
|
||||
if let codingKey = DynamicKey(stringValue: loca.lang) {
|
||||
try container.encode(loca.content, forKey: codingKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct XCStringLocalization: Codable, Equatable {
|
||||
let lang: String // json key -> custom encoding method
|
||||
let content: XCStringLocalizationLangContent
|
||||
}
|
||||
|
||||
struct XCStringLocalizationLangContent: Codable, Equatable {
|
||||
let stringUnit: DefaultStringUnit
|
||||
}
|
||||
|
||||
//enum VarationOrStringUnit: Encodable {
|
||||
// case variations([Varation])
|
||||
// case stringUnit: (DefaultStringUnit)
|
||||
//
|
||||
// func encode(to encoder: any Encoder) throws {
|
||||
// if let varations {
|
||||
//
|
||||
// } else if let {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
struct DefaultStringUnit: Codable, Equatable {
|
||||
let state: String
|
||||
let value: String
|
||||
}
|
@ -48,7 +48,8 @@ struct Stringium: ParsableCommand {
|
||||
defaultLang: options.defaultLang,
|
||||
tags: options.tags,
|
||||
outputPath: options.stringsFileOutputPath,
|
||||
inputFilenameWithoutExt: options.inputFilenameWithoutExt)
|
||||
inputFilenameWithoutExt: options.inputFilenameWithoutExt,
|
||||
isXcString: options.isXcstring)
|
||||
|
||||
// Generate extension
|
||||
StringsFileGenerator.writeExtensionFiles(sections: sections,
|
||||
|
@ -12,6 +12,9 @@ struct StringiumOptions: ParsableArguments {
|
||||
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
|
||||
var forceGeneration = false
|
||||
|
||||
@Flag(name: [.customShort("x"), .customLong("xcstrings")], help: "Generate xcstrings catalog")
|
||||
var isXcstring = false
|
||||
|
||||
@Argument(help: "Input files where strings ared defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
|
||||
var inputFile: String
|
||||
|
||||
|
@ -187,6 +187,268 @@ final class StringsFileGeneratorTests: XCTestCase {
|
||||
XCTAssertEqual(stringsFileContentEn.adaptForXCTest(), expectEn.adaptForXCTest())
|
||||
}
|
||||
|
||||
// MARK: - XcString File Content
|
||||
|
||||
func testGenerateXcStringsRootObject() {
|
||||
// Given
|
||||
let sectionOne = Section(name: "section_one")
|
||||
sectionOne.definitions = [
|
||||
getDefinition(name: "s1_def_one",
|
||||
translations: ["fr": "Section Un - Definition Un",
|
||||
"en": "Section One - Definition One"],
|
||||
tags: ["ios","iosonly"]),
|
||||
getDefinition(name: "s1_def_two",
|
||||
translations: ["fr": "Section Un - Definition Deux",
|
||||
"en": "Section One - Definition Two"],
|
||||
tags: ["ios","iosonly"])
|
||||
]
|
||||
|
||||
let sectionTwo = Section(name: "section_two")
|
||||
sectionTwo.definitions = [
|
||||
getDefinition(name: "s2_def_one",
|
||||
translations: ["fr": "Section Deux - Definition Un",
|
||||
"en": "Section Two - Definition One"],
|
||||
tags: ["ios","iosonly"]),
|
||||
getDefinition(name: "s2_def_two",
|
||||
translations: ["fr": "Section Deux - Definition Deux"],
|
||||
tags: ["notranslation"])
|
||||
]
|
||||
|
||||
// When
|
||||
let rootObject = StringsFileGenerator.generateRootObject(
|
||||
langs: ["fr", "en"],
|
||||
defaultLang: "en",
|
||||
tags: ["ios", "iosonly", "notranslation"],
|
||||
sections: [sectionOne, sectionTwo]
|
||||
)
|
||||
|
||||
// [[section_one]]
|
||||
// [s1_def_one]
|
||||
// fr = Section Un - Definition Un
|
||||
// en = Section One - Definition One
|
||||
// tags = ios,iosonly
|
||||
// comments =
|
||||
// [s1_def_two]
|
||||
// fr = Section Un - Definition Deux
|
||||
// en = Section One - Definition Two
|
||||
// tags = ios,iosonly
|
||||
// comments =
|
||||
//
|
||||
// [[section_two]
|
||||
// [s2_def_one]
|
||||
// fr = Section Deux - Definition Un
|
||||
// en = Section Two - Definition One
|
||||
// tags = ios,iosonly
|
||||
// comments =
|
||||
// [s2_def_two]
|
||||
// fr = Section Deux - Definition deux
|
||||
// en = Section Two - Definition Two
|
||||
// tags = ios,iosonly
|
||||
// comments =
|
||||
|
||||
// Expect
|
||||
let expect =
|
||||
Root(
|
||||
sourceLanguage: "en",
|
||||
strings: XCStringDefinitionContainer(
|
||||
strings: [
|
||||
XCStringDefinition(
|
||||
title: "s1_def_one",
|
||||
content: XCStringDefinitionContent(
|
||||
extractionState: "manual",
|
||||
localizations: XCStringLocalizationContainer(
|
||||
localizations: [
|
||||
XCStringLocalization(
|
||||
lang: "en",
|
||||
content: XCStringLocalizationLangContent(
|
||||
stringUnit: DefaultStringUnit(
|
||||
state: "translated",
|
||||
value: "Section One - Definition One"
|
||||
)
|
||||
)
|
||||
),
|
||||
XCStringLocalization(
|
||||
lang: "fr",
|
||||
content: XCStringLocalizationLangContent(
|
||||
stringUnit: DefaultStringUnit(
|
||||
state: "translated",
|
||||
value: "Section Un - Definition Un"
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
XCStringDefinition(
|
||||
title: "s1_def_two",
|
||||
content: XCStringDefinitionContent(
|
||||
extractionState: "manual",
|
||||
localizations: XCStringLocalizationContainer(
|
||||
localizations: [
|
||||
XCStringLocalization(
|
||||
lang: "en",
|
||||
content: XCStringLocalizationLangContent(
|
||||
stringUnit: DefaultStringUnit(
|
||||
state: "translated",
|
||||
value: "Section One - Definition Two"
|
||||
)
|
||||
)
|
||||
),
|
||||
XCStringLocalization(
|
||||
lang: "fr",
|
||||
content: XCStringLocalizationLangContent(
|
||||
stringUnit: DefaultStringUnit(
|
||||
state: "translated",
|
||||
value: "Section Un - Definition Deux"
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
XCStringDefinition(
|
||||
title: "s2_def_one",
|
||||
content: XCStringDefinitionContent(
|
||||
extractionState: "manual",
|
||||
localizations: XCStringLocalizationContainer(
|
||||
localizations: [
|
||||
XCStringLocalization(
|
||||
lang: "en",
|
||||
content: XCStringLocalizationLangContent(
|
||||
stringUnit: DefaultStringUnit(
|
||||
state: "translated",
|
||||
value: "Section Two - Definition One"
|
||||
)
|
||||
)
|
||||
),
|
||||
XCStringLocalization(
|
||||
lang: "fr",
|
||||
content: XCStringLocalizationLangContent(
|
||||
stringUnit: DefaultStringUnit(
|
||||
state: "translated",
|
||||
value: "Section Two - Definition Un"
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
),
|
||||
XCStringDefinition(
|
||||
title: "s2_def_two",
|
||||
content: XCStringDefinitionContent(
|
||||
extractionState: "manual",
|
||||
localizations: XCStringLocalizationContainer(
|
||||
localizations: [
|
||||
XCStringLocalization(
|
||||
lang: "en",
|
||||
content: XCStringLocalizationLangContent(
|
||||
stringUnit: DefaultStringUnit(
|
||||
state: "translated",
|
||||
value: "Section Two - Definition Two"
|
||||
)
|
||||
)
|
||||
),
|
||||
XCStringLocalization(
|
||||
lang: "fr",
|
||||
content: XCStringLocalizationLangContent(
|
||||
stringUnit: DefaultStringUnit(
|
||||
state: "translated",
|
||||
value: "Section Deux - Definition Deux"
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
version: "1.0"
|
||||
)
|
||||
|
||||
// """
|
||||
// {
|
||||
// "sourceLanguage" : "en",
|
||||
// "strings" : {
|
||||
// "s1_def_one" : {
|
||||
// "extractionState" : "manual",
|
||||
// "localizations" : {
|
||||
// "en" : {
|
||||
// "stringUnit" : {
|
||||
// "state" : "translated",
|
||||
// "value" : "Section One - Definition One"
|
||||
// }
|
||||
// },
|
||||
// "fr" : {
|
||||
// "stringUnit" : {
|
||||
// "state" : "translated",
|
||||
// "value" : "Section Un - Definition Un"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "s1_def_two" : {
|
||||
// "extractionState" : "manual",
|
||||
// "localizations" : {
|
||||
// "en" : {
|
||||
// "stringUnit" : {
|
||||
// "state" : "translated",
|
||||
// "value" : "Section One - Definition Two"
|
||||
// }
|
||||
// },
|
||||
// "fr" : {
|
||||
// "stringUnit" : {
|
||||
// "state" : "translated",
|
||||
// "value" : "Section Un - Definition Deux"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "s2_def_one" : {
|
||||
// "extractionState" : "manual",
|
||||
// "localizations" : {
|
||||
// "en" : {
|
||||
// "stringUnit" : {
|
||||
// "state" : "translated",
|
||||
// "value" : "Section Two - Definition One"
|
||||
// }
|
||||
// },
|
||||
// "fr" : {
|
||||
// "stringUnit" : {
|
||||
// "state" : "translated",
|
||||
// "value" : "Section Deux - Definition Une"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "s2_def_two" : {
|
||||
// "extractionState" : "manual",
|
||||
// "localizations" : {
|
||||
// "en" : {
|
||||
// "stringUnit" : {
|
||||
// "state" : "translated",
|
||||
// "value" : "Section Two - Definition Two"
|
||||
// }
|
||||
// },
|
||||
// "fr" : {
|
||||
// "stringUnit" : {
|
||||
// "state" : "translated",
|
||||
// "value" : "Section Deux - Definition Deux"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "version" : "1.0"
|
||||
// }
|
||||
// """
|
||||
|
||||
XCTAssertEqual(rootObject, expect)
|
||||
}
|
||||
|
||||
// MARK: - Extension Content
|
||||
func testGeneratedExtensionContent() {
|
||||
// Given
|
||||
|
Loading…
x
Reference in New Issue
Block a user