@@ -34,30 +34,30 @@ dynamic package func _localeICUClass() -> _LocaleProtocol.Type {
34
34
/// Singleton which listens for notifications about preference changes for Locale and holds cached singletons.
35
35
struct LocaleCache : Sendable , ~ Copyable {
36
36
// MARK: - State
37
-
37
+
38
38
struct State {
39
-
39
+
40
40
init ( ) {
41
41
#if FOUNDATION_FRAMEWORK
42
42
// For Foundation.framework, we listen for system notifications about the system Locale changing from the Darwin notification center.
43
43
_CFNotificationCenterInitializeDependentNotificationIfNecessary ( CFNotificationName . cfLocaleCurrentLocaleDidChange!. rawValue)
44
44
#endif
45
45
}
46
-
46
+
47
47
private var cachedFixedLocales : [ String : any _LocaleProtocol ] = [ : ]
48
48
private var cachedFixedComponentsLocales : [ Locale . Components : any _LocaleProtocol ] = [ : ]
49
49
50
50
#if FOUNDATION_FRAMEWORK
51
51
private var cachedFixedIdentifierToNSLocales : [ String : _NSSwiftLocale ] = [ : ]
52
-
52
+
53
53
struct IdentifierAndPrefs : Hashable {
54
54
let identifier : String
55
55
let prefs : LocalePreferences ?
56
56
}
57
-
57
+
58
58
private var cachedFixedLocaleToNSLocales : [ IdentifierAndPrefs : _NSSwiftLocale ] = [ : ]
59
59
#endif
60
-
60
+
61
61
mutating func fixed( _ id: String ) -> any _LocaleProtocol {
62
62
// Note: Even if the currentLocale's identifier is the same, currentLocale may have preference overrides which are not reflected in the identifier itself.
63
63
if let locale = cachedFixedLocales [ id] {
@@ -81,7 +81,7 @@ struct LocaleCache : Sendable, ~Copyable {
81
81
return locale
82
82
}
83
83
}
84
-
84
+
85
85
#if canImport(_FoundationICU)
86
86
mutating func fixedNSLocale( _ locale: _LocaleICU ) -> _NSSwiftLocale {
87
87
let id = IdentifierAndPrefs ( identifier: locale. identifier, prefs: locale. prefs)
@@ -102,33 +102,33 @@ struct LocaleCache : Sendable, ~Copyable {
102
102
func fixedComponents( _ comps: Locale . Components ) -> ( any _LocaleProtocol ) ? {
103
103
cachedFixedComponentsLocales [ comps]
104
104
}
105
-
105
+
106
106
mutating func fixedComponentsWithCache( _ comps: Locale . Components ) -> any _LocaleProtocol {
107
107
if let l = fixedComponents ( comps) {
108
108
return l
109
109
} else {
110
110
let new = _localeICUClass ( ) . init ( components: comps)
111
-
111
+
112
112
cachedFixedComponentsLocales [ comps] = new
113
113
return new
114
114
}
115
115
}
116
116
}
117
117
118
118
let lock : LockedState < State >
119
-
119
+
120
120
static let cache = LocaleCache ( )
121
121
private let _currentCache = LockedState < ( any _LocaleProtocol ) ? > ( initialState: nil )
122
-
122
+
123
123
#if FOUNDATION_FRAMEWORK
124
124
private var _currentNSCache = LockedState < _NSSwiftLocale ? > ( initialState: nil )
125
125
#endif
126
-
126
+
127
127
fileprivate init ( ) {
128
128
lock = LockedState ( initialState: State ( ) )
129
129
}
130
130
131
-
131
+
132
132
/// 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.
133
133
/// This mutates global state of the current locale, so it is not safe to use in concurrent testing.
134
134
func resetCurrent( to preferences: LocalePreferences ) {
@@ -150,32 +150,36 @@ struct LocaleCache : Sendable, ~Copyable {
150
150
}
151
151
152
152
var current : any _LocaleProtocol {
153
+ return _currentAndCache. locale
154
+ }
155
+
156
+ fileprivate var _currentAndCache : ( locale: any _LocaleProtocol , doCache: Bool ) {
153
157
if let result = _currentCache. withLock ( { $0 } ) {
154
- return result
158
+ return ( result, true )
155
159
}
156
-
160
+
157
161
// We need to fetch prefs and try again
158
162
let ( preferences, doCache) = preferences ( )
159
163
let locale = _localeICUClass ( ) . init ( name: nil , prefs: preferences, disableBundleMatching: false )
160
-
164
+
161
165
// It's possible this was an 'incomplete locale', in which case we will want to calculate it again later.
162
166
if doCache {
163
167
return _currentCache. withLock {
164
168
if let current = $0 {
165
169
// Someone beat us to setting it - use existing one
166
- return current
170
+ return ( current, true )
167
171
} else {
168
172
$0 = locale
169
- return locale
173
+ return ( locale, true )
170
174
}
171
175
}
176
+ } else {
177
+ return ( locale, false )
172
178
}
173
-
174
- return locale
175
179
}
176
-
180
+
177
181
// MARK: Singletons
178
-
182
+
179
183
// This value is immutable, so we can share one instance for the whole process.
180
184
static let unlocalized = _LocaleUnlocalized ( identifier: " en_001 " )
181
185
@@ -185,19 +189,19 @@ struct LocaleCache : Sendable, ~Copyable {
185
189
static let system : any _LocaleProtocol = {
186
190
_localeICUClass ( ) . init ( identifier: " " , prefs: nil )
187
191
} ( )
188
-
192
+
189
193
#if FOUNDATION_FRAMEWORK
190
194
static let autoupdatingCurrentNSLocale : _NSSwiftLocale = {
191
195
_NSSwiftLocale ( Locale ( inner: autoupdatingCurrent) )
192
196
} ( )
193
-
197
+
194
198
static let systemNSLocale : _NSSwiftLocale = {
195
199
_NSSwiftLocale ( Locale ( inner: system) )
196
200
} ( )
197
201
#endif
198
-
202
+
199
203
// MARK: -
200
-
204
+
201
205
func fixed( _ id: String ) -> any _LocaleProtocol {
202
206
lock. withLock {
203
207
$0. fixed ( id)
@@ -219,19 +223,25 @@ struct LocaleCache : Sendable, ~Copyable {
219
223
if let result = _currentNSCache. withLock ( { $0 } ) {
220
224
return result
221
225
}
222
-
226
+
223
227
// Create the current _NSSwiftLocale, based on the current Swift Locale.
228
+ // n.b. do not call just `current` here; instead, use `_currentAndCache`
229
+ // so that the caching status is honored
230
+ let ( current, doCache) = _currentAndCache
224
231
let nsLocale = _NSSwiftLocale ( Locale ( inner: current) )
225
-
226
- // TODO: The current locale has an idea of not caching, which we have never honored here in the NSLocale cache
227
- return _currentNSCache. withLock {
228
- if let current = $0 {
229
- // Someone beat us to setting it, use that one
230
- return current
231
- } else {
232
- $0 = nsLocale
233
- return nsLocale
232
+
233
+ if doCache {
234
+ return _currentNSCache. withLock {
235
+ if let current = $0 {
236
+ // Someone beat us to setting it, use that one
237
+ return current
238
+ } else {
239
+ $0 = nsLocale
240
+ return nsLocale
241
+ }
234
242
}
243
+ } else {
244
+ return nsLocale
235
245
}
236
246
}
237
247
@@ -240,7 +250,7 @@ struct LocaleCache : Sendable, ~Copyable {
240
250
func fixedComponents( _ comps: Locale . Components ) -> any _LocaleProtocol {
241
251
lock. withLock { $0. fixedComponentsWithCache ( comps) }
242
252
}
243
-
253
+
244
254
#if FOUNDATION_FRAMEWORK && !NO_CFPREFERENCES
245
255
func preferences( ) -> ( LocalePreferences , Bool ) {
246
256
// On Darwin, we check the current user preferences for Locale values
@@ -249,28 +259,28 @@ struct LocaleCache : Sendable, ~Copyable {
249
259
250
260
var prefs = LocalePreferences ( )
251
261
prefs. apply ( cfPrefs)
252
-
262
+
253
263
if wouldDeadlock. boolValue {
254
264
// Don't cache a locale built with incomplete prefs
255
265
return ( prefs, false )
256
266
} else {
257
267
return ( prefs, true )
258
268
}
259
269
}
260
-
270
+
261
271
func preferredLanguages( forCurrentUser: Bool ) -> [ String ] {
262
272
var languages : [ String ] = [ ]
263
273
if forCurrentUser {
264
274
languages = CFPreferencesCopyValue ( " AppleLanguages " as CFString , kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost) as? [ String ] ?? [ ]
265
275
} else {
266
276
languages = CFPreferencesCopyAppValue ( " AppleLanguages " as CFString , kCFPreferencesCurrentApplication) as? [ String ] ?? [ ]
267
277
}
268
-
278
+
269
279
return languages. compactMap {
270
280
Locale . canonicalLanguageIdentifier ( from: $0)
271
281
}
272
282
}
273
-
283
+
274
284
func preferredLocale( ) -> String ? {
275
285
guard let preferredLocaleID = CFPreferencesCopyAppValue ( " AppleLocale " as CFString , kCFPreferencesCurrentApplication) as? String else {
276
286
return nil
@@ -288,29 +298,29 @@ struct LocaleCache : Sendable, ~Copyable {
288
298
func preferredLanguages( forCurrentUser: Bool ) -> [ String ] {
289
299
[ Locale . canonicalLanguageIdentifier ( from: " en-001 " ) ]
290
300
}
291
-
301
+
292
302
func preferredLocale( ) -> String ? {
293
303
" en_001 "
294
304
}
295
305
#endif
296
-
306
+
297
307
#if FOUNDATION_FRAMEWORK && !NO_CFPREFERENCES
298
308
/// 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`.
299
309
func localeAsIfCurrent( name: String ? , cfOverrides: CFDictionary ? = nil , disableBundleMatching: Bool = false ) -> Locale {
300
-
310
+
301
311
var ( prefs, _) = preferences ( )
302
312
if let cfOverrides { prefs. apply ( cfOverrides) }
303
-
313
+
304
314
let inner = _LocaleICU ( name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
305
315
return Locale ( inner: inner)
306
316
}
307
317
#endif
308
-
318
+
309
319
/// 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`.
310
320
func localeAsIfCurrent( name: String ? , overrides: LocalePreferences ? = nil , disableBundleMatching: Bool = false ) -> Locale {
311
321
var ( prefs, _) = preferences ( )
312
322
if let overrides { prefs. apply ( overrides) }
313
-
323
+
314
324
let inner = _localeICUClass ( ) . init ( name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
315
325
return Locale ( inner: inner)
316
326
}
@@ -334,7 +344,7 @@ struct LocaleCache : Sendable, ~Copyable {
334
344
335
345
let preferredLanguages = preferredLanguages ( forCurrentUser: false )
336
346
guard let preferredLocaleID = preferredLocale ( ) else { return nil }
337
-
347
+
338
348
let canonicalizedLocalizations = availableLocalizations. compactMap { Locale . canonicalLanguageIdentifier ( from: $0) }
339
349
let identifier = Locale . localeIdentifierForCanonicalizedLocalizations ( canonicalizedLocalizations, preferredLanguages: preferredLanguages, preferredLocaleID: preferredLocaleID)
340
350
guard let identifier else {
0 commit comments