@@ -60,18 +60,26 @@ struct LocaleCache : Sendable {
6060 }
6161 }
6262
63- mutating func current( ) -> _Locale {
63+ mutating func current( preferences : LocalePreferences ? , cache : Bool ) -> _Locale ? {
6464 resetCurrentIfNeeded ( )
6565
6666 if let cachedCurrentLocale {
6767 return cachedCurrentLocale
68- } else {
69- let ( locale, doCache) = _Locale. _currentLocaleWithOverrides ( name: nil , overrides: nil , disableBundleMatching: false )
70- if doCache {
71- self . cachedCurrentLocale = locale
72- }
73- return locale
7468 }
69+
70+ // At this point we know we need to create, or re-create, the Locale instance.
71+ // If we do not have a set of preferences to use, we have to return nil.
72+ guard let preferences else {
73+ return nil
74+ }
75+
76+ let locale = _Locale ( name: nil , prefs: preferences, disableBundleMatching: false )
77+ if cache {
78+ // It's possible this was an 'incomplete locale', in which case we will want to calculate it again later.
79+ self . cachedCurrentLocale = locale
80+ }
81+
82+ return locale
7583 }
7684
7785 mutating func fixed( _ id: String ) -> _Locale {
@@ -98,7 +106,7 @@ struct LocaleCache : Sendable {
98106 }
99107 }
100108
101- mutating func currentNSLocale( ) -> _NSSwiftLocale {
109+ mutating func currentNSLocale( preferences : LocalePreferences ? , cache : Bool ) -> _NSSwiftLocale ? {
102110 resetCurrentIfNeeded ( )
103111
104112 if let currentNSLocale = cachedCurrentNSLocale {
@@ -108,17 +116,25 @@ struct LocaleCache : Sendable {
108116 let nsLocale = _NSSwiftLocale ( Locale ( inner: current) )
109117 cachedCurrentNSLocale = nsLocale
110118 return nsLocale
111- } else {
112- // We have neither a Swift Locale nor an NSLocale. Recalculate and set both.
113- let ( locale, doCache) = _Locale. _currentLocaleWithOverrides ( name: nil , overrides: nil , disableBundleMatching: false )
114- let nsLocale = _NSSwiftLocale ( Locale ( inner: locale) )
115- if doCache {
116- // It's possible this was an 'incomplete locale', in which case we will want to calculate it again later.
117- self . cachedCurrentLocale = locale
118- cachedCurrentNSLocale = nsLocale
119- }
120- return nsLocale
121119 }
120+
121+ // At this point we know we need to create, or re-create, the Locale instance.
122+
123+ // If we do not have a set of preferences to use, we have to return nil.
124+ guard let preferences else {
125+ return nil
126+ }
127+
128+ // We have neither a Swift Locale nor an NSLocale. Recalculate and set both.
129+ let locale = _Locale ( name: nil , prefs: preferences, disableBundleMatching: false )
130+ let nsLocale = _NSSwiftLocale ( Locale ( inner: locale) )
131+ if cache {
132+ // It's possible this was an 'incomplete locale', in which case we will want to calculate it again later.
133+ self . cachedCurrentLocale = locale
134+ cachedCurrentNSLocale = nsLocale
135+ }
136+
137+ return nsLocale
122138 }
123139
124140 mutating func autoupdatingNSLocale( ) -> _NSSwiftLocale {
@@ -179,18 +195,30 @@ struct LocaleCache : Sendable {
179195 }
180196
181197 var current : _Locale {
182- lock. withLock { $0. current ( ) }
198+ var result = lock. withLock {
199+ $0. current ( preferences: nil , cache: false )
200+ }
201+
202+ if let result { return result }
203+
204+ // We need to fetch prefs and try again
205+ let ( prefs, doCache) = preferences ( )
206+
207+ result = lock. withLock {
208+ $0. current ( preferences: prefs, cache: doCache)
209+ }
210+
211+ guard let result else {
212+ fatalError ( " Nil result getting current Locale with preferences " )
213+ }
214+
215+ return result
183216 }
184217
185218 var system : _Locale {
186219 lock. withLock { $0. system ( ) }
187220 }
188221
189- var preferred : _Locale {
190- let ( locale, _) = _Locale. _currentLocaleWithOverrides ( name: nil , overrides: nil , disableBundleMatching: false )
191- return locale
192- }
193-
194222 func fixed( _ id: String ) -> _Locale {
195223 lock. withLock { $0. fixed ( id) }
196224 }
@@ -205,7 +233,24 @@ struct LocaleCache : Sendable {
205233 }
206234
207235 func currentNSLocale( ) -> _NSSwiftLocale {
208- lock. withLock { $0. currentNSLocale ( ) }
236+ var result = lock. withLock {
237+ $0. currentNSLocale ( preferences: nil , cache: false )
238+ }
239+
240+ if let result { return result }
241+
242+ // We need to fetch prefs and try again. Don't do this inside a lock (106190030). On Darwin it is possible to get a KVO callout from fetching the preferences, which could ask for the current Locale, which could cause a reentrant lock.
243+ let ( prefs, doCache) = preferences ( )
244+
245+ result = lock. withLock {
246+ $0. currentNSLocale ( preferences: prefs, cache: doCache)
247+ }
248+
249+ guard let result else {
250+ fatalError ( " Nil result getting current NSLocale with preferences " )
251+ }
252+
253+ return result
209254 }
210255
211256 func systemNSLocale( ) -> _NSSwiftLocale {
@@ -216,4 +261,99 @@ struct LocaleCache : Sendable {
216261 func fixedComponents( _ comps: Locale . Components ) -> _Locale {
217262 lock. withLock { $0. fixedComponents ( comps) }
218263 }
264+
265+ #if FOUNDATION_FRAMEWORK
266+ func preferences( ) -> ( LocalePreferences , Bool ) {
267+ // On Darwin, we check the current user preferences for Locale values
268+ var wouldDeadlock : DarwinBoolean = false
269+ let cfPrefs = __CFXPreferencesCopyCurrentApplicationStateWithDeadlockAvoidance ( & wouldDeadlock) . takeRetainedValue ( )
270+
271+ var prefs = LocalePreferences ( )
272+ prefs. apply ( cfPrefs)
273+
274+ if wouldDeadlock. boolValue {
275+ // Don't cache a locale built with incomplete prefs
276+ return ( prefs, false )
277+ } else {
278+ return ( prefs, true )
279+ }
280+ }
281+
282+ func preferredLanguages( forCurrentUser: Bool ) -> [ String ] {
283+ var languages : [ String ] = [ ]
284+ if forCurrentUser {
285+ languages = CFPreferencesCopyValue ( " AppleLanguages " as CFString , kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost) as? [ String ] ?? [ ]
286+ } else {
287+ languages = CFPreferencesCopyAppValue ( " AppleLanguages " as CFString , kCFPreferencesCurrentApplication) as? [ String ] ?? [ ]
288+ }
289+
290+ return languages. compactMap {
291+ Locale . canonicalLanguageIdentifier ( from: $0)
292+ }
293+ }
294+
295+ func preferredLocale( ) -> String ? {
296+ guard let preferredLocaleID = CFPreferencesCopyAppValue ( " AppleLocale " as CFString , kCFPreferencesCurrentApplication) as? String else {
297+ return nil
298+ }
299+ return preferredLocaleID
300+ }
301+ #else
302+ func preferences( ) -> ( LocalePreferences , Bool ) {
303+ var prefs = LocalePreferences ( )
304+ prefs. locale = " en_US "
305+ prefs. languages = [ " en-US " ]
306+ return ( prefs, true )
307+ }
308+
309+ func preferredLanguages( forCurrentUser: Bool ) -> [ String ] {
310+ [ Locale . canonicalLanguageIdentifier ( from: " en-US " ) ]
311+ }
312+
313+ func preferredLocale( ) -> String ? {
314+ " en_US "
315+ }
316+ #endif
317+
318+ #if FOUNDATION_FRAMEWORK
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`.
320+ func localeAsIfCurrent( name: String ? , cfOverrides: CFDictionary ? = nil , disableBundleMatching: Bool = false ) -> Locale {
321+
322+ var ( prefs, _) = preferences ( )
323+ if let cfOverrides { prefs. apply ( cfOverrides) }
324+
325+ let inner = _Locale ( name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
326+ return Locale ( . fixed( inner) )
327+ }
328+ #endif
329+
330+ /// 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`.
331+ func localeAsIfCurrent( name: String ? , overrides: LocalePreferences ? = nil , disableBundleMatching: Bool = false ) -> Locale {
332+ var ( prefs, _) = preferences ( )
333+ if let overrides { prefs. apply ( overrides) }
334+
335+ let inner = _Locale ( name: name, prefs: prefs, disableBundleMatching: disableBundleMatching)
336+ return Locale ( . fixed( inner) )
337+ }
338+
339+
340+ func localeAsIfCurrentWithBundleLocalizations( _ availableLocalizations: [ String ] , allowsMixedLocalizations: Bool ) -> Locale ? {
341+ guard !allowsMixedLocalizations else {
342+ let ( prefs, _) = preferences ( )
343+ let inner = _Locale ( name: nil , prefs: prefs, disableBundleMatching: true )
344+ return Locale ( . fixed( inner) )
345+ }
346+
347+ let preferredLanguages = preferredLanguages ( forCurrentUser: false )
348+ guard let preferredLocaleID = preferredLocale ( ) else { return nil }
349+
350+ let canonicalizedLocalizations = availableLocalizations. compactMap { Locale . canonicalLanguageIdentifier ( from: $0) }
351+ let identifier = _Locale. localeIdentifierForCanonicalizedLocalizations ( canonicalizedLocalizations, preferredLanguages: preferredLanguages, preferredLocaleID: preferredLocaleID)
352+ guard let identifier else {
353+ return nil
354+ }
355+
356+ let inner = _Locale ( identifier: identifier)
357+ return Locale ( . fixed( inner) )
358+ }
219359}
0 commit comments