/ CFBundle_Locale.c
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, ®Code, &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, ®ion); 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, ®Code, 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