|
|
|
@ -12,12 +12,22 @@
|
|
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
|
|
|
|
|
typealias JSON = [String:Any]
|
|
|
|
|
|
|
|
|
|
extension ProjectState {
|
|
|
|
|
/// Adding `// stringlint:disable` to the top of a source file (before imports) or after a string will mean that file/line gets
|
|
|
|
|
/// ignored by this script (good for some things like the auto-generated emoji strings or debug strings)
|
|
|
|
|
static let lintSuppression: String = "stringlint:disable"
|
|
|
|
|
static let primaryLocalisationFile: String = "en"
|
|
|
|
|
static let validLocalisationSuffixes: Set<String> = ["Localizable.strings"]
|
|
|
|
|
static let permissionStrings: Set<String> = [
|
|
|
|
|
"NSAppleMusicUsageDescription",
|
|
|
|
|
"NSCameraUsageDescription",
|
|
|
|
|
"NSFaceIDUsageDescription",
|
|
|
|
|
"NSMicrophoneUsageDescription",
|
|
|
|
|
"NSPhotoLibraryAddUsageDescription",
|
|
|
|
|
"NSPhotoLibraryUsageDescription"
|
|
|
|
|
]
|
|
|
|
|
static let validSourceSuffixes: Set<String> = [".swift", ".m"]
|
|
|
|
|
static let excludedPaths: Set<String> = [
|
|
|
|
|
"build/", // Files under the build folder (CI)
|
|
|
|
@ -224,7 +234,40 @@ enum ScriptAction: String {
|
|
|
|
|
break
|
|
|
|
|
case .updatePermissionStrings:
|
|
|
|
|
print("------------ Updating permission strings ------------")
|
|
|
|
|
var updatedInfoPlistJSON: JSON = projectState.infoPlistLocalizationFile.json
|
|
|
|
|
var strings: JSON = updatedInfoPlistJSON["strings"] as! JSON
|
|
|
|
|
projectState.localizationFiles.forEach { file in
|
|
|
|
|
ProjectState.permissionStrings.forEach { key in
|
|
|
|
|
var keyPhrases: JSON = strings[key] as! JSON
|
|
|
|
|
var localizations: JSON = keyPhrases["localizations"] as! JSON
|
|
|
|
|
if let phrase: String = file.keyPhrase[key]?.value {
|
|
|
|
|
if let translations: JSON = localizations[file.name] as? JSON {
|
|
|
|
|
var stringUnit: JSON = translations["stringUnit"] as! JSON
|
|
|
|
|
if (stringUnit["value"] as! String) != phrase {
|
|
|
|
|
stringUnit["state"] = "translated"
|
|
|
|
|
stringUnit["value"] = phrase
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let stringUnit: JSON = [
|
|
|
|
|
"state": "translated",
|
|
|
|
|
"value": phrase.replacingOccurrences(of: "\"", with: "")
|
|
|
|
|
]
|
|
|
|
|
localizations[file.name] = ["stringUnit": stringUnit]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
keyPhrases["localizations"] = localizations
|
|
|
|
|
strings[key] = keyPhrases
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
updatedInfoPlistJSON["strings"] = strings
|
|
|
|
|
|
|
|
|
|
if let data: Data = try? JSONSerialization.data(withJSONObject: updatedInfoPlistJSON, options: [ .fragmentsAllowed ]) {
|
|
|
|
|
do {
|
|
|
|
|
try data.write(to: URL(fileURLWithPath: projectState.infoPlistLocalizationFile.path), options: [.atomic])
|
|
|
|
|
} catch {
|
|
|
|
|
fatalError("Could not write to InfoPlist.xcstrings, error: \(error)")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print("------------ Complete ------------")
|
|
|
|
@ -320,6 +363,11 @@ struct ProjectState {
|
|
|
|
|
}
|
|
|
|
|
self.primaryLocalizationFile = primaryLocalizationFile
|
|
|
|
|
|
|
|
|
|
self.infoPlistLocalizationFile = validFileUrls
|
|
|
|
|
.filter { fileUrl in fileUrl.path.contains("InfoPlist.xcstrings") }
|
|
|
|
|
.map { XCStringsFile(path: $0.path) }
|
|
|
|
|
.last!
|
|
|
|
|
|
|
|
|
|
guard loadSourceFiles else {
|
|
|
|
|
self.sourceFiles = []
|
|
|
|
|
return
|
|
|
|
@ -344,32 +392,28 @@ protocol KeyedLocatable: Locatable {
|
|
|
|
|
extension ProjectState {
|
|
|
|
|
// MARK: - XCStringsFile
|
|
|
|
|
struct XCStringsFile: Locatable {
|
|
|
|
|
struct Phrase {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
let name: String
|
|
|
|
|
let path: String
|
|
|
|
|
let keyPhrase: [String: JSON]
|
|
|
|
|
var json: JSON
|
|
|
|
|
|
|
|
|
|
var location: String { path }
|
|
|
|
|
|
|
|
|
|
init(path: String) {
|
|
|
|
|
let result = LocalizationStringsFile.parse(path)
|
|
|
|
|
|
|
|
|
|
self.name = (path
|
|
|
|
|
.replacingOccurrences(of: ".xcstrings", with: "")
|
|
|
|
|
.components(separatedBy: "/")
|
|
|
|
|
.last ?? "Unknown")
|
|
|
|
|
self.path = path
|
|
|
|
|
self.json = XCStringsFile.parse(path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static func parse(_ path: String) -> [String: JSON] {
|
|
|
|
|
static func parse(_ path: String) -> JSON {
|
|
|
|
|
guard
|
|
|
|
|
let data: Data = FileManager.default.contents(atPath: path),
|
|
|
|
|
let content: String = String(data: data, encoding: .utf8)
|
|
|
|
|
let json: JSON = try? JSONSerialization.jsonObject(with: data, options: [ .fragmentsAllowed ]) as? JSON
|
|
|
|
|
else { fatalError("Could not read from path: \(path)") }
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
return json
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|