Optional generation of Strings extension (stringium command)
This commit is contained in:
150
SampleFiles/Strings/Generated/sampleStrings.xcstrings
Normal file
150
SampleFiles/Strings/Generated/sampleStrings.xcstrings
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
{
|
||||||
|
"sourceLanguage" : "en",
|
||||||
|
"strings" : {
|
||||||
|
"generic_back" : {
|
||||||
|
"comment" : "",
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Back"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-us" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Back"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Retour"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"generic_loading_data" : {
|
||||||
|
"comment" : "",
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Loading data..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-us" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Loading data..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Chargement des données..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"generic_welcome_firstname_format" : {
|
||||||
|
"comment" : "",
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Welcome \\\"%@\\\" !"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-us" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Welcome \\\"%@\\\" !"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Bienvenue \\\"%@\\\" !"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"param_lang" : {
|
||||||
|
"comment" : "",
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "en"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-us" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "en-us"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "fr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"placeholders_test_one" : {
|
||||||
|
"comment" : "",
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "You %%: %2$@ %1$@ Age: %3$d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-us" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "You %%: %2$@ %1$@ Age: %3$d"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Vous %%: %1$@ %2$@ Age: %3$d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test_equal_symbol" : {
|
||||||
|
"comment" : "",
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "1€ = 1 point !"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-us" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "1€ = 1 point !"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "1€ = 1 point !"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version" : "1.0"
|
||||||
|
}
|
@@ -280,7 +280,7 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
let outputPath: String
|
let outputPath: String
|
||||||
let langs: String
|
let langs: String
|
||||||
let defaultLang: String
|
let defaultLang: String
|
||||||
let extensionOutputPath: String
|
let extensionOutputPath: String?
|
||||||
let extensionName: String?
|
let extensionName: String?
|
||||||
let extensionSuffix: String?
|
let extensionSuffix: String?
|
||||||
private let staticMembers: Bool?
|
private let staticMembers: Bool?
|
||||||
@@ -305,7 +305,7 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
outputPath: String,
|
outputPath: String,
|
||||||
langs: String,
|
langs: String,
|
||||||
defaultLang: String,
|
defaultLang: String,
|
||||||
extensionOutputPath: String,
|
extensionOutputPath: String?,
|
||||||
extensionName: String?,
|
extensionName: String?,
|
||||||
extensionSuffix: String?,
|
extensionSuffix: String?,
|
||||||
staticMembers: Bool?,
|
staticMembers: Bool?,
|
||||||
@@ -329,7 +329,7 @@ struct StringsConfiguration: Codable, CustomDebugStringConvertible {
|
|||||||
- Output path: \(outputPath)
|
- Output path: \(outputPath)
|
||||||
- Langs: \(langs)
|
- Langs: \(langs)
|
||||||
- Default lang: \(defaultLang)
|
- Default lang: \(defaultLang)
|
||||||
- Extension output path: \(extensionOutputPath)
|
- Extension output path: \(extensionOutputPath ?? "-")
|
||||||
- Extension name: \(extensionName ?? "-")
|
- Extension name: \(extensionName ?? "-")
|
||||||
- Extension suffix: \(extensionSuffix ?? "-")
|
- Extension suffix: \(extensionSuffix ?? "-")
|
||||||
"""
|
"""
|
||||||
|
@@ -24,26 +24,24 @@ extension StringsConfiguration: Runnable {
|
|||||||
langs,
|
langs,
|
||||||
"--default-lang",
|
"--default-lang",
|
||||||
defaultLang,
|
defaultLang,
|
||||||
"--extension-output-path",
|
|
||||||
extensionOutputPath.prependIfRelativePath(projectDirectory),
|
|
||||||
"--static-members",
|
"--static-members",
|
||||||
"\(staticMembersOptions)",
|
"\(staticMembersOptions)",
|
||||||
"--xc-strings",
|
"--xc-strings",
|
||||||
"\(xcStringsOptions)"
|
"\(xcStringsOptions)"
|
||||||
]
|
]
|
||||||
|
|
||||||
if let extensionName {
|
// Add optional parameters
|
||||||
|
[
|
||||||
|
("--extension-output-path", extensionOutputPath?.prependIfRelativePath(projectDirectory)),
|
||||||
|
("--extension-name", extensionName),
|
||||||
|
("--extension-suffix", extensionSuffix)
|
||||||
|
].forEach { argumentName, argumentValue in
|
||||||
|
if let argumentValue {
|
||||||
args += [
|
args += [
|
||||||
"--extension-name",
|
argumentName,
|
||||||
extensionName
|
argumentValue
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
if let extensionSuffix {
|
|
||||||
args += [
|
|
||||||
"--extension-suffix",
|
|
||||||
extensionSuffix
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Stringium.main(args)
|
Stringium.main(args)
|
||||||
|
@@ -19,8 +19,7 @@ enum StringsFileGenerator {
|
|||||||
langs: [String],
|
langs: [String],
|
||||||
defaultLang: String,
|
defaultLang: String,
|
||||||
tags: [String],
|
tags: [String],
|
||||||
outputPath: String,
|
lprojPathFormat: String
|
||||||
inputFilenameWithoutExt: String
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var stringsFilesContent = [String: String]()
|
var stringsFilesContent = [String: String]()
|
||||||
@@ -37,7 +36,7 @@ enum StringsFileGenerator {
|
|||||||
langs.forEach { lang in
|
langs.forEach { lang in
|
||||||
guard let fileContent = stringsFilesContent[lang] else { return }
|
guard let fileContent = stringsFilesContent[lang] else { return }
|
||||||
|
|
||||||
let stringsFilePath = "\(outputPath)/\(lang).lproj/\(inputFilenameWithoutExt).strings"
|
let stringsFilePath = String(format: lprojPathFormat, lang)
|
||||||
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
|
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
|
||||||
do {
|
do {
|
||||||
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
|
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
|
||||||
@@ -54,8 +53,7 @@ enum StringsFileGenerator {
|
|||||||
langs: [String],
|
langs: [String],
|
||||||
defaultLang: String,
|
defaultLang: String,
|
||||||
tags: [String],
|
tags: [String],
|
||||||
outputPath: String,
|
xcStringsFilePath: String
|
||||||
inputFilenameWithoutExt: String
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
let fileContent: String = Self.generateXcStringsFileContent(
|
let fileContent: String = Self.generateXcStringsFileContent(
|
||||||
@@ -65,12 +63,11 @@ enum StringsFileGenerator {
|
|||||||
sections: sections
|
sections: sections
|
||||||
)
|
)
|
||||||
|
|
||||||
let stringsFilePath = "\(outputPath)/\(inputFilenameWithoutExt).xcstrings"
|
let stringsFilePathURL = URL(fileURLWithPath: xcStringsFilePath)
|
||||||
let stringsFilePathURL = URL(fileURLWithPath: stringsFilePath)
|
|
||||||
do {
|
do {
|
||||||
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
|
try fileContent.write(to: stringsFilePathURL, atomically: false, encoding: .utf8)
|
||||||
} catch {
|
} catch {
|
||||||
let error = StringiumError.writeFile(error.localizedDescription, stringsFilePath)
|
let error = StringiumError.writeFile(error.localizedDescription, xcStringsFilePath)
|
||||||
print(error.description)
|
print(error.description)
|
||||||
Stringium.exit(withError: error)
|
Stringium.exit(withError: error)
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,6 @@ struct Stringium: ParsableCommand {
|
|||||||
// MARK: - Static
|
// MARK: - Static
|
||||||
|
|
||||||
static let toolName = "Stringium"
|
static let toolName = "Stringium"
|
||||||
static let defaultExtensionName = "String"
|
|
||||||
static let noTranslationTag: String = "notranslation"
|
static let noTranslationTag: String = "notranslation"
|
||||||
|
|
||||||
// MARK: - Command options
|
// MARK: - Command options
|
||||||
@@ -49,8 +48,7 @@ struct Stringium: ParsableCommand {
|
|||||||
langs: options.langs,
|
langs: options.langs,
|
||||||
defaultLang: options.defaultLang,
|
defaultLang: options.defaultLang,
|
||||||
tags: options.tags,
|
tags: options.tags,
|
||||||
outputPath: options.stringsFileOutputPath,
|
lprojPathFormat: options.lprojPathFormat
|
||||||
inputFilenameWithoutExt: options.inputFilenameWithoutExt
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
StringsFileGenerator.writeXcStringsFiles(
|
StringsFileGenerator.writeXcStringsFiles(
|
||||||
@@ -58,22 +56,25 @@ struct Stringium: ParsableCommand {
|
|||||||
langs: options.langs,
|
langs: options.langs,
|
||||||
defaultLang: options.defaultLang,
|
defaultLang: options.defaultLang,
|
||||||
tags: options.tags,
|
tags: options.tags,
|
||||||
outputPath: options.stringsFileOutputPath,
|
xcStringsFilePath: options.xcStringsFilePath
|
||||||
inputFilenameWithoutExt: options.inputFilenameWithoutExt
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate extension
|
// Generate extension
|
||||||
|
if let extensionName = options.extensionName,
|
||||||
|
let extensionFilePath = options.extensionFilePath {
|
||||||
|
print("Will generate extensions")
|
||||||
StringsFileGenerator.writeExtensionFiles(
|
StringsFileGenerator.writeExtensionFiles(
|
||||||
sections: sections,
|
sections: sections,
|
||||||
defaultLang: options.defaultLang,
|
defaultLang: options.defaultLang,
|
||||||
tags: options.tags,
|
tags: options.tags,
|
||||||
staticVar: options.staticMembers,
|
staticVar: options.staticMembers,
|
||||||
inputFilename: options.inputFilenameWithoutExt,
|
inputFilename: options.inputFilenameWithoutExt,
|
||||||
extensionName: options.extensionName,
|
extensionName: extensionName,
|
||||||
extensionFilePath: options.extensionFilePath,
|
extensionFilePath: extensionFilePath,
|
||||||
extensionSuffix: options.extensionSuffix
|
extensionSuffix: options.extensionSuffix ?? ""
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
print("[\(Self.toolName)] Strings generated")
|
print("[\(Self.toolName)] Strings generated")
|
||||||
}
|
}
|
||||||
@@ -104,10 +105,18 @@ struct Stringium: ParsableCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if needed to regenerate
|
// Check if needed to regenerate
|
||||||
|
let fileToCompareToInput: String = {
|
||||||
|
// If there is no extension file to compare
|
||||||
|
// Then check the xcassets file instead
|
||||||
|
if options.xcStrings {
|
||||||
|
return options.xcStringsFilePath
|
||||||
|
}
|
||||||
|
return String(format: options.lprojPathFormat, options.defaultLang)
|
||||||
|
}()
|
||||||
guard GeneratorChecker.shouldGenerate(
|
guard GeneratorChecker.shouldGenerate(
|
||||||
force: options.forceGeneration,
|
force: options.forceGeneration,
|
||||||
inputFilePath: options.inputFile,
|
inputFilePath: options.inputFile,
|
||||||
extensionFilePath: options.extensionFilePath
|
extensionFilePath: fileToCompareToInput
|
||||||
) else {
|
) else {
|
||||||
print("[\(Self.toolName)] Strings are already up to date :) ")
|
print("[\(Self.toolName)] Strings are already up to date :) ")
|
||||||
return false
|
return false
|
||||||
|
@@ -15,35 +15,51 @@ struct StringiumOptions: ParsableArguments {
|
|||||||
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
|
@Flag(name: [.customShort("f"), .customShort("F")], help: "Should force generation")
|
||||||
var forceGeneration = false
|
var forceGeneration = false
|
||||||
|
|
||||||
@Argument(help: "Input files where strings are defined.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
|
@Argument(
|
||||||
|
help: "Input files where strings are defined.",
|
||||||
|
transform: { $0.replaceTiltWithHomeDirectoryPath() }
|
||||||
|
)
|
||||||
var inputFile: String
|
var inputFile: String
|
||||||
|
|
||||||
@Option(name: .customLong("output-path"), help: "Path where to strings file.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
|
@Option(
|
||||||
|
name: .customLong("output-path"),
|
||||||
|
help: "Path where to strings file.",
|
||||||
|
transform: { $0.replaceTiltWithHomeDirectoryPath()}
|
||||||
|
)
|
||||||
fileprivate var outputPathRaw: String
|
fileprivate var outputPathRaw: String
|
||||||
|
|
||||||
@Option(name: .customLong("langs"), help: "Langs to generate.")
|
@Option(
|
||||||
|
name: .customLong("langs"),
|
||||||
|
help: "Langs to generate."
|
||||||
|
)
|
||||||
fileprivate var langsRaw: String
|
fileprivate var langsRaw: String
|
||||||
|
|
||||||
@Option(help: "Default langs.")
|
@Option(help: "Default langs.")
|
||||||
var defaultLang: String
|
var defaultLang: String
|
||||||
|
|
||||||
@Option(name: .customLong("tags"), help: "Tags to generate.")
|
@Option(
|
||||||
|
name: .customLong("tags"),
|
||||||
|
help: "Tags to generate."
|
||||||
|
)
|
||||||
fileprivate var tagsRaw: String = "ios iosonly iosOnly notranslation"
|
fileprivate var tagsRaw: String = "ios iosonly iosOnly notranslation"
|
||||||
|
|
||||||
@Option(help: "Path where to generate the extension.", transform: { $0.replaceTiltWithHomeDirectoryPath() })
|
@Option(help: "Generate static properties. False by default")
|
||||||
var extensionOutputPath: String
|
|
||||||
|
|
||||||
@Option(help: "Tell if it will generate static properties or not")
|
|
||||||
var staticMembers: Bool = false
|
var staticMembers: Bool = false
|
||||||
|
|
||||||
@Option(help: "Tell if it will generate xcStrings file or not")
|
@Option(help: "Tell if it will generate xcStrings file or lproj file. True by default")
|
||||||
var xcStrings: Bool = false
|
var xcStrings: Bool = true
|
||||||
|
|
||||||
@Option(help: "Extension name. If not specified, it will generate an String extension.")
|
@Option(
|
||||||
var extensionName: String = Stringium.defaultExtensionName
|
help: "Path where to generate the extension.",
|
||||||
|
transform: { $0.replaceTiltWithHomeDirectoryPath() }
|
||||||
|
)
|
||||||
|
var extensionOutputPath: String?
|
||||||
|
|
||||||
|
@Option(help: "Extension name. If not specified, no extension will be generated.")
|
||||||
|
var extensionName: String?
|
||||||
|
|
||||||
@Option(help: "Extension suffix: {extensionName}+{extensionSuffix}.swift")
|
@Option(help: "Extension suffix: {extensionName}+{extensionSuffix}.swift")
|
||||||
var extensionSuffix: String
|
var extensionSuffix: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private var getter
|
// MARK: - Private var getter
|
||||||
@@ -75,12 +91,21 @@ extension StringiumOptions {
|
|||||||
|
|
||||||
extension StringiumOptions {
|
extension StringiumOptions {
|
||||||
|
|
||||||
var extensionFileName: String {
|
private var extensionFileName: String? {
|
||||||
"\(extensionName)+\(extensionSuffix).swift"
|
if let extensionName {
|
||||||
|
if let extensionSuffix {
|
||||||
|
return "\(extensionName)+\(extensionSuffix).swift"
|
||||||
|
}
|
||||||
|
return "\(extensionName).swift"
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var extensionFilePath: String {
|
var extensionFilePath: String? {
|
||||||
"\(extensionOutputPath)/\(extensionFileName)"
|
if let extensionOutputPath, let extensionFileName {
|
||||||
|
return "\(extensionOutputPath)/\(extensionFileName)"
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var inputFilenameWithoutExt: String {
|
var inputFilenameWithoutExt: String {
|
||||||
@@ -88,4 +113,12 @@ extension StringiumOptions {
|
|||||||
.deletingPathExtension()
|
.deletingPathExtension()
|
||||||
.lastPathComponent
|
.lastPathComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var xcStringsFilePath: String {
|
||||||
|
"\(stringsFileOutputPath)/\(inputFilenameWithoutExt).xcstrings"
|
||||||
|
}
|
||||||
|
|
||||||
|
var lprojPathFormat: String {
|
||||||
|
"\(stringsFileOutputPath)/%@.lproj/\(inputFilenameWithoutExt).strings"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user