-
Notifications
You must be signed in to change notification settings - Fork 1k
/
PoiStringProvider.swift
346 lines (304 loc) · 12.2 KB
/
PoiStringProvider.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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
//
// PoiStringProvider.swift
// Aerial
//
// Created by Guillaume Louel on 13/10/2018.
// Copyright © 2018 John Coates. All rights reserved.
//
import Foundation
final class CommunityStrings {
let id: String
let name: String
let poi: [String: String]
init(id: String, name: String, poi: [String: String]) {
self.id = id
self.name = name
self.poi = poi
}
}
final class PoiStringProvider {
static let sharedInstance = PoiStringProvider()
var loadedDescriptions = false
var loadedDescriptionsWasLocalized = false
var stringBundle: Bundle?
var stringDict: [String: String]?
var communityStrings = [CommunityStrings]()
var communityLanguage = ""
// MARK: - Lifecycle
init() {
debugLog("Poi Strings Provider initialized")
loadBundle()
loadCommunity()
}
// MARK: - Bundle management
private func getBundleLanguages() -> [String] {
// Might want to improve that...
// This is a static list of what's supposed to be in the bundle
// swiftlint:disable:next line_length
return ["de", "he", "en_AU", "ar", "el", "ja", "en", "uk", "es_419", "zh_CN", "es", "pt_BR", "da", "it", "sk", "pt_PT", "ms", "sv", "cs", "ko", "no", "hu", "zh_HK", "tr", "pl", "zh_TW", "en_GB", "vi", "ru", "fr_CA", "fr", "fi", "id", "nl", "th", "pt", "ro", "hr", "hi", "ca"]
}
private func loadBundle() {
// Idle string bundle
var bundlePath = Cache.supportPath.appending("/tvOS 16")
if PrefsAdvanced.ciOverrideLanguage == "" {
debugLog("Preferred languages : \(Locale.preferredLanguages)")
let bestMatchedLanguage = Bundle.preferredLocalizations(from: getBundleLanguages(), forPreferences: Locale.preferredLanguages).first
if let match = bestMatchedLanguage {
debugLog("Best matched language : \(match)")
bundlePath.append(contentsOf: "/TVIdleScreenStrings.bundle/" + match + ".lproj/")
} else {
debugLog("No match, reverting to english")
// We load the bundle and let system grab the closest available preferred language
// This no longer works in Catalina and defaults back to english
// as legacyScreenSaver.appex, our new "mainbundle" is english only
bundlePath.append(contentsOf: "/TVIdleScreenStrings.bundle")
}
} else {
let bestMatchedLanguage = Bundle.preferredLocalizations(from: getBundleLanguages(), forPreferences: [PrefsAdvanced.ciOverrideLanguage]).first
if let match = bestMatchedLanguage {
debugLog("Best matched language : \(match)")
bundlePath.append(contentsOf: "/TVIdleScreenStrings.bundle/" + match + ".lproj/")
} else {
debugLog("No match, reverting to english")
// We load the bundle and let system grab the closest available preferred language
// This no longer works in Catalina and defaults back to english
// as legacyScreenSaver.appex, our new "mainbundle" is english only
bundlePath.append(contentsOf: "/TVIdleScreenStrings.bundle")
}
/*debugLog("Language overriden to \(String(describing: bestMatchedLanguage))")
// Or we load the overriden one
bundlePath.append(contentsOf: "/TVIdleScreenStrings.bundle/" + PrefsAdvanced.ciOverrideLanguage + ".lproj/")*/
}
if let sb = Bundle.init(path: bundlePath) {
let dictPath = Cache.supportPath.appending("/tvOS 16/TVIdleScreenStrings.bundle/en.lproj/Localizable.nocache.strings")
// We could probably only work with that...
if let sd = NSDictionary(contentsOfFile: dictPath) as? [String: String] {
self.stringDict = sd
}
self.stringBundle = sb
self.loadedDescriptions = true
} else {
errorLog("TVIdleScreenStrings.bundle is missing, please remove entries.json in Cache folder to fix the issue")
}
}
// Make sure we have the correct bundle loaded
private func ensureLoadedBundle() -> Bool {
if loadedDescriptions {
return true
} else {
loadBundle()
return loadedDescriptions
}
}
// Return the Localized (or english) string for a key from the Strings Bundle
func getString(key: String, video: AerialVideo) -> String {
guard ensureLoadedBundle() else { return "" }
/*let preferences = Preferences.sharedInstance
let locale: NSLocale = NSLocale(localeIdentifier: Locale.preferredLanguages[0])
if #available(OSX 10.12, *) {
if preferences.localizeDescriptions && locale.languageCode != communityLanguage && preferences.ciOverrideLanguage == "" {
return stringBundle!.localizedString(forKey: key, value: "", table: "Localizable.nocache")
}
}*/
if !video.communityPoi.isEmpty {
return key // We directly store the string in the key
} else {
return stringBundle!.localizedString(forKey: key, value: "", table: "Localizable.nocache")
}
}
// Return all POIs for an id
func fetchExtraPoiForId(id: String) -> [String: String]? {
guard let stringDict = stringDict, ensureLoadedBundle() else { return [:] }
var found = [String: String]()
for key in stringDict.keys where key.starts(with: id) {
found[String(key.split(separator: "_").last!)] = key // FIXME: crash if key doesn't have "_"
}
return found
}
//
func getPoiKeys(video: AerialVideo) -> [String: String] {
if !video.communityPoi.isEmpty {
return video.communityPoi
} else {
return video.poi
}
}
// Do we have any keys, anywhere, for said video ?
func hasPoiKeys(video: AerialVideo) -> Bool {
return (!video.poi.isEmpty && loadedDescriptions) ||
(!video.communityPoi.isEmpty && !getPoiKeys(video: video).isEmpty)
}
// MARK: - Community data
// siftlint:disable:next cyclomatic_complexity
private func getCommunityPathForLocale() -> String {
let locale: NSLocale = NSLocale(localeIdentifier: Locale.preferredLanguages[0])
// Do we have a language override ?
if PrefsAdvanced.ciOverrideLanguage != "" {
let path = Bundle(for: PoiStringProvider.self).path(forResource: PrefsAdvanced.ciOverrideLanguage, ofType: "json")
if path != nil {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: path!) {
debugLog("Community Language overriden to : \(PrefsAdvanced.ciOverrideLanguage)")
communityLanguage = PrefsAdvanced.ciOverrideLanguage
return path!
}
}
}
if #available(OSX 10.12, *) {
// First we look in the Cache Folder for a locale directory
let cacheDirectory = Cache.supportPath
var cacheResourcesString = cacheDirectory
cacheResourcesString.append(contentsOf: "/locale")
let cacheUrl = URL(fileURLWithPath: cacheResourcesString)
if cacheUrl.hasDirectoryPath {
debugLog("Aerial cache directory contains /locale")
let cc = locale.languageCode
debugLog("Looking for \(cc).json")
let fileUrl = URL(fileURLWithPath: cacheResourcesString.appending("/\(cc).json"))
debugLog(fileUrl.absoluteString)
let fileManager = FileManager.default
if fileManager.fileExists(atPath: fileUrl.path) {
debugLog("Locale description found")
communityLanguage = cc
return fileUrl.path
} else {
debugLog("Locale description not found")
}
}
debugLog("Defaulting to bundle")
let cc = locale.languageCode
let path = Bundle(for: PoiStringProvider.self).path(forResource: cc, ofType: "json")
if path != nil {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: path!) {
communityLanguage = cc
return path!
}
}
}
// Fallback to english in bundle
communityLanguage = "en"
return Bundle(for: PoiStringProvider.self).path(forResource: "en", ofType: "json")!
}
// Load the community strings
private func loadCommunity() {
let bundlePath = getCommunityPathForLocale()
debugLog("path : \(bundlePath)")
do {
let data = try Data(contentsOf: URL(fileURLWithPath: bundlePath), options: .mappedIfSafe)
let batches = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
guard let batch = batches as? NSDictionary else {
errorLog("Community : Encountered unexpected content type for batch, please report !")
return
}
for item in batch {
let id = item.key as! String
let name = (item.value as! NSDictionary)["name"] as! String
let poi = (item.value as! NSDictionary)["pointsOfInterest"] as? [String: String]
communityStrings.append(CommunityStrings(id: id, name: name, poi: poi ?? [:]))
}
} catch {
// handle error
errorLog("Community JSON ERROR : \(error)")
}
debugLog("Community JSON : \(communityStrings.count) entries")
}
func getCommunityName(id: String) -> String? {
return communityStrings.first(where: { $0.id == id }).map { $0.name }
}
func getCommunityPoi(id: String) -> [String: String] {
return communityStrings.first(where: { $0.id == id }).map { $0.poi } ?? [:]
}
// Helpers for the main popup
// swiftlint:disable:next cyclomatic_complexity
func getLanguagePosition() -> Int {
// The list is alphabetized based on their english name in the UI
switch PrefsAdvanced.ciOverrideLanguage {
case "ar": // Arabic
return 1
case "zh_CN": // Chinese Simplified
return 2
case "zh_TW": // Chinese Traditional
return 3
case "nl": // Dutch
return 4
case "en": // English
return 5
case "fr": // French
return 6
case "de": // German
return 7
case "he": // Hebrew
return 8
case "hu": // Hungarian
return 9
case "it": // Italian
return 10
case "ja": // Japanese
return 11
case "ko": // Korean
return 12
case "pl": // Polish
return 13
case "pt": // Portuguese
return 14
case "pt_BR": // Portuguese (Brazil)
return 15
case "ru": // Russian
return 16
case "es": // Spanish
return 17
case "sv": // Swedish
return 18
case "tl": // Tagalog
return 19
default: // This is the default, preferred language
return 0
}
}
// swiftlint:disable:next cyclomatic_complexity
func getLanguageStringFromPosition(pos: Int) -> String {
switch pos {
case 1:
return "ar"
case 2:
return "zh_CN"
case 3:
return "zh_TW"
case 4:
return "nl"
case 5:
return "en"
case 6:
return "fr"
case 7:
return "de"
case 8:
return "he"
case 9:
return "hu"
case 10:
return "it"
case 11:
return "ja"
case 12:
return "ko"
case 13:
return "pl"
case 14:
return "pt"
case 15:
return "pt_BR"
case 16:
return "ru"
case 17:
return "es"
case 18:
return "sv"
case 19:
return "tl"
default:
return ""
}
}
}