@@ -34,30 +34,30 @@ dynamic package func _localeICUClass() -> _LocaleProtocol.Type {
3434/// Singleton which listens for notifications about preference changes for Locale and holds cached singletons.
3535struct LocaleCache : Sendable , ~ Copyable {
3636 // MARK: - State
37-
37+
3838 struct State {
39-
39+
4040 init ( ) {
4141#if FOUNDATION_FRAMEWORK
4242 // For Foundation.framework, we listen for system notifications about the system Locale changing from the Darwin notification center.
4343 _CFNotificationCenterInitializeDependentNotificationIfNecessary ( CFNotificationName . cfLocaleCurrentLocaleDidChange!. rawValue)
4444#endif
4545 }
46-
46+
4747 private var cachedFixedLocales : [ String : any _LocaleProtocol ] = [ : ]
4848 private var cachedFixedComponentsLocales : [ Locale . Components : any _LocaleProtocol ] = [ : ]
4949
5050#if FOUNDATION_FRAMEWORK
5151 private var cachedFixedIdentifierToNSLocales : [ String : _NSSwiftLocale ] = [ : ]
52-
52+
5353 struct IdentifierAndPrefs : Hashable {
5454 let identifier : String
5555 let prefs : LocalePreferences ?
5656 }
57-
57+
5858 private var cachedFixedLocaleToNSLocales : [ IdentifierAndPrefs : _NSSwiftLocale ] = [ : ]
5959#endif
60-
60+
6161 mutating func fixed( _ id: String ) -> any _LocaleProtocol {
6262 // Note: Even if the currentLocale's identifier is the same, currentLocale may have preference overrides which are not reflected in the identifier itself.
6363 if let locale = cachedFixedLocales [ id] {
@@ -81,7 +81,7 @@ struct LocaleCache : Sendable, ~Copyable {
8181 return locale
8282 }
8383 }
84-
84+
8585#if canImport(_FoundationICU)
8686 mutating func fixedNSLocale( _ locale: _LocaleICU ) -> _NSSwiftLocale {
8787 let id = IdentifierAndPrefs ( identifier: locale. identifier, prefs: locale. prefs)
@@ -102,33 +102,33 @@ struct LocaleCache : Sendable, ~Copyable {
102102 func fixedComponents( _ comps: Locale . Components ) -> ( any _LocaleProtocol ) ? {
103103 cachedFixedComponentsLocales [ comps]
104104 }
105-
105+
106106 mutating func fixedComponentsWithCache( _ comps: Locale . Components ) -> any _LocaleProtocol {
107107 if let l = fixedComponents ( comps) {
108108 return l
109109 } else {
110110 let new = _localeICUClass ( ) . init ( components: comps)
111-
111+
112112 cachedFixedComponentsLocales [ comps] = new
113113 return new
114114 }
115115 }
116116 }
117117
118118 let lock : LockedState < State >
119-
119+
120120 static let cache = LocaleCache ( )
121121 private let _currentCache = LockedState < ( any _LocaleProtocol ) ? > ( initialState: nil )
122-
122+
123123#if FOUNDATION_FRAMEWORK
124124 private var _currentNSCache = LockedState < _NSSwiftLocale ? > ( initialState: nil )
125125#endif
126-
126+
127127 fileprivate init ( ) {
128128 lock = LockedState ( initialState: State ( ) )
129129 }
130130
131-
131+
132132 /// 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.
133133 /// This mutates global state of the current locale, so it is not safe to use in concurrent testing.
134134 func resetCurrent( to preferences: LocalePreferences ) {
@@ -150,32 +150,36 @@ struct LocaleCache : Sendable, ~Copyable {
150150 }
151151
152152 var current : any _LocaleProtocol {
153+ return _currentAndCache. locale
154+ }
155+
156+ fileprivate var _currentAndCache : ( locale: any _LocaleProtocol , doCache: Bool ) {
153157 if let result = _currentCache. withLock ( { $0 } ) {
154- return result
158+ return ( result, true )
155159 }
156-
160+
157161 // We need to fetch prefs and try again
158162 let ( preferences, doCache) = preferences ( )
159163 let locale = _localeICUClass ( ) . init ( name: nil , prefs: preferences, disableBundleMatching: false )
160-
164+
161165 // It's possible this was an 'incomplete locale', in which case we will want to calculate it again later.
162166 if doCache {
163167 return _currentCache. withLock {
164168 if let current = $0 {
165169 // Someone beat us to setting it - use existing one
166- return current
170+ return ( current, true )
167171 } else {
168172 $0 = locale
169- return locale
173+ return ( locale, true )
170174 }
171175 }
176+ } else {
177+ return ( locale, false )
172178 }
173-
174- return locale
175179 }
176-
180+
177181 // MARK: Singletons
178-
182+
179183 // This value is immutable, so we can share one instance for the whole process.
180184 static let unlocalized = _LocaleUnlocalized ( identifier: " en_001 " )
181185
@@ -185,19 +189,19 @@ struct LocaleCache : Sendable, ~Copyable {
185189 static let system : any _LocaleProtocol = {
186190 _localeICUClass ( ) . init ( identifier: " " , prefs: nil )
187191 } ( )
188-
192+
189193#if FOUNDATION_FRAMEWORK
190194 static let autoupdatingCurrentNSLocale : _NSSwiftLocale = {
191195 _NSSwiftLocale ( Locale ( inner: autoupdatingCurrent) )
192196 } ( )
193-
197+
194198 static let systemNSLocale : _NSSwiftLocale = {
195199 _NSSwiftLocale ( Locale ( inner: system) )
196200 } ( )
197201#endif
198-
202+
199203 // MARK: -
200-
204+
201205 func fixed( _ id: String ) -> any _LocaleProtocol {
202206 lock. withLock {
203207 $0. fixed ( id)
@@ -219,19 +223,25 @@ struct LocaleCache : Sendable, ~Copyable {
219223 if let result = _currentNSCache. withLock ( { $0 } ) {
220224 return result
221225 }
222-
226+
223227 // 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
224231 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+ }
234242 }
243+ } else {
244+ return nsLocale
235245 }
236246 }
237247
@@ -240,7 +250,7 @@ struct LocaleCache : Sendable, ~Copyable {
240250 func fixedComponents( _ comps: Locale . Components ) -> any _LocaleProtocol {
241251 lock. withLock { $0. fixedComponentsWithCache ( comps) }
242252 }
243-
253+
244254#if FOUNDATION_FRAMEWORK && !NO_CFPREFERENCES
245255 func preferences( ) -> ( LocalePreferences , Bool ) {
246256 // On Darwin, we check the current user preferences for Locale values
@@ -249,28 +259,28 @@ struct LocaleCache : Sendable, ~Copyable {
249259
250260 var prefs = LocalePreferences ( )
251261 prefs. apply ( cfPrefs)
252-
262+
253263 if wouldDeadlock. boolValue {
254264 // Don't cache a locale built with incomplete prefs
255265 return ( prefs, false )
256266 } else {
257267 return ( prefs, true )
258268 }
259269 }
260-
270+
261271 func preferredLanguages( forCurrentUser: Bool ) -> [ String ] {
262272 var languages : [ String ] = [ ]
263273 if forCurrentUser {
264274 languages = CFPreferencesCopyValue ( " AppleLanguages " as CFString , kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost) as? [ String ] ?? [ ]
265275 } else {
266276 languages = CFPreferencesCopyAppValue ( " AppleLanguages " as CFString , kCFPreferencesCurrentApplication) as? [ String ] ?? [ ]
267277 }
268-
278+
269279 return languages. compactMap {
270280 Locale . canonicalLanguageIdentifier ( from: $0)
271281 }
272282 }
273-
283+
274284 func preferredLocale( ) -> String ? {
275285 guard let preferredLocaleID = CFPreferencesCopyAppValue ( " AppleLocale " as CFString , kCFPreferencesCurrentApplication) as? String else {
276286 return nil
@@ -288,29 +298,29 @@ struct LocaleCache : Sendable, ~Copyable {
288298 func preferredLanguages( forCurrentUser: Bool ) -> [ String ] {
289299 [ Locale . canonicalLanguageIdentifier ( from: " en-001 " ) ]
290300 }
291-
301+
292302 func preferredLocale( ) -> String ? {
293303 " en_001 "
294304 }
295305#endif
296-
306+
297307#if FOUNDATION_FRAMEWORK && !NO_CFPREFERENCES
298308 /// 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`.
299309 func localeAsIfCurrent( name: String ? , cfOverrides: CFDictionary ? = nil , disableBundleMatching: Bool = false ) -> Locale {
300-
310+
301311 var ( prefs, _) = preferences ( )
302312 if let cfOverrides { prefs. apply ( cfOverrides) }
303-
313+
304314 let inner = _LocaleICU ( name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
305315 return Locale ( inner: inner)
306316 }
307317#endif
308-
318+
309319 /// 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`.
310320 func localeAsIfCurrent( name: String ? , overrides: LocalePreferences ? = nil , disableBundleMatching: Bool = false ) -> Locale {
311321 var ( prefs, _) = preferences ( )
312322 if let overrides { prefs. apply ( overrides) }
313-
323+
314324 let inner = _localeICUClass ( ) . init ( name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
315325 return Locale ( inner: inner)
316326 }
@@ -334,7 +344,7 @@ struct LocaleCache : Sendable, ~Copyable {
334344
335345 let preferredLanguages = preferredLanguages ( forCurrentUser: false )
336346 guard let preferredLocaleID = preferredLocale ( ) else { return nil }
337-
347+
338348 let canonicalizedLocalizations = availableLocalizations. compactMap { Locale . canonicalLanguageIdentifier ( from: $0) }
339349 let identifier = Locale . localeIdentifierForCanonicalizedLocalizations ( canonicalizedLocalizations, preferredLanguages: preferredLanguages, preferredLocaleID: preferredLocaleID)
340350 guard let identifier else {
0 commit comments