@@ -24,6 +24,7 @@ struct StringsTableLoader {
24
24
public init ( rawValue: UInt ) { self . rawValue = rawValue }
25
25
26
26
public static let lineNumbers = Options ( rawValue: 1 << 0 )
27
+ public static let ignoreCached = Options ( rawValue: 1 << 1 )
27
28
}
28
29
29
30
var options : Options = [ ]
@@ -40,23 +41,95 @@ struct StringsTableLoader {
40
41
var entries : StringsTable . EntriesType = [ : ]
41
42
var dictEntries : StringsTable . DictEntriesType = [ : ]
42
43
44
+ var cached : CachedStringsTable ?
45
+ if !options. contains ( . ignoreCached) , let url = cacheURL ( for: name) , let data = try ? Data ( contentsOf: url) {
46
+ let decoder = PropertyListDecoder ( )
47
+ cached = try ? decoder. decode ( CachedStringsTable . self, from: data)
48
+ }
49
+
43
50
try url. lprojURLs. forEach {
44
51
guard let locale = $0. locale else { return }
45
52
46
53
let stringsTableURL = $0. appendingPathComponent ( name) . appendingPathExtension ( " strings " )
47
- if let reachable = try ? stringsTableURL. checkResourceIsReachable ( ) , reachable == true {
54
+ if let cached = cached, cached. isCacheValid ( for: locale, type: . strings, base: url) , let cachedStrings = cached. strings ( for: locale) {
55
+ entries [ locale] = cachedStrings
56
+ } else if let reachable = try ? stringsTableURL. checkResourceIsReachable ( ) , reachable == true {
48
57
entries [ locale] = try load ( from: stringsTableURL, options: options)
49
58
}
50
59
51
60
let stringsDictTableURL = $0. appendingPathComponent ( name) . appendingPathExtension ( " stringsdict " )
52
- if let reachable = try ? stringsDictTableURL. checkResourceIsReachable ( ) , reachable == true {
61
+ if let cached = cached, cached. isCacheValid ( for: locale, type: . stringsdict, base: url) , let cachedStringsDict = cached. stringsDict ( for: locale) {
62
+ dictEntries [ locale] = cachedStringsDict
63
+ } else if let reachable = try ? stringsDictTableURL. checkResourceIsReachable ( ) , reachable == true {
53
64
dictEntries [ locale] = try load ( from: stringsDictTableURL)
54
65
}
55
66
}
56
67
57
68
return StringsTable ( name: name, base: base, entries: entries, dictEntries: dictEntries)
58
69
}
59
70
71
+ func write( to url: Foundation . URL , table: StringsTable ) throws {
72
+ for (languageId, languageEntries) in table. entries where !languageEntries. isEmpty {
73
+ let fileURL = try url. stringsURL ( tableName: table. name, locale: languageId)
74
+ guard let outputStream = OutputStream ( url: fileURL, append: false ) else { continue }
75
+ outputStream. open ( )
76
+ var firstEntry = true
77
+ for entry in languageEntries {
78
+ if !firstEntry {
79
+ outputStream. write ( string: " \n " )
80
+ }
81
+ firstEntry = false
82
+ outputStream. write ( string: " \( entry) \n " )
83
+ }
84
+ outputStream. close ( )
85
+ }
86
+
87
+ for (languageId, languageEntries) in table. dictEntries where !languageEntries. isEmpty {
88
+ let fileURL = try url. stringsDictURL ( tableName: table. name, locale: languageId)
89
+ let encoder = PropertyListEncoder ( )
90
+ encoder. outputFormat = . xml
91
+ let data = try encoder. encode ( languageEntries)
92
+ try data. write ( to: fileURL, options: [ . atomic] )
93
+ }
94
+ }
95
+
96
+ func writeCache( table: StringsTable , baseURL: URL ) throws {
97
+ var cacheKeys : [ String : Date ] = [ : ]
98
+
99
+ for (languageId, languageEntries) in table. entries where !languageEntries. isEmpty {
100
+ let fileURL = try baseURL. stringsURL ( tableName: table. name, locale: languageId)
101
+ let attributes = try FileManager . default. attributesOfItem ( atPath: fileURL. path)
102
+ guard let modificationDate = attributes [ . modificationDate] as? Date else { continue }
103
+ cacheKeys [ CachedStringsTable . cacheKey ( for: languageId, type: . strings) ] = modificationDate
104
+ }
105
+
106
+ for (languageId, languageEntries) in table. dictEntries where !languageEntries. isEmpty {
107
+ let fileURL = try baseURL. stringsDictURL ( tableName: table. name, locale: languageId)
108
+ let attributes = try FileManager . default. attributesOfItem ( atPath: fileURL. path)
109
+ guard let modificationDate = attributes [ . modificationDate] as? Date else { continue }
110
+ cacheKeys [ CachedStringsTable . cacheKey ( for: languageId, type: . stringsdict) ] = modificationDate
111
+ }
112
+
113
+ let cachedTable = CachedStringsTable ( stringsTable: table, cacheKeys: cacheKeys)
114
+ let encoder = PropertyListEncoder ( )
115
+ encoder. outputFormat = . binary
116
+ let cachedData = try encoder. encode ( cachedTable)
117
+ guard let url = cacheURL ( for: table. name) else { return }
118
+ try cachedData. write ( to: url, options: [ . atomic] )
119
+ }
120
+
121
+ private func cacheURL( for tableName: String ) -> Foundation . URL ? {
122
+ let bundleIdentifier = Bundle . main. bundleIdentifier ?? " net.g-Off.stringray "
123
+ let filePath = " \( bundleIdentifier) / \( tableName) .localization "
124
+ guard let cacheURL = try ? FileManager . default. url ( for: . applicationSupportDirectory, in: . userDomainMask, appropriateFor: URL ( fileURLWithPath: filePath) , create: true )
125
+ else {
126
+ return nil
127
+ }
128
+ let fileURL = URL ( fileURLWithPath: filePath, relativeTo: cacheURL)
129
+ try ! FileManager . default. createDirectory ( at: fileURL. deletingLastPathComponent ( ) , withIntermediateDirectories: true , attributes: nil )
130
+ return URL ( fileURLWithPath: filePath, relativeTo: cacheURL)
131
+ }
132
+
60
133
private func load( from url: URL , options: Options ) throws -> OrderedSet < StringsTable . Entry > {
61
134
func lineNumber( scanLocation: Int , newlineLocations: [ Int ] ) -> Int {
62
135
var lastIndex = 0
@@ -123,3 +196,10 @@ struct StringsTableLoader {
123
196
return try decoder. decode ( [ String : StringsTable . DictEntry ] . self, from: data)
124
197
}
125
198
}
199
+
200
+ private extension OutputStream {
201
+ func write( string: String ) {
202
+ let encodedDataArray = [ UInt8] ( string. utf8)
203
+ write ( encodedDataArray, maxLength: encodedDataArray. count)
204
+ }
205
+ }
0 commit comments