2 * Copyright (c) 2015 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 Copyright (c) 1999-2014, Apple Inc. All rights reserved.
26 Responsibility: Tony Parker
29 #include "CFBundle_Internal.h"
31 #include <CoreFoundation/CFPreferences.h>
33 #include <unicode/ualoc.h>
36 static CFStringRef
_CFBundleCopyLanguageFoundInLocalizations(CFArrayRef localizations
, CFStringRef language
);
39 #pragma mark Mixed Localizations
41 // This helper function checks for various permutations of the ways people put boolean values in Info.plist dictionaries
42 static Boolean
_CFBundleGetInfoDictionaryBoolean(CFStringRef key
) {
43 Boolean result
= false;
44 CFBundleRef mainBundle
= CFBundleGetMainBundle();
45 CFDictionaryRef infoDict
= mainBundle
? CFBundleGetInfoDictionary(mainBundle
) : NULL
;
46 CFTypeRef infoDictValue
= infoDict
? CFDictionaryGetValue(infoDict
, key
) : NULL
;
48 CFTypeID typeID
= CFGetTypeID(infoDictValue
);
49 if (typeID
== CFBooleanGetTypeID()) {
50 result
= CFBooleanGetValue((CFBooleanRef
)infoDictValue
);
51 } else if (typeID
== CFStringGetTypeID()) {
52 result
= (CFStringCompare((CFStringRef
)infoDictValue
, CFSTR("true"), kCFCompareCaseInsensitive
) == kCFCompareEqualTo
|| CFStringCompare((CFStringRef
)infoDictValue
, CFSTR("YES"), kCFCompareCaseInsensitive
) == kCFCompareEqualTo
);
53 } else if (typeID
== CFNumberGetTypeID()) {
55 if (CFNumberGetValue((CFNumberRef
)infoDictValue
, kCFNumberSInt32Type
, &val
)) result
= (val
!= 0);
61 CF_PRIVATE Boolean
CFBundleAllowMixedLocalizations(void) {
62 static Boolean allowMixed
= false;
63 static dispatch_once_t once
= 0;
64 dispatch_once(&once
, ^{
65 allowMixed
= _CFBundleGetInfoDictionaryBoolean(_kCFBundleAllowMixedLocalizationsKey
);
70 static Boolean
CFBundleFollowParentLocalization(void) {
71 static Boolean followParent
= false;
72 static dispatch_once_t once
= 0;
73 dispatch_once(&once
, ^{
74 followParent
= _CFBundleGetInfoDictionaryBoolean(CFSTR("CFBundleFollowParentLocalization"));
81 #pragma mark Language and Locale Codes
83 // string, with groups of 6 characters being 1 element in the array of locale abbreviations
84 const char * __CFBundleLocaleAbbreviationsArray
=
85 "en_US\0" "fr_FR\0" "en_GB\0" "de_DE\0" "it_IT\0" "nl_NL\0" "nl_BE\0" "sv_SE\0"
86 "es_ES\0" "da_DK\0" "pt_PT\0" "fr_CA\0" "nb_NO\0" "he_IL\0" "ja_JP\0" "en_AU\0"
87 "ar\0\0\0\0" "fi_FI\0" "fr_CH\0" "de_CH\0" "el_GR\0" "is_IS\0" "mt_MT\0" "el_CY\0"
88 "tr_TR\0" "hr_HR\0" "nl_NL\0" "nl_BE\0" "en_CA\0" "en_CA\0" "pt_PT\0" "nb_NO\0"
89 "da_DK\0" "hi_IN\0" "ur_PK\0" "tr_TR\0" "it_CH\0" "en\0\0\0\0" "\0\0\0\0\0\0" "ro_RO\0"
90 "grc\0\0\0" "lt_LT\0" "pl_PL\0" "hu_HU\0" "et_EE\0" "lv_LV\0" "se\0\0\0\0" "fo_FO\0"
91 "fa_IR\0" "ru_RU\0" "ga_IE\0" "ko_KR\0" "zh_CN\0" "zh_TW\0" "th_TH\0" "\0\0\0\0\0\0"
92 "cs_CZ\0" "sk_SK\0" "\0\0\0\0\0\0" "hu_HU\0" "bn\0\0\0\0" "be_BY\0" "uk_UA\0" "\0\0\0\0\0\0"
93 "el_GR\0" "sr_CS\0" "sl_SI\0" "mk_MK\0" "hr_HR\0" "\0\0\0\0\0\0" "de_DE\0" "pt_BR\0"
94 "bg_BG\0" "ca_ES\0" "\0\0\0\0\0\0" "gd\0\0\0\0" "gv\0\0\0\0" "br\0\0\0\0" "iu_CA\0" "cy\0\0\0\0"
95 "en_CA\0" "ga_IE\0" "en_CA\0" "dz_BT\0" "hy_AM\0" "ka_GE\0" "es_XL\0" "es_ES\0"
96 "to_TO\0" "pl_PL\0" "ca_ES\0" "fr\0\0\0\0" "de_AT\0" "es_XL\0" "gu_IN\0" "pa\0\0\0\0"
97 "ur_IN\0" "vi_VN\0" "fr_BE\0" "uz_UZ\0" "en_SG\0" "nn_NO\0" "af_ZA\0" "eo\0\0\0\0"
98 "mr_IN\0" "bo\0\0\0\0" "ne_NP\0" "kl\0\0\0\0" "en_IE\0";
100 #define NUM_LOCALE_ABBREVIATIONS 109
101 #define LOCALE_ABBREVIATION_LENGTH 6
103 static const char * const __CFBundleLanguageNamesArray
[] = {
104 "English", "French", "German", "Italian", "Dutch", "Swedish", "Spanish", "Danish",
105 "Portuguese", "Norwegian", "Hebrew", "Japanese", "Arabic", "Finnish", "Greek", "Icelandic",
106 "Maltese", "Turkish", "Croatian", "Chinese", "Urdu", "Hindi", "Thai", "Korean",
107 "Lithuanian", "Polish", "Hungarian", "Estonian", "Latvian", "Sami", "Faroese", "Farsi",
108 "Russian", "Chinese", "Dutch", "Irish", "Albanian", "Romanian", "Czech", "Slovak",
109 "Slovenian", "Yiddish", "Serbian", "Macedonian", "Bulgarian", "Ukrainian", "Byelorussian", "Uzbek",
110 "Kazakh", "Azerbaijani", "Azerbaijani", "Armenian", "Georgian", "Moldavian", "Kirghiz", "Tajiki",
111 "Turkmen", "Mongolian", "Mongolian", "Pashto", "Kurdish", "Kashmiri", "Sindhi", "Tibetan",
112 "Nepali", "Sanskrit", "Marathi", "Bengali", "Assamese", "Gujarati", "Punjabi", "Oriya",
113 "Malayalam", "Kannada", "Tamil", "Telugu", "Sinhalese", "Burmese", "Khmer", "Lao",
114 "Vietnamese", "Indonesian", "Tagalog", "Malay", "Malay", "Amharic", "Tigrinya", "Oromo",
115 "Somali", "Swahili", "Kinyarwanda", "Rundi", "Nyanja", "Malagasy", "Esperanto", "",
116 "", "", "", "", "", "", "", "",
117 "", "", "", "", "", "", "", "",
118 "", "", "", "", "", "", "", "",
119 "", "", "", "", "", "", "", "",
120 "Welsh", "Basque", "Catalan", "Latin", "Quechua", "Guarani", "Aymara", "Tatar",
121 "Uighur", "Dzongkha", "Javanese", "Sundanese", "Galician", "Afrikaans", "Breton", "Inuktitut",
122 "Scottish", "Manx", "Irish", "Tongan", "Greek", "Greenlandic", "Azerbaijani", "Nynorsk"
125 #define NUM_LANGUAGE_NAMES 152
126 #define LANGUAGE_NAME_LENGTH 13
128 // string, with groups of 3 characters being 1 element in the array of abbreviations
129 const char * __CFBundleLanguageAbbreviationsArray
=
130 "en\0" "fr\0" "de\0" "it\0" "nl\0" "sv\0" "es\0" "da\0"
131 "pt\0" "nb\0" "he\0" "ja\0" "ar\0" "fi\0" "el\0" "is\0"
132 "mt\0" "tr\0" "hr\0" "zh\0" "ur\0" "hi\0" "th\0" "ko\0"
133 "lt\0" "pl\0" "hu\0" "et\0" "lv\0" "se\0" "fo\0" "fa\0"
134 "ru\0" "zh\0" "nl\0" "ga\0" "sq\0" "ro\0" "cs\0" "sk\0"
135 "sl\0" "yi\0" "sr\0" "mk\0" "bg\0" "uk\0" "be\0" "uz\0"
136 "kk\0" "az\0" "az\0" "hy\0" "ka\0" "mo\0" "ky\0" "tg\0"
137 "tk\0" "mn\0" "mn\0" "ps\0" "ku\0" "ks\0" "sd\0" "bo\0"
138 "ne\0" "sa\0" "mr\0" "bn\0" "as\0" "gu\0" "pa\0" "or\0"
139 "ml\0" "kn\0" "ta\0" "te\0" "si\0" "my\0" "km\0" "lo\0"
140 "vi\0" "id\0" "tl\0" "ms\0" "ms\0" "am\0" "ti\0" "om\0"
141 "so\0" "sw\0" "rw\0" "rn\0" "\0\0\0" "mg\0" "eo\0" "\0\0\0"
142 "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0"
143 "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0"
144 "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0"
145 "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0" "\0\0\0"
146 "cy\0" "eu\0" "ca\0" "la\0" "qu\0" "gn\0" "ay\0" "tt\0"
147 "ug\0" "dz\0" "jv\0" "su\0" "gl\0" "af\0" "br\0" "iu\0"
148 "gd\0" "gv\0" "ga\0" "to\0" "el\0" "kl\0" "az\0" "nn\0";
150 #define NUM_LANGUAGE_ABBREVIATIONS 152
151 #define LANGUAGE_ABBREVIATION_LENGTH 3
153 static CFStringRef
_CFBundleGetAlternateNameForLanguage(CFStringRef language
) {
154 // These are not necessarily common localizations per se, but localizations for which the full language name is still in common use.
155 // These are used to provide a fast path for it (other localizations usually use the abbreviation, which is even faster).
156 static CFStringRef
const __CFBundleCommonLanguageNamesArray
[] = {CFSTR("English"), CFSTR("French"), CFSTR("German"), CFSTR("Italian"), CFSTR("Dutch"), CFSTR("Spanish"), CFSTR("Japanese")};
157 static CFStringRef
const __CFBundleCommonLanguageAbbreviationsArray
[] = {CFSTR("en"), CFSTR("fr"), CFSTR("de"), CFSTR("it"), CFSTR("nl"), CFSTR("es"), CFSTR("ja")};
159 for (CFIndex idx
= 0; idx
< sizeof(__CFBundleCommonLanguageNamesArray
) / sizeof(CFStringRef
); idx
++) {
160 if (CFEqual(language
, __CFBundleCommonLanguageAbbreviationsArray
[idx
])) {
161 return __CFBundleCommonLanguageNamesArray
[idx
];
162 } else if (CFEqual(language
, __CFBundleCommonLanguageNamesArray
[idx
])) {
163 return __CFBundleCommonLanguageAbbreviationsArray
[idx
];
170 static const SInt32 __CFBundleScriptCodesArray
[] = {
171 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1, 4, 0, 6, 0,
172 0, 0, 0, 2, 4, 9, 21, 3, 29, 29, 29, 29, 29, 0, 0, 4,
173 7, 25, 0, 0, 0, 0, 29, 29, 0, 5, 7, 7, 7, 7, 7, 7,
174 7, 7, 4, 24, 23, 7, 7, 7, 7, 27, 7, 4, 4, 4, 4, 26,
175 9, 9, 9, 13, 13, 11, 10, 12, 17, 16, 14, 15, 18, 19, 20, 22,
176 30, 0, 0, 0, 4, 28, 28, 28, 0, 0, 0, 0, 0, 0, 0, 0,
177 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
178 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
179 0, 0, 0, 0, 0, 0, 0, 7, 4, 26, 0, 0, 0, 0, 0, 28,
180 0, 0, 0, 0, 6, 0, 0, 0
183 static const CFStringEncoding __CFBundleStringEncodingsArray
[] = {
184 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1, 4, 0, 6, 37,
185 0, 35, 36, 2, 4, 9, 21, 3, 29, 29, 29, 29, 29, 0, 37, 0x8C,
186 7, 25, 0, 39, 0, 38, 29, 29, 36, 5, 7, 7, 7, 0x98, 7, 7,
187 7, 7, 4, 24, 23, 7, 7, 7, 7, 27, 7, 4, 4, 4, 4, 26,
188 9, 9, 9, 13, 13, 11, 10, 12, 17, 16, 14, 15, 18, 19, 20, 22,
189 30, 0, 0, 0, 4, 28, 28, 28, 0, 0, 0, 0, 0, 0, 0, 0,
190 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
191 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
192 39, 0, 0, 0, 0, 0, 0, 7, 4, 26, 0, 0, 0, 0, 39, 0xEC,
193 39, 39, 40, 0, 6, 0, 0, 0
196 static SInt32
_CFBundleGetLanguageCodeForLocalization(CFStringRef localizationName
) {
197 SInt32 result
= -1, i
;
199 CFIndex length
= CFStringGetLength(localizationName
);
200 if (length
>= LANGUAGE_ABBREVIATION_LENGTH
- 1 && length
<= 255 && CFStringGetCString(localizationName
, buff
, 255, kCFStringEncodingASCII
)) {
202 for (i
= 0; -1 == result
&& i
< NUM_LANGUAGE_NAMES
; i
++) {
203 if (0 == strcmp(buff
, __CFBundleLanguageNamesArray
[i
])) result
= i
;
205 if (0 == strcmp(buff
, "zh_TW") || 0 == strcmp(buff
, "zh-Hant")) result
= 19; else if (0 == strcmp(buff
, "zh_CN") || 0 == strcmp(buff
, "zh-Hans")) result
= 33; // hack for mixed-up Chinese language codes
206 if (-1 == result
&& (length
== LANGUAGE_ABBREVIATION_LENGTH
- 1 || !isalpha(buff
[LANGUAGE_ABBREVIATION_LENGTH
- 1]))) {
207 buff
[LANGUAGE_ABBREVIATION_LENGTH
- 1] = '\0';
208 if ('n' == buff
[0] && 'o' == buff
[1]) result
= 9; // hack for Norwegian
209 for (i
= 0; -1 == result
&& i
< NUM_LANGUAGE_ABBREVIATIONS
* LANGUAGE_ABBREVIATION_LENGTH
; i
+= LANGUAGE_ABBREVIATION_LENGTH
) {
210 if (buff
[0] == *(__CFBundleLanguageAbbreviationsArray
+ i
+ 0) && buff
[1] == *(__CFBundleLanguageAbbreviationsArray
+ i
+ 1)) result
= i
/ LANGUAGE_ABBREVIATION_LENGTH
;
217 static CFStringRef
_CFBundleCopyLanguageAbbreviationForLanguageCode(SInt32 languageCode
) {
218 CFStringRef result
= NULL
;
219 if (0 <= languageCode
&& languageCode
< NUM_LANGUAGE_ABBREVIATIONS
) {
220 const char *languageAbbreviation
= __CFBundleLanguageAbbreviationsArray
+ languageCode
* LANGUAGE_ABBREVIATION_LENGTH
;
221 if (languageAbbreviation
&& *languageAbbreviation
!= '\0') result
= CFStringCreateWithCStringNoCopy(kCFAllocatorSystemDefault
, languageAbbreviation
, kCFStringEncodingASCII
, kCFAllocatorNull
);
226 // Swaps - for _ and _ for - in a localization name
227 static CFStringRef
_CFBundleCopyModifiedLocalization(CFStringRef localizationName
) {
228 CFMutableStringRef result
= NULL
;
229 CFIndex length
= CFStringGetLength(localizationName
);
231 UniChar c
= CFStringGetCharacterAtIndex(localizationName
, 2);
232 if ('-' == c
|| '_' == c
) {
233 result
= CFStringCreateMutableCopy(kCFAllocatorSystemDefault
, length
, localizationName
);
234 CFStringReplace(result
, CFRangeMake(2, 1), ('-' == c
) ? CFSTR("_") : CFSTR("-"));
240 static SInt32
_CFBundleGetLanguageCodeForRegionCode(SInt32 regionCode
) {
241 SInt32 result
= -1, i
;
242 if (52 == regionCode
) { // hack for mixed-up Chinese language codes
244 } else if (0 <= regionCode
&& regionCode
< NUM_LOCALE_ABBREVIATIONS
) {
245 const char *localeAbbreviation
= __CFBundleLocaleAbbreviationsArray
+ regionCode
* LOCALE_ABBREVIATION_LENGTH
;
246 if (localeAbbreviation
&& *localeAbbreviation
!= '\0') {
247 for (i
= 0; -1 == result
&& i
< NUM_LANGUAGE_ABBREVIATIONS
* LANGUAGE_ABBREVIATION_LENGTH
; i
+= LANGUAGE_ABBREVIATION_LENGTH
) {
248 if (localeAbbreviation
[0] == *(__CFBundleLanguageAbbreviationsArray
+ i
+ 0) && localeAbbreviation
[1] == *(__CFBundleLanguageAbbreviationsArray
+ i
+ 1)) result
= i
/ LANGUAGE_ABBREVIATION_LENGTH
;
255 static SInt32
_CFBundleGetRegionCodeForLanguageCode(SInt32 languageCode
) {
256 SInt32 result
= -1, i
;
257 if (19 == languageCode
) { // hack for mixed-up Chinese language codes
259 } else if (0 <= languageCode
&& languageCode
< NUM_LANGUAGE_ABBREVIATIONS
) {
260 const char *languageAbbreviation
= __CFBundleLanguageAbbreviationsArray
+ languageCode
* LANGUAGE_ABBREVIATION_LENGTH
;
261 if (languageAbbreviation
&& *languageAbbreviation
!= '\0') {
262 for (i
= 0; -1 == result
&& i
< NUM_LOCALE_ABBREVIATIONS
* LOCALE_ABBREVIATION_LENGTH
; i
+= LOCALE_ABBREVIATION_LENGTH
) {
263 if (*(__CFBundleLocaleAbbreviationsArray
+ i
+ 0) == languageAbbreviation
[0] && *(__CFBundleLocaleAbbreviationsArray
+ i
+ 1) == languageAbbreviation
[1]) result
= i
/ LOCALE_ABBREVIATION_LENGTH
;
267 if (25 == result
) result
= 68;
268 if (28 == result
) result
= 82;
272 static SInt32
_CFBundleGetRegionCodeForLocalization(CFStringRef localizationName
) {
273 SInt32 result
= -1, i
;
274 char buff
[LOCALE_ABBREVIATION_LENGTH
];
275 CFIndex length
= CFStringGetLength(localizationName
);
276 if (length
>= LANGUAGE_ABBREVIATION_LENGTH
- 1 && length
<= LOCALE_ABBREVIATION_LENGTH
- 1 && CFStringGetCString(localizationName
, buff
, LOCALE_ABBREVIATION_LENGTH
, kCFStringEncodingASCII
)) {
277 buff
[LOCALE_ABBREVIATION_LENGTH
- 1] = '\0';
278 for (i
= 0; -1 == result
&& i
< NUM_LOCALE_ABBREVIATIONS
* LOCALE_ABBREVIATION_LENGTH
; i
+= LOCALE_ABBREVIATION_LENGTH
) {
279 if (0 == strcmp(buff
, __CFBundleLocaleAbbreviationsArray
+ i
)) result
= i
/ LOCALE_ABBREVIATION_LENGTH
;
282 if (25 == result
) result
= 68;
283 if (28 == result
) result
= 82;
284 if (37 == result
) result
= 0;
286 SInt32 languageCode
= _CFBundleGetLanguageCodeForLocalization(localizationName
);
287 result
= _CFBundleGetRegionCodeForLanguageCode(languageCode
);
292 CF_PRIVATE CFStringRef
_CFBundleCopyLocaleAbbreviationForRegionCode(SInt32 regionCode
) {
293 CFStringRef result
= NULL
;
294 if (0 <= regionCode
&& regionCode
< NUM_LOCALE_ABBREVIATIONS
) {
295 const char *localeAbbreviation
= __CFBundleLocaleAbbreviationsArray
+ regionCode
* LOCALE_ABBREVIATION_LENGTH
;
296 if (localeAbbreviation
&& *localeAbbreviation
!= '\0') {
297 result
= CFStringCreateWithCStringNoCopy(kCFAllocatorSystemDefault
, localeAbbreviation
, kCFStringEncodingASCII
, kCFAllocatorNull
);
303 CF_EXPORT Boolean
CFBundleGetLocalizationInfoForLocalization(CFStringRef localizationName
, SInt32
*languageCode
, SInt32
*regionCode
, SInt32
*scriptCode
, CFStringEncoding
*stringEncoding
) {
304 Boolean retval
= false;
305 SInt32 language
= -1, region
= -1, script
= 0;
306 CFStringEncoding encoding
= kCFStringEncodingMacRoman
;
307 if (!localizationName
) {
308 CFBundleRef mainBundle
= CFBundleGetMainBundle();
309 CFArrayRef languages
= NULL
;
311 languages
= _CFBundleCopyLanguageSearchListInBundle(mainBundle
);
313 if (!languages
) languages
= _CFBundleCopyUserLanguages();
314 if (languages
&& CFArrayGetCount(languages
) > 0) localizationName
= (CFStringRef
)CFArrayGetValueAtIndex(languages
, 0);
316 if (localizationName
) {
317 LangCode langCode
= -1;
318 RegionCode regCode
= -1;
319 ScriptCode scrCode
= 0;
320 CFStringEncoding enc
= kCFStringEncodingMacRoman
;
321 retval
= CFLocaleGetLanguageRegionEncodingForLocaleIdentifier(localizationName
, &langCode
, ®Code
, &scrCode
, &enc
);
330 if (localizationName
) {
331 language
= _CFBundleGetLanguageCodeForLocalization(localizationName
);
332 region
= _CFBundleGetRegionCodeForLocalization(localizationName
);
334 _CFBundleGetLanguageAndRegionCodes(&language
, ®ion
);
336 if ((language
< 0 || language
> (int)(sizeof(__CFBundleScriptCodesArray
)/sizeof(SInt32
))) && region
!= -1) language
= _CFBundleGetLanguageCodeForRegionCode(region
);
337 if (region
== -1 && language
!= -1) region
= _CFBundleGetRegionCodeForLanguageCode(language
);
338 if (language
>= 0 && language
< (int)(sizeof(__CFBundleScriptCodesArray
)/sizeof(SInt32
))) {
339 script
= __CFBundleScriptCodesArray
[language
];
341 if (language
>= 0 && language
< (int)(sizeof(__CFBundleStringEncodingsArray
)/sizeof(CFStringEncoding
))) {
342 encoding
= __CFBundleStringEncodingsArray
[language
];
344 retval
= (language
!= -1 || region
!= -1);
346 if (languageCode
) *languageCode
= language
;
347 if (regionCode
) *regionCode
= region
;
348 if (scriptCode
) *scriptCode
= script
;
349 if (stringEncoding
) *stringEncoding
= encoding
;
353 CFStringRef
CFBundleCopyLocalizationForLocalizationInfo(SInt32 languageCode
, SInt32 regionCode
, SInt32 scriptCode
, CFStringEncoding stringEncoding
) {
354 CFStringRef localizationName
= NULL
;
355 if (!localizationName
) localizationName
= _CFBundleCopyLocaleAbbreviationForRegionCode(regionCode
);
356 #if DEPLOYMENT_TARGET_MACOSX
357 if (!localizationName
&& 0 <= languageCode
&& languageCode
< SHRT_MAX
) localizationName
= CFLocaleCreateCanonicalLocaleIdentifierFromScriptManagerCodes(kCFAllocatorSystemDefault
, (LangCode
)languageCode
, (RegionCode
)-1);
359 if (!localizationName
) localizationName
= _CFBundleCopyLanguageAbbreviationForLanguageCode(languageCode
);
360 if (!localizationName
) {
361 SInt32 language
= -1, scriptLanguage
= -1, encodingLanguage
= -1;
363 for (i
= 0; language
== -1 && i
< (sizeof(__CFBundleScriptCodesArray
)/sizeof(SInt32
)); i
++) {
364 if (__CFBundleScriptCodesArray
[i
] == scriptCode
&& __CFBundleStringEncodingsArray
[i
] == stringEncoding
) language
= i
;
366 for (i
= 0; scriptLanguage
== -1 && i
< (sizeof(__CFBundleScriptCodesArray
)/sizeof(SInt32
)); i
++) {
367 if (__CFBundleScriptCodesArray
[i
] == scriptCode
) scriptLanguage
= i
;
369 for (i
= 0; encodingLanguage
== -1 && i
< (sizeof(__CFBundleStringEncodingsArray
)/sizeof(CFStringEncoding
)); i
++) {
370 if (__CFBundleStringEncodingsArray
[i
] == stringEncoding
) encodingLanguage
= i
;
372 localizationName
= _CFBundleCopyLanguageAbbreviationForLanguageCode(language
);
373 if (!localizationName
) localizationName
= _CFBundleCopyLanguageAbbreviationForLanguageCode(encodingLanguage
);
374 if (!localizationName
) localizationName
= _CFBundleCopyLanguageAbbreviationForLanguageCode(scriptLanguage
);
376 return localizationName
;
381 // Get a list of lproj directories for a particular resource directory URL. Uncached. Does not include any predefined localizations from an Info.plist. This function does make any attempt to localize or canonicalize the results.
382 static CFArrayRef
_CFBundleCopyLProjDirectoriesForURL(CFAllocatorRef allocator
, CFURLRef url
) {
383 __block CFMutableArrayRef result
= NULL
;
384 CFURLRef absoluteURL
= CFURLCopyAbsoluteURL(url
);
385 CFStringRef directoryPath
= CFURLCopyFileSystemPath(absoluteURL
, PLATFORM_PATH_STYLE
);
386 CFRelease(absoluteURL
);
388 CFStringRef lproj
= _CFBundleLprojExtensionWithDot
;
389 CFIndex lprojLen
= CFStringGetLength(lproj
);
391 _CFIterateDirectory(directoryPath
, ^Boolean(CFStringRef fileName
, uint8_t fileType
) {
392 // See if the fileName ends in .lproj
393 // The comparison starts at the end of the fileName, backed up by the length of .lproj
394 CFIndex fileNameLen
= CFStringGetLength(fileName
);
395 if (fileNameLen
> lprojLen
&& CFStringCompareWithOptions(fileName
, lproj
, CFRangeMake(fileNameLen
- lprojLen
, lprojLen
), 0) == kCFCompareEqualTo
) {
396 // Chop off the .lproj part before creating a string
397 CFStringRef lprojDirectoryName
= CFStringCreateWithSubstring(kCFAllocatorSystemDefault
, fileName
, CFRangeMake(0, fileNameLen
- lprojLen
));
398 if (!result
) result
= CFArrayCreateMutable(allocator
, 0, &kCFTypeArrayCallBacks
);
399 CFArrayAppendValue(result
, lprojDirectoryName
);
400 CFRelease(lprojDirectoryName
);
405 CFRelease(directoryPath
);
406 return (CFArrayRef
)result
;
409 /* This function returns:
410 1. The predefined localizations in the Info.plist (CFBundleLocalizations)
411 2. Additionally, the .lproj directories inside the bundle
412 3. Additionally, the development region of the bundle (CFBundleDevelopmentRegion) -- although if it's already in #1, or #2, we don't append it again
413 4. As an ultimate fallback, an empty array
415 This doesn't attempt to include a list of localizations supported by a bundle by way of a fallback path; e.g., if the bundle has en_GB then we do not include en_IN (which falls back to en_GB if not present).
417 Since the result of this is "typically passed as a parameter to either the CFBundleCopyPreferredLocalizationsFromArray or CFBundleCopyLocalizationsForPreferences function", those other functions will take into account the user prefs and pick the right lproj.
419 CF_EXPORT CFArrayRef
CFBundleCopyBundleLocalizations(CFBundleRef bundle
) {
420 CFArrayRef result
= NULL
;
422 __CFLock(&bundle
->_lock
);
423 if (bundle
->_lookedForLocalizations
) {
424 result
= (CFArrayRef
)CFRetain(bundle
->_localizations
);
425 __CFUnlock(&bundle
->_lock
);
428 __CFUnlock(&bundle
->_lock
);
430 CFDictionaryRef infoDict
= CFBundleGetInfoDictionary(bundle
);
432 CFArrayRef predefinedLocalizations
= (CFArrayRef
)CFDictionaryGetValue(infoDict
, kCFBundleLocalizationsKey
);
433 if (predefinedLocalizations
&& CFGetTypeID(predefinedLocalizations
) == CFArrayGetTypeID()) {
434 // <rdar://problem/14255685> Some people put bad things inside this array =(
435 CFMutableArrayRef realPredefinedLocalizations
= CFArrayCreateMutable(CFGetAllocator(bundle
), CFArrayGetCount(predefinedLocalizations
), &kCFTypeArrayCallBacks
);
436 for (CFIndex i
= 0; i
< CFArrayGetCount(predefinedLocalizations
); i
++) {
437 CFStringRef oneEntry
= CFArrayGetValueAtIndex(predefinedLocalizations
, i
);
438 if (CFGetTypeID(oneEntry
) == CFStringGetTypeID() && CFStringGetLength(oneEntry
) > 0) {
439 CFArrayAppendValue(realPredefinedLocalizations
, oneEntry
);
442 result
= CFArrayCreateCopy(CFGetAllocator(bundle
), realPredefinedLocalizations
);
443 CFRelease(realPredefinedLocalizations
);
447 CFURLRef resourcesURL
= CFBundleCopyResourcesDirectoryURL(bundle
);
449 CFArrayRef lprojDirectoriesInResources
= _CFBundleCopyLProjDirectoriesForURL(CFGetAllocator(bundle
), resourcesURL
);
450 if (lprojDirectoriesInResources
) {
452 // Append the lproj result to the predefined localization array
453 CFMutableArrayRef newResult
= CFArrayCreateMutableCopy(kCFAllocatorDefault
, 0, result
);
455 CFArrayAppendArray(newResult
, lprojDirectoriesInResources
, CFRangeMake(0, CFArrayGetCount(lprojDirectoriesInResources
)));
456 CFRelease(lprojDirectoriesInResources
);
459 result
= lprojDirectoriesInResources
;
462 CFRelease(resourcesURL
);
465 CFStringRef developmentLocalization
= CFBundleGetDevelopmentRegion(bundle
);
467 if (developmentLocalization
) {
468 CFRange entireRange
= CFRangeMake(0, CFArrayGetCount(result
));
469 if (CFArrayContainsValue(result
, entireRange
, _CFBundleBaseDirectory
)) {
470 // Base.lproj contains localizations for the development region. Insert the development region into the existing array if there isn't already a match so that resource lookup doesn't default to another language.
471 // We need to make sure that we don't add "en" if "English" exists. (14006652)
472 CFStringRef foundInLocalizations
= _CFBundleCopyLanguageFoundInLocalizations(result
, developmentLocalization
);
473 if (!foundInLocalizations
) {
474 CFMutableArrayRef newResult
= CFArrayCreateMutableCopy(kCFAllocatorDefault
, 0, result
);
476 CFArrayAppendValue(newResult
, developmentLocalization
);
479 // The development localization was in the result, but in some other form. We don't need to add it again.
480 CFRelease(foundInLocalizations
);
485 if (developmentLocalization
) {
486 result
= CFArrayCreate(CFGetAllocator(bundle
), (const void **)&developmentLocalization
, 1, &kCFTypeArrayCallBacks
);
488 result
= CFArrayCreate(CFGetAllocator(bundle
), NULL
, 0, &kCFTypeArrayCallBacks
);
493 __CFLock(&bundle
->_lock
);
494 if (bundle
->_lookedForLocalizations
&& result
) {
495 // Another thread beat us to it. Release our result and return the existing answer.
497 result
= (CFArrayRef
)CFRetain(bundle
->_localizations
);
499 bundle
->_localizations
= (CFArrayRef
)CFRetain(result
);
500 bundle
->_lookedForLocalizations
= true;
502 __CFUnlock(&bundle
->_lock
);
507 CF_EXPORT CFArrayRef
CFBundleCopyLocalizationsForURL(CFURLRef url
) {
508 CFArrayRef result
= NULL
;
509 CFBundleRef bundle
= CFBundleCreate(kCFAllocatorSystemDefault
, url
);
510 CFStringRef devLang
= NULL
;
512 result
= CFBundleCopyBundleLocalizations(bundle
);
515 CFDictionaryRef infoDict
= _CFBundleCopyInfoDictionaryInExecutable(url
);
517 CFArrayRef predefinedLocalizations
= (CFArrayRef
)CFDictionaryGetValue(infoDict
, kCFBundleLocalizationsKey
);
518 if (predefinedLocalizations
&& CFGetTypeID(predefinedLocalizations
) == CFArrayGetTypeID()) {
519 result
= (CFArrayRef
)CFRetain(predefinedLocalizations
);
522 devLang
= (CFStringRef
)CFDictionaryGetValue(infoDict
, kCFBundleDevelopmentRegionKey
);
523 if (devLang
&& (CFGetTypeID(devLang
) == CFStringGetTypeID() && CFStringGetLength(devLang
) > 0)) {
524 result
= CFArrayCreate(kCFAllocatorSystemDefault
, (const void **)&devLang
, 1, &kCFTypeArrayCallBacks
);
533 extern void *__CFAppleLanguages
;
538 CF_PRIVATE CFArrayRef
_CFBundleCopyUserLanguages() {
539 static CFArrayRef _CFBundleUserLanguages
= NULL
;
540 static dispatch_once_t once
= 0;
541 dispatch_once(&once
, ^{
542 CFArrayRef preferencesArray
= NULL
;
543 if (__CFAppleLanguages
) {
545 CFIndex length
= strlen((const char *)__CFAppleLanguages
);
547 data
= CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault
, (const UInt8
*)__CFAppleLanguages
, length
, kCFAllocatorNull
);
549 _CFBundleUserLanguages
= (CFArrayRef
)CFPropertyListCreateWithData(kCFAllocatorSystemDefault
, data
, kCFPropertyListImmutable
, NULL
, NULL
);
554 if (!_CFBundleUserLanguages
&& preferencesArray
) _CFBundleUserLanguages
= (CFArrayRef
)CFRetain(preferencesArray
);
555 Boolean useEnglishAsBackstop
= true;
556 // could perhaps read out of LANG environment variable
557 if (useEnglishAsBackstop
&& !_CFBundleUserLanguages
) {
558 CFStringRef english
= CFSTR("en");
559 _CFBundleUserLanguages
= CFArrayCreate(kCFAllocatorSystemDefault
, (const void **)&english
, 1, &kCFTypeArrayCallBacks
);
561 if (_CFBundleUserLanguages
&& CFGetTypeID(_CFBundleUserLanguages
) != CFArrayGetTypeID()) {
562 CFRelease(_CFBundleUserLanguages
);
563 _CFBundleUserLanguages
= NULL
;
565 if (preferencesArray
) CFRelease(preferencesArray
);
568 if (_CFBundleUserLanguages
) {
569 CFRetain(_CFBundleUserLanguages
);
570 return _CFBundleUserLanguages
;
576 CF_EXPORT
void _CFBundleGetLanguageAndRegionCodes(SInt32
*languageCode
, SInt32
*regionCode
) {
577 // an attempt to answer the question, "what language are we running in?"
578 // note that the question cannot be answered fully since it may depend on the bundle
579 SInt32 language
= -1, region
= -1;
580 CFBundleRef mainBundle
= CFBundleGetMainBundle();
581 CFArrayRef languages
= NULL
;
583 languages
= _CFBundleCopyLanguageSearchListInBundle(mainBundle
);
585 if (!languages
) languages
= _CFBundleCopyUserLanguages();
586 if (languages
&& CFArrayGetCount(languages
) > 0) {
587 CFStringRef localizationName
= (CFStringRef
)CFArrayGetValueAtIndex(languages
, 0);
588 Boolean retval
= false;
589 LangCode langCode
= -1;
590 RegionCode regCode
= -1;
591 retval
= CFLocaleGetLanguageRegionEncodingForLocaleIdentifier(localizationName
, &langCode
, ®Code
, NULL
, NULL
);
597 language
= _CFBundleGetLanguageCodeForLocalization(localizationName
);
598 region
= _CFBundleGetRegionCodeForLocalization(localizationName
);
604 if (language
== -1 && region
!= -1) language
= _CFBundleGetLanguageCodeForRegionCode(region
);
605 if (region
== -1 && language
!= -1) region
= _CFBundleGetRegionCodeForLanguageCode(language
);
606 if (languages
) CFRelease(languages
);
607 if (languageCode
) *languageCode
= language
;
608 if (regionCode
) *regionCode
= region
;
611 // Return a CFStringRef as it appears in the localizations, if it matches the input language in any form.
612 // For example, if the input language is 'en', and the array contains 'English', return 'English'.
613 // If the input language is 'en-US', and the array contains 'en_US', return 'en_US'.
614 // If the input language is 'froobleblax' and the array does not contain it, return NULL.
615 static CFStringRef
_CFBundleCopyLanguageFoundInLocalizations(CFArrayRef localizations
, CFStringRef language
) {
616 // Bail early on empty input
617 if (!localizations
|| !language
) {
621 CFRange localizationsRange
= CFRangeMake(0, CFArrayGetCount(localizations
));
623 // Does the array straight-up contain this language?
624 if (CFArrayContainsValue(localizations
, localizationsRange
, language
)) {
625 return CFRetain(language
);
628 // Does the array contain the alternate form of this language? (en -> English, or vice versa)
629 CFStringRef altLangStr
= _CFBundleGetAlternateNameForLanguage(language
);
630 if (altLangStr
&& CFArrayContainsValue(localizations
, localizationsRange
, altLangStr
)) {
631 return CFRetain(altLangStr
);
634 // Does the array contain a modified form of this language? (en-US -> en_US, or vice versa)
635 CFStringRef modifiedLangStr
= _CFBundleCopyModifiedLocalization(language
);
636 if (modifiedLangStr
) {
637 if (CFArrayContainsValue(localizations
, localizationsRange
, modifiedLangStr
)) {
638 return modifiedLangStr
;
640 CFRelease(modifiedLangStr
);
643 // Does the array contain a canonical form of this language?
644 CFStringRef canonicalLanguage
= CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorSystemDefault
, language
);
645 if (canonicalLanguage
) {
646 if (CFArrayContainsValue(localizations
, localizationsRange
, canonicalLanguage
)) {
647 return canonicalLanguage
;
650 // Does the array converted to canonical forms match the canonical form of this language?
651 for (CFIndex i
= 0; i
< localizationsRange
.length
; i
++) {
652 CFStringRef oneLanguage
= (CFStringRef
)CFArrayGetValueAtIndex(localizations
, i
);
653 CFStringRef canonicalOneLanguage
= CFLocaleCreateCanonicalLanguageIdentifierFromString(kCFAllocatorSystemDefault
, oneLanguage
);
655 if (canonicalOneLanguage
) {
656 if (CFEqual(canonicalOneLanguage
, canonicalLanguage
)) {
657 // oneLocalization is the same as the input language, even though they are in different forms
658 CFRelease(canonicalOneLanguage
);
659 CFRelease(canonicalLanguage
);
661 return CFRetain(oneLanguage
);
663 CFRelease(canonicalOneLanguage
);
667 CFRelease(canonicalLanguage
);
670 // The language was not found in the array in any form
674 // Given a list of localizations (e.g., provided as argument to API, or present as .lproj directories), return a mutable array of localizations in preferred order. Returns NULL if nothing is found.
675 static CFMutableArrayRef
_CFBundleCreateMutableArrayOfFallbackLanguages(CFArrayRef localizations
, CFStringRef language
) {
676 CFMutableArrayRef result
= CFArrayCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFTypeArrayCallBacks
);
678 // Check for the language itself (in whatever form)
679 CFStringRef languageInLocalizations
= _CFBundleCopyLanguageFoundInLocalizations(localizations
, language
);
680 if (languageInLocalizations
) {
681 CFArrayAppendValue(result
, languageInLocalizations
);
682 CFRelease(languageInLocalizations
);
685 // Languages form a tree in ICU data. For example:
687 // en_IN -> en_GB -> en_001 -> en -> root
689 // zh_Hant_MO -> zh_Hant_HK -> zh_Hant -> zh_TW -> root
691 // zh_Hans -> zh -> zh_CN -> root
693 // We will iterate through each element until we get to root, adding the entries to our resulting list of preferred languages as we go if they exist in the localizations list.
696 if (CFStringGetCString(language
, locale
, 128, kCFStringEncodingUTF8
)) {
697 UErrorCode error
= U_ZERO_ERROR
;
702 ualoc_getAppleParent(locale
, parent
, 128, &error
);
703 if (error
!= U_ZERO_ERROR
) break;
704 if (strncmp(parent
, "root", 4) == 0) break;
706 CFStringRef parentString
= CFStringCreateWithCString(kCFAllocatorSystemDefault
, parent
, kCFStringEncodingUTF8
);
708 CFStringRef parentInLocalizations
= _CFBundleCopyLanguageFoundInLocalizations(localizations
, parentString
);
709 if (parentInLocalizations
) {
710 // Be sure not to add the same language to the result array twice. This can happen when the above function canonicalizes names (e.g., zh_TW -> zh_Hans).
711 if (!CFArrayContainsValue(result
, CFRangeMake(0, CFArrayGetCount(result
)), parentInLocalizations
)) {
712 CFArrayAppendValue(result
, parentInLocalizations
);
714 CFRelease(parentInLocalizations
);
716 CFRelease(parentString
);
719 // The parent now becomes the locale we enter in the next loop
720 if (strlcpy(locale
, parent
, 128) >= 128) break;
722 // Just in case the call into ICU to get the parent locale results in a cycle, we need a fallback mechanism to break out of this while loop. Therefore we'll only fallback a max of 16 times.
724 if (counter
>= 16) break;
728 if (CFArrayGetCount(result
) == 0) {
737 Funnel point for figuring out the language search order for resource lookup and other functions.
739 The input to the search is the list of searchLanguages, a development language, and the list of user-preferred languages.
741 The output is a mutable array. The result will add elements in order of the preferred languages specified. Returns an empty array if nothing is found.
743 The users list can contain region names (like "en_US" for US English). In this case, if the region lproj exists, it will be added, and, if the region's associated language lproj exists that will be added.
745 static CFMutableArrayRef
_CFBundleCopyPreferredLanguagesInList(CFArrayRef searchLanguages
, CFStringRef devLang
, CFArrayRef userLanguages
, Boolean considerMain
, CFURLRef bundleURL
, CFBundleRef bundle
) {
746 CFMutableArrayRef result
= NULL
;
747 CFArrayRef mainBundleLangs
= NULL
;
749 // If CFBundleAllowMixedLocalizations is set, then we do not check the main bundle. If considerMain is not set (we've entered through the API that specifically ignores the main bundle), then do not check the main bundle.
750 if (considerMain
&& !CFBundleAllowMixedLocalizations()) {
751 if (CFBundleFollowParentLocalization()) {
753 CFBundleRef mainBundle
= CFBundleGetMainBundle();
755 CFURLRef mainBundleURL
= CFBundleCopyBundleURL(mainBundle
);
757 if (!bundleURL
|| !CFEqual(bundleURL
, mainBundleURL
)) {
758 // If there is a main bundle, and it isn't this one, try to use the language it prefers.
759 mainBundleLangs
= _CFBundleCopyLanguageSearchListInBundle(mainBundle
);
761 CFRelease(mainBundleURL
);
766 if (mainBundleLangs
) {
767 if (CFArrayGetCount(mainBundleLangs
) > 0) {
768 result
= _CFBundleCreateMutableArrayOfFallbackLanguages(searchLanguages
, (CFStringRef
)CFArrayGetValueAtIndex(mainBundleLangs
, 0));
774 // If we didn't find the main bundle's preferred language, look at the users' prefs again and find the best one.
776 CFIndex count
= CFArrayGetCount(userLanguages
);
777 for (CFIndex i
= 0; i
< count
; i
++) {
778 CFStringRef curLangStr
= (CFStringRef
)CFArrayGetValueAtIndex(userLanguages
, i
);
779 result
= _CFBundleCreateMutableArrayOfFallbackLanguages(searchLanguages
, curLangStr
);
785 // use development region and U.S. English as backstops
786 if (!result
&& devLang
) {
787 result
= _CFBundleCreateMutableArrayOfFallbackLanguages(searchLanguages
, devLang
);
791 result
= _CFBundleCreateMutableArrayOfFallbackLanguages(searchLanguages
, CFSTR("en_US"));
796 result
= CFArrayCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFTypeArrayCallBacks
);
800 if (mainBundleLangs
) CFRelease(mainBundleLangs
);
805 // userLanguages must be non-NULL
806 static CFArrayRef
_CFBundleCopyLocalizationsForPreferences(CFArrayRef localizations
, CFArrayRef userLanguages
, Boolean considerMain
) {
807 CFMutableArrayRef result
= NULL
;
809 if (localizations
&& CFArrayGetCount(localizations
) > 0) {
810 result
= _CFBundleCopyPreferredLanguagesInList(localizations
, NULL
, userLanguages
, considerMain
, NULL
, NULL
);
812 // Additional backstop behavior: use first entry as backstop
813 if (CFArrayGetCount(result
) == 0 && CFArrayGetCount(localizations
) > 0) {
814 CFArrayAppendValue(result
, CFArrayGetValueAtIndex(localizations
, 0));
815 } else if (CFArrayGetCount(result
) == 0) {
816 // Total backstop behavior to avoid having an empty array.
817 CFArrayAppendValue(result
, CFSTR("en"));
822 // Total backstop behavior to avoid having an empty array.
823 result
= CFArrayCreateMutable(kCFAllocatorSystemDefault
, 0, &kCFTypeArrayCallBacks
);
824 CFArrayAppendValue(result
, CFSTR("en"));
830 CF_EXPORT CFArrayRef
CFBundleCopyLocalizationsForPreferences(CFArrayRef localizations
, CFArrayRef preferredLocalizations
) {
831 // NOTE: This function has an interesting side effect; passing NULL for preferredLocalizations will still use the user's current set of preferences, but it will ignore the main bundle for the purposes of matching languages. This is something that people rely upon.
832 // Given an array of possible localizations, returns the one or more of them that CFBundle would use, without reference to the current application context, if the user's preferred localizations were given by prefArray. If prefArray is NULL, the current user's actual preferred localizations will be used. This is not the same as CFBundleCopyPreferredLocalizationsFromArray(), because that function takes the current application context into account. To determine the localizations that another application would use, apply this function to the result of CFBundleCopyBundleLocalizations().
834 if (preferredLocalizations
) {
835 return _CFBundleCopyLocalizationsForPreferences(localizations
, preferredLocalizations
, false);
837 CFArrayRef defaultPreferredLocalizations
= _CFBundleCopyUserLanguages();
838 if (!defaultPreferredLocalizations
) defaultPreferredLocalizations
= CFArrayCreate(kCFAllocatorSystemDefault
, NULL
, 0, &kCFTypeArrayCallBacks
);
839 CFArrayRef result
= _CFBundleCopyLocalizationsForPreferences(localizations
, defaultPreferredLocalizations
, false);
840 CFRelease(defaultPreferredLocalizations
);
845 CF_EXPORT CFArrayRef
CFBundleCopyPreferredLocalizationsFromArray(CFArrayRef localizations
) {
846 // Given an array of possible localizations, returns the one or more of them that CFBundle would use in the current application context. To determine the localizations that would be used for a particular bundle in the current application context, apply this function to the result of CFBundleCopyBundleLocalizations().
847 // NOTE: Current application context refers to both using the main bundle and also using the preferred languages for the user
848 CFArrayRef preferredLocalizations
= _CFBundleCopyUserLanguages();
849 if (!preferredLocalizations
) preferredLocalizations
= CFArrayCreate(kCFAllocatorSystemDefault
, NULL
, 0, &kCFTypeArrayCallBacks
);
851 CFArrayRef result
= _CFBundleCopyLocalizationsForPreferences(localizations
, preferredLocalizations
, true);
853 CFRelease(preferredLocalizations
);
857 static CFStringRef _defaultLocalization
= NULL
;
859 CF_EXPORT
void _CFBundleSetDefaultLocalization(CFStringRef localizationName
) {
860 CFStringRef newLocalization
= localizationName
? (CFStringRef
)CFStringCreateCopy(kCFAllocatorSystemDefault
, localizationName
) : NULL
;
861 if (_defaultLocalization
) CFRelease(_defaultLocalization
);
862 _defaultLocalization
= newLocalization
;
869 // This is the funnel point for looking up languages for a particular bundle.
870 CF_PRIVATE CFArrayRef
_CFBundleCopyLanguageSearchListInBundle(CFBundleRef bundle
) {
871 if (!bundle
->_searchLanguages
) {
872 #if DEPLOYMENT_TARGET_WINDOWS
873 if (_defaultLocalization
) CFArrayAppendValue(langs
, _defaultLocalization
);
875 // includes predefined localizations
876 CFArrayRef localizationsForBundle
= CFBundleCopyBundleLocalizations(bundle
);
877 CFArrayRef userLanguages
= _CFBundleCopyUserLanguages();
878 CFStringRef devLang
= CFBundleGetDevelopmentRegion(bundle
);
880 CFMutableArrayRef result
= _CFBundleCopyPreferredLanguagesInList(localizationsForBundle
, devLang
, userLanguages
, true, bundle
->_url
, bundle
);
882 if (CFArrayGetCount(result
) == 0) {
883 // If the user does not prefer any of our languages, and devLang is not present, try English
885 result
= _CFBundleCopyPreferredLanguagesInList(localizationsForBundle
, CFSTR("en_US"), userLanguages
, true, bundle
->_url
, bundle
);
888 // if none of the preferred localizations are present, fall back on a random localization that is present
889 if (CFArrayGetCount(result
) == 0 && localizationsForBundle
&& CFArrayGetCount(localizationsForBundle
) > 0) {
890 CFStringRef firstLocalization
= (CFStringRef
)CFArrayGetValueAtIndex(localizationsForBundle
, 0);
892 result
= _CFBundleCopyPreferredLanguagesInList(localizationsForBundle
, firstLocalization
, userLanguages
, true, bundle
->_url
, bundle
);
895 if (userLanguages
) CFRelease(userLanguages
);
897 if (devLang
&& !CFArrayContainsValue(result
, CFRangeMake(0, CFArrayGetCount(result
)), devLang
)) {
898 // Make sure that devLang is on the list as a fallback for individual resources that are not present
899 CFArrayAppendValue(result
, devLang
);
900 } else if (!devLang
) {
901 if (localizationsForBundle
) {
902 CFStringRef en_US
= CFSTR("en_US"), en
= CFSTR("en"), English
= CFSTR("English");
903 CFRange range
= CFRangeMake(0, CFArrayGetCount(localizationsForBundle
));
904 if (CFArrayContainsValue(localizationsForBundle
, range
, en
)) {
905 if (!CFArrayContainsValue(result
, CFRangeMake(0, CFArrayGetCount(result
)), en
)) CFArrayAppendValue(result
, en
);
906 } else if (CFArrayContainsValue(localizationsForBundle
, range
, English
)) {
907 if (!CFArrayContainsValue(result
, CFRangeMake(0, CFArrayGetCount(result
)), English
)) CFArrayAppendValue(result
, English
);
908 } else if (CFArrayContainsValue(localizationsForBundle
, range
, en_US
)) {
909 if (!CFArrayContainsValue(result
, CFRangeMake(0, CFArrayGetCount(result
)), en_US
)) CFArrayAppendValue(result
, en_US
);
914 if (localizationsForBundle
) CFRelease(localizationsForBundle
);
916 if (CFArrayGetCount(result
) == 0) {
917 // Total backstop behavior to avoid having an empty array.
918 if (_defaultLocalization
) {
919 CFArrayAppendValue(result
, _defaultLocalization
);
921 CFArrayAppendValue(result
, CFSTR("en"));
925 if (!OSAtomicCompareAndSwapPtrBarrier(NULL
, (void *)result
, (void * volatile *)&(bundle
->_searchLanguages
))) {
929 return (CFArrayRef
)CFRetain(bundle
->_searchLanguages
);
932 // This is the funnel point for looking up languages for a particular directory.
933 CF_PRIVATE CFArrayRef
_CFBundleCopyLanguageSearchListInDirectory(CFURLRef url
, uint8_t *version
) {
934 uint8_t localVersion
= 0;
935 CFDictionaryRef infoDict
= _CFBundleCopyInfoDictionaryInDirectory(kCFAllocatorSystemDefault
, url
, &localVersion
);
937 CFArrayRef predefinedLocalizations
= NULL
;
938 CFStringRef devLang
= NULL
;
940 devLang
= (CFStringRef
)CFDictionaryGetValue(infoDict
, kCFBundleDevelopmentRegionKey
);
941 if (devLang
&& (CFGetTypeID(devLang
) != CFStringGetTypeID() || CFStringGetLength(devLang
) == 0)) devLang
= NULL
;
943 predefinedLocalizations
= (CFArrayRef
)CFDictionaryGetValue(infoDict
, kCFBundleLocalizationsKey
);
944 if (predefinedLocalizations
&& CFGetTypeID(predefinedLocalizations
) != CFArrayGetTypeID()) {
945 predefinedLocalizations
= NULL
;
949 CFURLRef resourcesURL
= _CFBundleCopyResourcesDirectoryURLInDirectory(url
, localVersion
);
950 CFArrayRef localizationsInDirectory
= _CFBundleCopyLProjDirectoriesForURL(kCFAllocatorSystemDefault
, resourcesURL
);
951 CFRelease(resourcesURL
);
953 if (predefinedLocalizations
&& localizationsInDirectory
) {
954 CFMutableArrayRef newLocalizations
= CFArrayCreateMutableCopy(kCFAllocatorSystemDefault
, 0, predefinedLocalizations
);
955 CFArrayAppendArray(newLocalizations
, localizationsInDirectory
, CFRangeMake(0, CFArrayGetCount(localizationsInDirectory
)));
956 CFRelease(localizationsInDirectory
);
957 localizationsInDirectory
= (CFArrayRef
)newLocalizations
;
958 } else if (predefinedLocalizations
) {
959 localizationsInDirectory
= (CFArrayRef
)CFRetain(predefinedLocalizations
);
960 } else if (!localizationsInDirectory
) {
961 localizationsInDirectory
= CFArrayCreate(kCFAllocatorSystemDefault
, NULL
, 0, &kCFTypeArrayCallBacks
);
964 CFArrayRef userLanguages
= _CFBundleCopyUserLanguages();
965 CFMutableArrayRef result
= _CFBundleCopyPreferredLanguagesInList(localizationsInDirectory
, devLang
, userLanguages
, true, url
, NULL
);
967 if (userLanguages
) CFRelease(userLanguages
);
968 CFRelease(localizationsInDirectory
);
970 if (devLang
&& CFArrayGetFirstIndexOfValue(result
, CFRangeMake(0, CFArrayGetCount(result
)), devLang
) < 0) CFArrayAppendValue(result
, devLang
);
972 // Total backstop behavior to avoid having an empty array.
973 if (CFArrayGetCount(result
) == 0) CFArrayAppendValue(result
, CFSTR("en"));
975 if (infoDict
) CFRelease(infoDict
);
976 if (version
) *version
= localVersion
;