-
Notifications
You must be signed in to change notification settings - Fork 7
/
CSV2Localizables.swift
126 lines (97 loc) · 4.4 KB
/
CSV2Localizables.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import Foundation
class CSV2Localizables {
private typealias Key = String
private typealias LanguageId = String
private typealias Content = String
private let csvURL: URL
private let options: Options
init(csvURL: URL, options: Option...) {
self.csvURL = csvURL
self.options = Options(from: options)
}
func writeFiles(to outputURL: URL) {
let translationByKey = translations(from: self.csvURL)
let translationsByLanguage = translationByLanguage(from: translationByKey)
for (languageKey, keyContent) in translationsByLanguage {
let languageFolderURL = outputURL.appendingPathComponent("\(languageKey).lproj")
guard (try? FileManager.default.createDirectory(at: languageFolderURL, withIntermediateDirectories: true)) != nil else { continue }
let localizableStrings: String = keyContent.map { #""**\#($0)**" = "\#($1)";"# }.joined(separator: "\n")
let localizableStringURL = languageFolderURL.appendingPathComponent("Localizable.strings")
try? FileManager.default.removeItem(at: localizableStringURL)
guard FileManager.default.createFile(atPath: localizableStringURL.path, contents: Data(localizableStrings.utf8)) else { continue }
}
}
private func translations(from url: URL) -> [Key: [LanguageId: Content]] {
guard let data = FileManager.default.contents(atPath: url.path) else { return [:] }
let csv = String(decoding: data, as: UTF8.self)
var translationsOutput: [Key: [LanguageId: Content]] = [:]
let lines = csv.components(separatedBy: CharacterSet.newlines).filter { !$0.isEmpty }
let columnNames = lines.first?.split(separator: options.columnSeparator) ?? []
let languages = Array(columnNames.dropFirst())
let linesSkippingColumnNames = lines.dropFirst()
for line in linesSkippingColumnNames {
let lineParts = line.split(separator: options.columnSeparator)
guard lineParts.count == columnNames.count else {
if lineParts.count > 0 {
assertionFailure("Bad parse or one language is missing its localization.\n - Full line: \(line)\n - Line parts\(lineParts)")
}
continue
}
var languagesDictionary: [LanguageId: Content] = [:]
for (language, linePart) in zip(languages, lineParts.dropFirst()) {
let languageId = String(language)
languagesDictionary[languageId] = escape(text: String(linePart))
}
let key = String(lineParts[0])
translationsOutput[key] = languagesDictionary
}
return translationsOutput
}
private func escape(text: String) -> String {
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedQuotes = trimmed.replacingOccurrences(of: "\"", with: #"\""#)
let trimmedSeparator = trimmedQuotes.replacingOccurrences(of: String(options.columnSeparator), with: "_")
let explicitNewlineCharacter = trimmedSeparator.replacingOccurrences(of: #"\\n"#, with: #"\n"#)
return explicitNewlineCharacter
}
private func translationByLanguage(from translationByKey: [Key: [LanguageId: Content]]) -> [LanguageId: [Key: Content]] {
var translationByLanguage: [LanguageId: [Key: Content]] = [:]
for (key, languageAndContent) in translationByKey {
for (languageKey, content) in languageAndContent {
if translationByLanguage[languageKey] == nil {
translationByLanguage[languageKey] = [:]
}
translationByLanguage[languageKey]?[key] = content
}
}
return translationByLanguage
}
}
extension CSV2Localizables {
enum Option {
case columnSeparator(Character)
case failIfMissingLocalization(Bool)
}
fileprivate struct Options {
var columnSeparator: Character = "~"
var failIfMissingLocalization: Bool = true
init(from optionArray: [Option]) {
for option in optionArray {
switch option {
case .columnSeparator(let character):
self.columnSeparator = character
case .failIfMissingLocalization(let flag):
self.failIfMissingLocalization = flag
}
}
}
}
}
//
let projectName = "99_Stocks"
let projectBaseURL = URL(fileURLWithPath: "/Users/daniel/Documents/Programming/IDE Projects/Xcode/Projects/- TestProjects/\(projectName)", isDirectory: true)
//
let currentDirectoryURL = projectBaseURL.appendingPathComponent("Generation/Localization", isDirectory: true)
let csvURL = currentDirectoryURL.appendingPathComponent("Localizable.csv", isDirectory: false)
let csv2Localizables = CSV2Localizables(csvURL: csvURL, options: .columnSeparator(";"))
csv2Localizables.writeFiles(to: projectBaseURL.appendingPathComponent("\(projectName)/Resources"))