Aerial/Source/Models/Cache/PoiStringProvider.swift
//
// 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("/macOS 14")
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("/macOS 14/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)
}
func getLocalizedNameKey(key: String) -> String {
guard ensureLoadedBundle() else { return "" }
return stringBundle!.localizedString(forKey: key, value: "", table: "Localizable.nocache")
}
// 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 ""
}
}
}