File: Locale_Cache.swift

package info (click to toggle)
swiftlang 6.1.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,791,532 kB
  • sloc: cpp: 9,901,743; ansic: 2,201,431; asm: 1,091,827; python: 308,252; objc: 82,166; f90: 80,126; lisp: 38,358; pascal: 25,559; sh: 20,429; ml: 5,058; perl: 4,745; makefile: 4,484; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (352 lines) | stat: -rw-r--r-- 13,571 bytes parent folder | download
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
347
348
349
350
351
352
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#if FOUNDATION_FRAMEWORK
internal import _ForSwiftFoundation
import CoreFoundation
internal import CoreFoundation_Private.CFNotificationCenter
internal import os
#endif

internal import _FoundationCShims

#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
// Here, we always have access to _LocaleICU
internal func _localeICUClass() -> _LocaleProtocol.Type {
    _LocaleICU.self
}
#else
dynamic package func _localeICUClass() -> _LocaleProtocol.Type {
    // Return _LocaleUnlocalized if FoundationInternationalization isn't loaded. The `Locale` initializers are not failable, so we just fall back to the unlocalized type when needed without failure.
    _LocaleUnlocalized.self
}
#endif

/// Singleton which listens for notifications about preference changes for Locale and holds cached singletons.
struct LocaleCache : Sendable, ~Copyable {
    // MARK: - State
    
    struct State {
        
        init() {
#if FOUNDATION_FRAMEWORK
            // For Foundation.framework, we listen for system notifications about the system Locale changing from the Darwin notification center.
            _CFNotificationCenterInitializeDependentNotificationIfNecessary(CFNotificationName.cfLocaleCurrentLocaleDidChange!.rawValue)
#endif
        }
        
        private var cachedFixedLocales: [String : any _LocaleProtocol] = [:]
        private var cachedFixedComponentsLocales: [Locale.Components : any _LocaleProtocol] = [:]

#if FOUNDATION_FRAMEWORK
        private var cachedFixedIdentifierToNSLocales: [String : _NSSwiftLocale] = [:]
        
        struct IdentifierAndPrefs : Hashable {
            let identifier: String
            let prefs: LocalePreferences?
        }
        
        private var cachedFixedLocaleToNSLocales: [IdentifierAndPrefs : _NSSwiftLocale] = [:]
#endif
                
        mutating func fixed(_ id: String) -> any _LocaleProtocol {
            // Note: Even if the currentLocale's identifier is the same, currentLocale may have preference overrides which are not reflected in the identifier itself.
            if let locale = cachedFixedLocales[id] {
                return locale
            } else {
                let locale = _localeICUClass().init(identifier: id, prefs: nil)
                cachedFixedLocales[id] = locale
                return locale
            }
        }

#if FOUNDATION_FRAMEWORK
        mutating func fixedNSLocale(identifier id: String) -> _NSSwiftLocale {
            if let locale = cachedFixedIdentifierToNSLocales[id] {
                return locale
            } else {
                let inner = Locale(inner: fixed(id))
                let locale = _NSSwiftLocale(inner)
                // We have found ObjC clients that rely upon an immortal lifetime for these `Locale`s, so we do not clear this cache.
                cachedFixedIdentifierToNSLocales[id] = locale
                return locale
            }
        }
        
#if canImport(_FoundationICU)
        mutating func fixedNSLocale(_ locale: _LocaleICU) -> _NSSwiftLocale {
            let id = IdentifierAndPrefs(identifier: locale.identifier, prefs: locale.prefs)
            if let locale = cachedFixedLocaleToNSLocales[id] {
                return locale
            } else {
                let inner = Locale(inner: locale)
                let nsLocale = _NSSwiftLocale(inner)
                // We have found ObjC clients that rely upon an immortal lifetime for these `Locale`s, so we do not clear this cache.
                cachedFixedLocaleToNSLocales[id] = nsLocale
                return nsLocale
            }
        }
#endif

#endif // FOUNDATION_FRAMEWORK

        func fixedComponents(_ comps: Locale.Components) -> (any _LocaleProtocol)? {
            cachedFixedComponentsLocales[comps]
        }
        
        mutating func fixedComponentsWithCache(_ comps: Locale.Components) -> any _LocaleProtocol {
            if let l = fixedComponents(comps) {
                return l
            } else {
                let new = _localeICUClass().init(components: comps)
                
                cachedFixedComponentsLocales[comps] = new
                return new
            }
        }
    }

