]> git.saurik.com Git - apple/cf.git/blob - CFBundle_Locale.c
CF-1152.14.tar.gz
[apple/cf.git] / CFBundle_Locale.c
1 /*
2 * Copyright (c) 2015 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /* CFBundle_Locale.c
25 Copyright (c) 1999-2014, Apple Inc. All rights reserved.
26 Responsibility: Tony Parker
27 */
28
29 #include "CFBundle_Internal.h"
30
31 #include <CoreFoundation/CFPreferences.h>
32
33 #include <unicode/ualoc.h>
34 #include <ctype.h>
35
36 static CFStringRef _CFBundleCopyLanguageFoundInLocalizations(CFArrayRef localizations, CFStringRef language);
37
38 #pragma mark -
39 #pragma mark Mixed Localizations
40
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;
47 if (infoDictValue) {
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()) {
54 SInt32 val = 0;
55 if (CFNumberGetValue((CFNumberRef)infoDictValue, kCFNumberSInt32Type, &val)) result = (val != 0);
56 }
57 }
58 return result;
59 }
60
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);
66 });
67 return allowMixed;
68 }
69
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"));
75 });
76 return followParent;
77
78 }
79
80 #pragma mark -
81 #pragma mark Language and Locale Codes
82
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";
99
100 #define NUM_LOCALE_ABBREVIATIONS 109
101 #define LOCALE_ABBREVIATION_LENGTH 6
102
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"
123 };
124
125 #define NUM_LANGUAGE_NAMES 152
126 #define LANGUAGE_NAME_LENGTH 13
127
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";
149
150 #define NUM_LANGUAGE_ABBREVIATIONS 152
151 #define LANGUAGE_ABBREVIATION_LENGTH 3
152
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")};
158
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];
164 }
165 }
166
167 return NULL;
168 }
169
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
181 };
182
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
194 };
195
196 static SInt32 _CFBundleGetLanguageCodeForLocalization(CFStringRef localizationName) {
197 SInt32 result = -1, i;
198 char buff[256];
199 CFIndex length = CFStringGetLength(localizationName);
200 if (length >= LANGUAGE_ABBREVIATION_LENGTH - 1 && length <= 255 && CFStringGetCString(localizationName, buff, 255, kCFStringEncodingASCII)) {
201 buff[255] = '\0';
202 for (i = 0; -1 == result && i < NUM_LANGUAGE_NAMES; i++) {
203 if (0 == strcmp(buff, __CFBundleLanguageNamesArray[i])) result = i;
204 }
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;
211 }
212 }
213 }
214 return result;
215 }
216
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);
222 }
223 return result;
224 }
225
226 // Swaps - for _ and _ for - in a localization name
227 static CFStringRef _CFBundleCopyModifiedLocalization(CFStringRef localizationName) {
228 CFMutableStringRef result = NULL;
229 CFIndex length = CFStringGetLength(localizationName);
230 if (length >= 4) {
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("-"));
235 }
236 }
237 return result;
238 }
239
240 static SInt32 _CFBundleGetLanguageCodeForRegionCode(SInt32 regionCode) {
241 SInt32 result = -1, i;
242 if (52 == regionCode) { // hack for mixed-up Chinese language codes
243 result = 33;
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;
249 }
250 }
251 }
252 return result;
253 }
254
255 static SInt32 _CFBundleGetRegionCodeForLanguageCode(SInt32 languageCode) {
256 SInt32 result = -1, i;
257 if (19 == languageCode) { // hack for mixed-up Chinese language codes
258 result = 53;
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;
264 }
265 }
266 }
267 if (25 == result) result = 68;
268 if (28 == result) result = 82;
269 return result;
270 }
271
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;
280 }
281 }
282 if (25 == result) result = 68;
283 if (28 == result) result = 82;
284 if (37 == result) result = 0;
285 if (-1 == result) {
286 SInt32 languageCode = _CFBundleGetLanguageCodeForLocalization(localizationName);
287 result = _CFBundleGetRegionCodeForLanguageCode(languageCode);
288 }
289 return result;
290 }
291
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);
298 }
299 }
300 return result;
301 }
302
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;
310 if (mainBundle) {
311 languages = _CFBundleCopyLanguageSearchListInBundle(mainBundle);
312 }
313 if (!languages) languages = _CFBundleCopyUserLanguages();
314 if (languages && CFArrayGetCount(languages) > 0) localizationName = (CFStringRef)CFArrayGetValueAtIndex(languages, 0);
315 }
316 if (localizationName) {
317 LangCode langCode = -1;
318 RegionCode regCode = -1;
319 ScriptCode scrCode = 0;
320 CFStringEncoding enc = kCFStringEncodingMacRoman;
321 retval = CFLocaleGetLanguageRegionEncodingForLocaleIdentifier(localizationName, &langCode, &regCode, &scrCode, &enc);
322 if (retval) {
323 language = langCode;
324 region = regCode;
325 script = scrCode;
326 encoding = enc;
327 }
328 }
329 if (!retval) {
330 if (localizationName) {
331 language = _CFBundleGetLanguageCodeForLocalization(localizationName);
332 region = _CFBundleGetRegionCodeForLocalization(localizationName);
333 } else {
334 _CFBundleGetLanguageAndRegionCodes(&language, &region);
335 }
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];
340 }
341 if (language >= 0 && language < (int)(sizeof(__CFBundleStringEncodingsArray)/sizeof(CFStringEncoding))) {
342 encoding = __CFBundleStringEncodingsArray[language];
343 }
344 retval = (language != -1 || region != -1);
345 }
346 if (languageCode) *languageCode = language;
347 if (regionCode) *regionCode = region;
348 if (scriptCode) *scriptCode = script;
349 if (stringEncoding) *stringEncoding = encoding;
350 return retval;
351 }
352
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);
358 #endif
359 if (!localizationName) localizationName = _CFBundleCopyLanguageAbbreviationForLanguageCode(languageCode);
360 if (!localizationName) {
361 SInt32 language = -1, scriptLanguage = -1, encodingLanguage = -1;
362 unsigned int i;
363 for (i = 0; language == -1 && i < (sizeof(__CFBundleScriptCodesArray)/sizeof(SInt32)); i++) {
364 if (__CFBundleScriptCodesArray[i] == scriptCode && __CFBundleStringEncodingsArray[i] == stringEncoding) language = i;
365 }
366 for (i = 0; scriptLanguage == -1 && i < (sizeof(__CFBundleScriptCodesArray)/sizeof(SInt32)); i++) {
367 if (__CFBundleScriptCodesArray[i] == scriptCode) scriptLanguage = i;
368 }
369 for (i = 0; encodingLanguage == -1 && i < (sizeof(__CFBundleStringEncodingsArray)/sizeof(CFStringEncoding)); i++) {
370 if (__CFBundleStringEncodingsArray[i] == stringEncoding) encodingLanguage = i;
371 }
372 localizationName = _CFBundleCopyLanguageAbbreviationForLanguageCode(language);
373 if (!localizationName) localizationName = _CFBundleCopyLanguageAbbreviationForLanguageCode(encodingLanguage);
374 if (!localizationName) localizationName = _CFBundleCopyLanguageAbbreviationForLanguageCode(scriptLanguage);
375 }
376 return localizationName;
377 }
378
379 #pragma mark -
380
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);
387
388 CFStringRef lproj = _CFBundleLprojExtensionWithDot;
389 CFIndex lprojLen = CFStringGetLength(lproj);
390
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);
401 }
402 return true;
403 });
404
405 CFRelease(directoryPath);
406 return (CFArrayRef)result;
407 }
408
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
414
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).
416
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.
418 */
419 CF_EXPORT CFArrayRef CFBundleCopyBundleLocalizations(CFBundleRef bundle) {
420 CFArrayRef result = NULL;
421
422 __CFLock(&bundle->_lock);
423 if (bundle->_lookedForLocalizations) {
424 result = (CFArrayRef)CFRetain(bundle->_localizations);
425 __CFUnlock(&bundle->_lock);
426 return result;
427 }
428 __CFUnlock(&bundle->_lock);
429
430 CFDictionaryRef infoDict = CFBundleGetInfoDictionary(bundle);
431 if (infoDict) {
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);
440 }
441 }
442 result = CFArrayCreateCopy(CFGetAllocator(bundle), realPredefinedLocalizations);
443 CFRelease(realPredefinedLocalizations);
444 }
445 }
446
447 CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle);
448 if (resourcesURL) {
449 CFArrayRef lprojDirectoriesInResources = _CFBundleCopyLProjDirectoriesForURL(CFGetAllocator(bundle), resourcesURL);
450 if (lprojDirectoriesInResources) {
451 if (result) {
452 // Append the lproj result to the predefined localization array
453 CFMutableArrayRef newResult = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, result);
454 CFRelease(result);
455 CFArrayAppendArray(newResult, lprojDirectoriesInResources, CFRangeMake(0, CFArrayGetCount(lprojDirectoriesInResources)));
456 CFRelease(lprojDirectoriesInResources);
457 result = newResult;
458 } else {
459 result = lprojDirectoriesInResources;
460 }
461 }
462 CFRelease(resourcesURL);
463 }
464
465 CFStringRef developmentLocalization = CFBundleGetDevelopmentRegion(bundle);
466 if (result) {
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);
475 CFRelease(result);
476 CFArrayAppendValue(newResult, developmentLocalization);
477 result = newResult;
478 } else {
479 // The development localization was in the result, but in some other form. We don't need to add it again.
480 CFRelease(foundInLocalizations);
481 }
482 }
483 }
484 } else {
485 if (developmentLocalization) {
486 result = CFArrayCreate(CFGetAllocator(bundle), (const void **)&developmentLocalization, 1, &kCFTypeArrayCallBacks);
487 } else {
488 result = CFArrayCreate(CFGetAllocator(bundle), NULL, 0, &kCFTypeArrayCallBacks);
489 }
490 }
491
492 // Cache the result.
493 __CFLock(&bundle->_lock);
494 if (bundle->_lookedForLocalizations && result) {
495 // Another thread beat us to it. Release our result and return the existing answer.
496 CFRelease(result);
497 result = (CFArrayRef)CFRetain(bundle->_localizations);
498 } else {
499 bundle->_localizations = (CFArrayRef)CFRetain(result);
500 bundle->_lookedForLocalizations = true;
501 }
502 __CFUnlock(&bundle->_lock);
503
504 return result;
505 }
506
507 CF_EXPORT CFArrayRef CFBundleCopyLocalizationsForURL(CFURLRef url) {
508 CFArrayRef result = NULL;
509 CFBundleRef bundle = CFBundleCreate(kCFAllocatorSystemDefault, url);
510 CFStringRef devLang = NULL;
511 if (bundle) {
512 result = CFBundleCopyBundleLocalizations(bundle);
513 CFRelease(bundle);
514 } else {
515 CFDictionaryRef infoDict = _CFBundleCopyInfoDictionaryInExecutable(url);
516 if (infoDict) {
517 CFArrayRef predefinedLocalizations = (CFArrayRef)CFDictionaryGetValue(infoDict, kCFBundleLocalizationsKey);
518 if (predefinedLocalizations && CFGetTypeID(predefinedLocalizations) == CFArrayGetTypeID()) {
519 result = (CFArrayRef)CFRetain(predefinedLocalizations);
520 }
521 if (!result) {
522 devLang = (CFStringRef)CFDictionaryGetValue(infoDict, kCFBundleDevelopmentRegionKey);
523 if (devLang && (CFGetTypeID(devLang) == CFStringGetTypeID() && CFStringGetLength(devLang) > 0)) {
524 result = CFArrayCreate(kCFAllocatorSystemDefault, (const void **)&devLang, 1, &kCFTypeArrayCallBacks);
525 }
526 }
527 CFRelease(infoDict);
528 }
529 }
530 return result;
531 }
532
533 extern void *__CFAppleLanguages;
534
535
536
537
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) {
544 CFDataRef data;
545 CFIndex length = strlen((const char *)__CFAppleLanguages);
546 if (length > 0) {
547 data = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, (const UInt8 *)__CFAppleLanguages, length, kCFAllocatorNull);
548 if (data) {
549 _CFBundleUserLanguages = (CFArrayRef)CFPropertyListCreateWithData(kCFAllocatorSystemDefault, data, kCFPropertyListImmutable, NULL, NULL);
550 CFRelease(data);
551 }
552 }
553 }
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);
560 }
561 if (_CFBundleUserLanguages && CFGetTypeID(_CFBundleUserLanguages) != CFArrayGetTypeID()) {
562 CFRelease(_CFBundleUserLanguages);
563 _CFBundleUserLanguages = NULL;
564 }
565 if (preferencesArray) CFRelease(preferencesArray);
566 });
567
568 if (_CFBundleUserLanguages) {
569 CFRetain(_CFBundleUserLanguages);
570 return _CFBundleUserLanguages;
571 } else {
572 return NULL;
573 }
574 }
575
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;
582 if (mainBundle) {
583 languages = _CFBundleCopyLanguageSearchListInBundle(mainBundle);
584 }
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, &regCode, NULL, NULL);
592 if (retval) {
593 language = langCode;
594 region = regCode;
595 }
596 if (!retval) {
597 language = _CFBundleGetLanguageCodeForLocalization(localizationName);
598 region = _CFBundleGetRegionCodeForLocalization(localizationName);
599 }
600 } else {
601 language = 0;
602 region = 0;
603 }
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;
609 }
610
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) {
618 return NULL;
619 }
620
621 CFRange localizationsRange = CFRangeMake(0, CFArrayGetCount(localizations));
622
623 // Does the array straight-up contain this language?
624 if (CFArrayContainsValue(localizations, localizationsRange, language)) {
625 return CFRetain(language);
626 }
627
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);
632 }
633
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;
639 }
640 CFRelease(modifiedLangStr);
641 }
642
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;
648 }
649
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);
654
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);
660
661 return CFRetain(oneLanguage);
662 }
663 CFRelease(canonicalOneLanguage);
664 }
665 }
666
667 CFRelease(canonicalLanguage);
668 }
669
670 // The language was not found in the array in any form
671 return NULL;
672 }
673
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);
677
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);
683 }
684
685 // Languages form a tree in ICU data. For example:
686
687 // en_IN -> en_GB -> en_001 -> en -> root
688 // or
689 // zh_Hant_MO -> zh_Hant_HK -> zh_Hant -> zh_TW -> root
690 // or
691 // zh_Hans -> zh -> zh_CN -> root
692
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.
694
695 char locale[128];
696 if (CFStringGetCString(language, locale, 128, kCFStringEncodingUTF8)) {
697 UErrorCode error = U_ZERO_ERROR;
698 char parent[128];
699
700 int counter = 0;
701 while (1) {
702 ualoc_getAppleParent(locale, parent, 128, &error);
703 if (error != U_ZERO_ERROR) break;
704 if (strncmp(parent, "root", 4) == 0) break;
705
706 CFStringRef parentString = CFStringCreateWithCString(kCFAllocatorSystemDefault, parent, kCFStringEncodingUTF8);
707 if (parentString) {
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);
713 }
714 CFRelease(parentInLocalizations);
715 }
716 CFRelease(parentString);
717 }
718
719 // The parent now becomes the locale we enter in the next loop
720 if (strlcpy(locale, parent, 128) >= 128) break;
721
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.
723 counter++;
724 if (counter >= 16) break;
725 }
726 }
727
728 if (CFArrayGetCount(result) == 0) {
729 CFRelease(result);
730 result = NULL;
731 }
732
733 return result;
734 }
735
736 /*
737 Funnel point for figuring out the language search order for resource lookup and other functions.
738
739 The input to the search is the list of searchLanguages, a development language, and the list of user-preferred languages.
740
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.
742
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.
744 */
745 static CFMutableArrayRef _CFBundleCopyPreferredLanguagesInList(CFArrayRef searchLanguages, CFStringRef devLang, CFArrayRef userLanguages, Boolean considerMain, CFURLRef bundleURL, CFBundleRef bundle) {
746 CFMutableArrayRef result = NULL;
747 CFArrayRef mainBundleLangs = NULL;
748
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()) {
752 } else {
753 CFBundleRef mainBundle = CFBundleGetMainBundle();
754 if (mainBundle) {
755 CFURLRef mainBundleURL = CFBundleCopyBundleURL(mainBundle);
756 if (mainBundleURL) {
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);
760 }
761 CFRelease(mainBundleURL);
762 }
763 }
764 }
765
766 if (mainBundleLangs) {
767 if (CFArrayGetCount(mainBundleLangs) > 0) {
768 result = _CFBundleCreateMutableArrayOfFallbackLanguages(searchLanguages, (CFStringRef)CFArrayGetValueAtIndex(mainBundleLangs, 0));
769 }
770 }
771 }
772
773 if (!result) {
774 // If we didn't find the main bundle's preferred language, look at the users' prefs again and find the best one.
775 if (userLanguages) {
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);
780
781 if (result) break;
782 }
783 }
784
785 // use development region and U.S. English as backstops
786 if (!result && devLang) {
787 result = _CFBundleCreateMutableArrayOfFallbackLanguages(searchLanguages, devLang);
788 }
789
790 if (!result) {
791 result = _CFBundleCreateMutableArrayOfFallbackLanguages(searchLanguages, CFSTR("en_US"));
792 }
793 }
794
795 if (!result) {
796 result = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
797 }
798
799
800 if (mainBundleLangs) CFRelease(mainBundleLangs);
801
802 return result;
803 }
804
805 // userLanguages must be non-NULL
806 static CFArrayRef _CFBundleCopyLocalizationsForPreferences(CFArrayRef localizations, CFArrayRef userLanguages, Boolean considerMain) {
807 CFMutableArrayRef result = NULL;
808
809 if (localizations && CFArrayGetCount(localizations) > 0) {
810 result = _CFBundleCopyPreferredLanguagesInList(localizations, NULL, userLanguages, considerMain, NULL, NULL);
811
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"));
818 }
819 }
820
821 if (!result) {
822 // Total backstop behavior to avoid having an empty array.
823 result = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
824 CFArrayAppendValue(result, CFSTR("en"));
825 }
826
827 return result;
828 }
829
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().
833
834 if (preferredLocalizations) {
835 return _CFBundleCopyLocalizationsForPreferences(localizations, preferredLocalizations, false);
836 } else {
837 CFArrayRef defaultPreferredLocalizations = _CFBundleCopyUserLanguages();
838 if (!defaultPreferredLocalizations) defaultPreferredLocalizations = CFArrayCreate(kCFAllocatorSystemDefault, NULL, 0, &kCFTypeArrayCallBacks);
839 CFArrayRef result = _CFBundleCopyLocalizationsForPreferences(localizations, defaultPreferredLocalizations, false);
840 CFRelease(defaultPreferredLocalizations);
841 return result;
842 }
843 }
844
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);
850
851 CFArrayRef result = _CFBundleCopyLocalizationsForPreferences(localizations, preferredLocalizations, true);
852
853 CFRelease(preferredLocalizations);
854 return result;
855 }
856
857 static CFStringRef _defaultLocalization = NULL;
858
859 CF_EXPORT void _CFBundleSetDefaultLocalization(CFStringRef localizationName) {
860 CFStringRef newLocalization = localizationName ? (CFStringRef)CFStringCreateCopy(kCFAllocatorSystemDefault, localizationName) : NULL;
861 if (_defaultLocalization) CFRelease(_defaultLocalization);
862 _defaultLocalization = newLocalization;
863 }
864
865 #pragma mark -
866
867
868
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);
874 #endif
875 // includes predefined localizations
876 CFArrayRef localizationsForBundle = CFBundleCopyBundleLocalizations(bundle);
877 CFArrayRef userLanguages = _CFBundleCopyUserLanguages();
878 CFStringRef devLang = CFBundleGetDevelopmentRegion(bundle);
879
880 CFMutableArrayRef result = _CFBundleCopyPreferredLanguagesInList(localizationsForBundle, devLang, userLanguages, true, bundle->_url, bundle);
881
882 if (CFArrayGetCount(result) == 0) {
883 // If the user does not prefer any of our languages, and devLang is not present, try English
884 CFRelease(result);
885 result = _CFBundleCopyPreferredLanguagesInList(localizationsForBundle, CFSTR("en_US"), userLanguages, true, bundle->_url, bundle);
886 }
887
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);
891 CFRelease(result);
892 result = _CFBundleCopyPreferredLanguagesInList(localizationsForBundle, firstLocalization, userLanguages, true, bundle->_url, bundle);
893 }
894
895 if (userLanguages) CFRelease(userLanguages);
896
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);
910 }
911 }
912 }
913
914 if (localizationsForBundle) CFRelease(localizationsForBundle);
915
916 if (CFArrayGetCount(result) == 0) {
917 // Total backstop behavior to avoid having an empty array.
918 if (_defaultLocalization) {
919 CFArrayAppendValue(result, _defaultLocalization);
920 } else {
921 CFArrayAppendValue(result, CFSTR("en"));
922 }
923 }
924
925 if (!OSAtomicCompareAndSwapPtrBarrier(NULL, (void *)result, (void * volatile *)&(bundle->_searchLanguages))) {
926 CFRelease(result);
927 }
928 }
929 return (CFArrayRef)CFRetain(bundle->_searchLanguages);
930 }
931
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);
936
937 CFArrayRef predefinedLocalizations = NULL;
938 CFStringRef devLang = NULL;
939 if (infoDict) {
940 devLang = (CFStringRef)CFDictionaryGetValue(infoDict, kCFBundleDevelopmentRegionKey);
941 if (devLang && (CFGetTypeID(devLang) != CFStringGetTypeID() || CFStringGetLength(devLang) == 0)) devLang = NULL;
942
943 predefinedLocalizations = (CFArrayRef)CFDictionaryGetValue(infoDict, kCFBundleLocalizationsKey);
944 if (predefinedLocalizations && CFGetTypeID(predefinedLocalizations) != CFArrayGetTypeID()) {
945 predefinedLocalizations = NULL;
946 }
947 }
948
949 CFURLRef resourcesURL = _CFBundleCopyResourcesDirectoryURLInDirectory(url, localVersion);
950 CFArrayRef localizationsInDirectory = _CFBundleCopyLProjDirectoriesForURL(kCFAllocatorSystemDefault, resourcesURL);
951 CFRelease(resourcesURL);
952
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);
962 }
963
964 CFArrayRef userLanguages = _CFBundleCopyUserLanguages();
965 CFMutableArrayRef result = _CFBundleCopyPreferredLanguagesInList(localizationsInDirectory, devLang, userLanguages, true, url, NULL);
966
967 if (userLanguages) CFRelease(userLanguages);
968 CFRelease(localizationsInDirectory);
969
970 if (devLang && CFArrayGetFirstIndexOfValue(result, CFRangeMake(0, CFArrayGetCount(result)), devLang) < 0) CFArrayAppendValue(result, devLang);
971
972 // Total backstop behavior to avoid having an empty array.
973 if (CFArrayGetCount(result) == 0) CFArrayAppendValue(result, CFSTR("en"));
974
975 if (infoDict) CFRelease(infoDict);
976 if (version) *version = localVersion;
977 return result;
978 }
979