/ 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, &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