    let lock: LockedState<State>
    
    static let cache = LocaleCache()
    private let _currentCache = LockedState<(any _LocaleProtocol)?>(initialState: nil)
    
#if FOUNDATION_FRAMEWORK
    private var _currentNSCache = LockedState<_NSSwiftLocale?>(initialState: nil)
#endif
    
    fileprivate init() {
        lock = LockedState(initialState: State())
    }

    
    /// For testing of `autoupdatingCurrent` only. If you want to test `current`, create a custom `Locale` with the appropriate settings using `localeAsIfCurrent(name:overrides:disableBundleMatching:)` and use that instead.
    /// This mutates global state of the current locale, so it is not safe to use in concurrent testing.
    func resetCurrent(to preferences: LocalePreferences) {
        // Disable bundle matching so we can emulate a non-English main bundle during test
        let newLocale = _localeICUClass().init(name: nil, prefs: preferences, disableBundleMatching: true)
        _currentCache.withLock {
            $0 = newLocale
        }
#if FOUNDATION_FRAMEWORK
        _currentNSCache.withLock { $0 = nil }
#endif
    }

    func reset() {
        _currentCache.withLock { $0 = nil }
#if FOUNDATION_FRAMEWORK
        _currentNSCache.withLock { $0 = nil }
#endif
    }

    var current: any _LocaleProtocol {
        if let result = _currentCache.withLock({ $0 }) {
            return result
        }
        
        // We need to fetch prefs and try again
        let (preferences, doCache) = preferences()
        let locale = _localeICUClass().init(name: nil, prefs: preferences, disableBundleMatching: false)
        
        // It's possible this was an 'incomplete locale', in which case we will want to calculate it again later.
        if doCache {
            return _currentCache.withLock {
                if let current = $0 {
                    // Someone beat us to setting it - use existing one
                    return current
                } else {
                    $0 = locale
                    return locale
                }
            }
        }
        
        return locale
    }
    
    // MARK: Singletons
    
    // This value is immutable, so we can share one instance for the whole process.
    static let unlocalized = _LocaleUnlocalized(identifier: "en_001")

    // This value is immutable, so we can share one instance for the whole process.
    static let autoupdatingCurrent = _LocaleAutoupdating()

    static let system : any _LocaleProtocol = {
        _localeICUClass().init(identifier: "", prefs: nil)
    }()
    
#if FOUNDATION_FRAMEWORK
    static let autoupdatingCurrentNSLocale : _NSSwiftLocale = {
        _NSSwiftLocale(Locale(inner: autoupdatingCurrent))
    }()
    
    static let systemNSLocale : _NSSwiftLocale = {
        _NSSwiftLocale(Locale(inner: system))
    }()
#endif
    
    // MARK: -
    
    func fixed(_ id: String) -> any _LocaleProtocol {
        lock.withLock {
            $0.fixed(id)
        }
    }

#if FOUNDATION_FRAMEWORK
    func fixedNSLocale(identifier id: String) -> _NSSwiftLocale {
        lock.withLock { $0.fixedNSLocale(identifier: id) }
    }

#if canImport(_FoundationICU)
    func fixedNSLocale(_ locale: _LocaleICU) -> _NSSwiftLocale {
        lock.withLock { $0.fixedNSLocale(locale) }
    }
#endif

    func currentNSLocale() -> _NSSwiftLocale {
        if let result = _currentNSCache.withLock({ $0 }) {
            return result
        }
        
        // Create the current _NSSwiftLocale, based on the current Swift Locale.
        let nsLocale = _NSSwiftLocale(Locale(inner: current))
            
        // TODO: The current locale has an idea of not caching, which we have never honored here in the NSLocale cache
        return _currentNSCache.withLock {
            if let current = $0 {
                // Someone beat us to setting it, use that one
                return current
            } else {
                $0 = nsLocale
                return nsLocale
            }
        }
    }

#endif // FOUNDATION_FRAMEWORK

    func fixedComponents(_ comps: Locale.Components) -> any _LocaleProtocol {
        lock.withLock { $0.fixedComponentsWithCache(comps) }
    }
    
#if FOUNDATION_FRAMEWORK && !NO_CFPREFERENCES
    func preferences() -> (LocalePreferences, Bool) {
        // On Darwin, we check the current user preferences for Locale values
        var wouldDeadlock: DarwinBoolean = false
        let cfPrefs = __CFXPreferencesCopyCurrentApplicationStateWithDeadlockAvoidance(&wouldDeadlock).takeRetainedValue()

        var prefs = LocalePreferences()
        prefs.apply(cfPrefs)
        
        if wouldDeadlock.boolValue {
            // Don't cache a locale built with incomplete prefs
            return (prefs, false)
        } else {
            return (prefs, true)
        }
    }
    
    func preferredLanguages(forCurrentUser: Bool) -> [String] {
        var languages: [String] = []
        if forCurrentUser {
            languages = CFPreferencesCopyValue("AppleLanguages" as CFString, kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost) as? [String] ?? []
        } else {
            languages = CFPreferencesCopyAppValue("AppleLanguages" as CFString, kCFPreferencesCurrentApplication) as? [String] ?? []
        }
        
        return languages.compactMap {
            Locale.canonicalLanguageIdentifier(from: $0)
        }
    }
    
    func preferredLocale() -> String? {
        guard let preferredLocaleID = CFPreferencesCopyAppValue("AppleLocale" as CFString, kCFPreferencesCurrentApplication) as? String else {
            return nil
        }
        return preferredLocaleID
    }
#else
    func preferences() -> (LocalePreferences, Bool) {
        var prefs = LocalePreferences()
        prefs.locale = "en_001"
        prefs.languages = ["en-001"]
        return (prefs, true)
    }

    func preferredLanguages(forCurrentUser: Bool) -> [String] {
        [Locale.canonicalLanguageIdentifier(from: "en-001")]
    }
    
    func preferredLocale() -> String? {
        "en_001"
    }
#endif
    
#if FOUNDATION_FRAMEWORK && !NO_CFPREFERENCES
    /// This returns an instance of `Locale` that's set up exactly like it would be if the user changed the current locale to that identifier, set the preferences keys in the overrides dictionary, then called `current`.
    func localeAsIfCurrent(name: String?, cfOverrides: CFDictionary? = nil, disableBundleMatching: Bool = false) -> Locale {
        
        var (prefs, _) = preferences()
        if let cfOverrides { prefs.apply(cfOverrides) }
        
        let inner = _LocaleICU(name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
        return Locale(inner: inner)
    }
#endif
    
    /// This returns an instance of `Locale` that's set up exactly like it would be if the user changed the current locale to that identifier, set the preferences keys in the overrides dictionary, then called `current`.
    func localeAsIfCurrent(name: String?, overrides: LocalePreferences? = nil, disableBundleMatching: Bool = false) -> Locale {
        var (prefs, _) = preferences()
        if let overrides { prefs.apply(overrides) }
        
        let inner = _localeICUClass().init(name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
        return Locale(inner: inner)
    }

    func localeWithPreferences(identifier: String, prefs: LocalePreferences?) -> Locale {
        if let prefs {
            let inner = _localeICUClass().init(identifier: identifier, prefs: prefs)
            return Locale(inner: inner)
        } else {
            return Locale(inner: LocaleCache.cache.fixed(identifier))
        }
    }

    func localeAsIfCurrentWithBundleLocalizations(_ availableLocalizations: [String], allowsMixedLocalizations: Bool) -> Locale? {
#if FOUNDATION_FRAMEWORK && canImport(_FoundationICU)
        guard !allowsMixedLocalizations else {
            let (prefs, _) = preferences()
            let inner = _LocaleICU(name: nil, prefs: prefs, disableBundleMatching: true)
            return Locale(inner: inner)
        }

        let preferredLanguages = preferredLanguages(forCurrentUser: false)
        guard let preferredLocaleID = preferredLocale() else { return nil }
        
        let canonicalizedLocalizations = availableLocalizations.compactMap { Locale.canonicalLanguageIdentifier(from: $0) }
        let identifier = Locale.localeIdentifierForCanonicalizedLocalizations(canonicalizedLocalizations, preferredLanguages: preferredLanguages, preferredLocaleID: preferredLocaleID)
        guard let identifier else {
            return nil
        }

        let (prefs, _) = preferences()
        let inner = _LocaleICU(identifier: identifier, prefs: prefs)
        return Locale(inner: inner)
#else
        // No way to canonicalize on this platform
        return nil
#endif
    }
}