]> git.saurik.com Git - apple/icu.git/blob - icuSources/common/ualoc.cpp
ICU-62108.0.1.tar.gz
[apple/icu.git] / icuSources / common / ualoc.cpp
1 /*
2 *****************************************************************************************
3 * Copyright (C) 2014-2017 Apple Inc. All Rights Reserved.
4 *****************************************************************************************
5 */
6
7 #define DEBUG_UALOC 0
8 #if DEBUG_UALOC
9 #include <stdio.h>
10 #endif
11 #include <string.h>
12 #include <ctype.h>
13 #include "unicode/utypes.h"
14 #include "unicode/ualoc.h"
15 #include "unicode/uloc.h"
16 #include "unicode/ures.h"
17 #include "unicode/putil.h"
18 #include "unicode/ustring.h"
19 #include "cstring.h"
20 #include "cmemory.h"
21 #include "uhash.h"
22 #include "umutex.h"
23 #include "ucln_cmn.h"
24 // the following has replacements for some math.h funcs etc
25 #include "putilimp.h"
26
27
28 // The numeric values in territoryInfo are in "IntF" format from LDML2ICUConverter.
29 // From its docs (adapted): [IntF is] a special integer that represents the number in
30 // normalized scientific notation.
31 // Resultant integers are in the form -?xxyyyyyy, where xx is the exponent
32 // offset by 50 and yyyyyy is the coefficient to 5 decimal places (range 1.0 to 9.99999), e.g.
33 // 14660000000000 -> 1.46600E13 -> 63146600
34 // 0.0001 -> 1.00000E-4 -> 46100000
35 // -123.456 -> -1.23456E-2 -> -48123456
36 //
37 // Here to avoid an extra division we have the max coefficient as 999999 (instead of
38 // 9.99999) and instead offset the exponent by -55.
39 //
40 static double doubleFromIntF(int32_t intF) {
41 double coefficient = (double)(intF % 1000000);
42 int32_t exponent = (intF / 1000000) - 55;
43 return coefficient * uprv_pow10(exponent);
44 }
45
46 static int compareLangEntries(const void * entry1, const void * entry2) {
47 double fraction1 = ((const UALanguageEntry *)entry1)->userFraction;
48 double fraction2 = ((const UALanguageEntry *)entry2)->userFraction;
49 // want descending order
50 if (fraction1 > fraction2) return -1;
51 if (fraction1 < fraction2) return 1;
52 // userFractions the same, sort by languageCode
53 return uprv_strcmp(((const UALanguageEntry *)entry1)->languageCode,((const UALanguageEntry *)entry2)->languageCode);
54 }
55
56 // language codes to version with default script
57 // must be sorted by language code
58 static const char * langToDefaultScript[] = {
59 "az", "az_Latn",
60 "bs", "bs_Latn",
61 "iu", "iu_Cans",
62 "kk", "kk_Arab",
63 "ks", "ks_Arab",
64 "ku", "ku_Latn",
65 "ky", "ky_Cyrl",
66 "mn", "mn_Cyrl",
67 "ms", "ms_Latn",
68 "pa", "pa_Guru",
69 "rif", "rif_Tfng",
70 "shi", "shi_Tfng",
71 "sr", "sr_Cyrl",
72 "tg", "tg_Cyrl",
73 "tk", "tk_Latn",
74 "ug", "ug_Arab",
75 "uz", "uz_Latn",
76 "vai", "vai_Vaii",
77 "yue", "yue_Hant",
78 "zh", "zh_Hans",
79 NULL
80 };
81
82 static const char * langCodeWithScriptIfAmbig(const char * langCode) {
83 const char ** langToDefScriptPtr = langToDefaultScript;
84 const char * testCurLoc;
85 while ( (testCurLoc = *langToDefScriptPtr++) != NULL ) {
86 int cmp = uprv_strcmp(langCode, testCurLoc);
87 if (cmp <= 0) {
88 if (cmp == 0) {
89 return *langToDefScriptPtr;
90 }
91 break;
92 }
93 langToDefScriptPtr++;
94 }
95 return langCode;
96 }
97
98 static const UChar ustrLangStatusDefacto[] = {0x64,0x65,0x5F,0x66,0x61,0x63,0x74,0x6F,0x5F,0x6F,0x66,0x66,0x69,0x63,0x69,0x61,0x6C,0}; //"de_facto_official"
99 static const UChar ustrLangStatusOfficial[] = {0x6F,0x66,0x66,0x69,0x63,0x69,0x61,0x6C,0}; //"official"
100 static const UChar ustrLangStatusRegional[] = {0x6F,0x66,0x66,0x69,0x63,0x69,0x61,0x6C,0x5F,0x72,0x65,0x67,0x69,0x6F,0x6E,0x61,0x6C,0}; //"official_regional"
101
102 enum {
103 kLocalLangEntriesMax = 26, // enough for most regions to minimumFraction 0.001 except India
104 kLangEntriesFactor = 3 // if we have to allocate, multiply existing size by this
105 };
106
107 U_CAPI int32_t U_EXPORT2
108 ualoc_getLanguagesForRegion(const char *regionID, double minimumFraction,
109 UALanguageEntry *entries, int32_t entriesCapacity,
110 UErrorCode *err)
111 {
112 if (U_FAILURE(*err)) {
113 return 0;
114 }
115 if ( regionID == NULL || minimumFraction < 0.0 || minimumFraction > 1.0 ||
116 ((entries==NULL)? entriesCapacity!=0: entriesCapacity<0) ) {
117 *err = U_ILLEGAL_ARGUMENT_ERROR;
118 return 0;
119 }
120 UResourceBundle *rb = ures_openDirect(NULL, "supplementalData", err);
121 rb = ures_getByKey(rb, "territoryInfo", rb, err);
122 rb = ures_getByKey(rb, regionID, rb, err);
123 if (U_FAILURE(*err)) {
124 ures_close(rb);
125 return 0;
126 }
127
128 int32_t entryCount = 0;
129 UResourceBundle *langBund = NULL;
130 int32_t lbIdx, lbCount = ures_getSize(rb);
131 UALanguageEntry localLangEntries[kLocalLangEntriesMax];
132 UALanguageEntry * langEntries = localLangEntries;
133 int32_t langEntriesMax = kLocalLangEntriesMax;
134
135 for (lbIdx = 0; lbIdx < lbCount; lbIdx++) {
136 langBund = ures_getByIndex(rb, lbIdx, langBund, err);
137 if (U_FAILURE(*err)) {
138 break;
139 }
140 const char * langCode = ures_getKey(langBund);
141 if (uprv_strcmp(langCode,"territoryF") == 0) {
142 continue;
143 }
144 if (strnlen(langCode, UALANGDATA_CODELEN+1) > UALANGDATA_CODELEN) { // no uprv_strnlen
145 continue; // a code we cannot handle
146 }
147
148 UErrorCode localErr = U_ZERO_ERROR;
149 double userFraction = 0.0;
150 UResourceBundle *itemBund = ures_getByKey(langBund, "populationShareF", NULL, &localErr);
151 if (U_SUCCESS(localErr)) {
152 int32_t intF = ures_getInt(itemBund, &localErr);
153 if (U_SUCCESS(localErr)) {
154 userFraction = doubleFromIntF(intF);
155 }
156 ures_close(itemBund);
157 }
158 if (userFraction < minimumFraction) {
159 continue;
160 }
161 if (entries != NULL) {
162 localErr = U_ZERO_ERROR;
163 UALanguageStatus langStatus = UALANGSTATUS_UNSPECIFIED;
164 int32_t ulen;
165 const UChar * ustrLangStatus = ures_getStringByKey(langBund, "officialStatus", &ulen, &localErr);
166 if (U_SUCCESS(localErr)) {
167 int32_t cmp = u_strcmp(ustrLangStatus, ustrLangStatusOfficial);
168 if (cmp == 0) {
169 langStatus = UALANGSTATUS_OFFICIAL;
170 } else if (cmp < 0 && u_strcmp(ustrLangStatus, ustrLangStatusDefacto) == 0) {
171 langStatus = UALANGSTATUS_DEFACTO_OFFICIAL;
172 } else if (u_strcmp(ustrLangStatus, ustrLangStatusRegional) == 0) {
173 langStatus = UALANGSTATUS_REGIONAL_OFFICIAL;
174 }
175 }
176 // Now we have all of the info for our next entry
177 if (entryCount >= langEntriesMax) {
178 int32_t newMax = langEntriesMax * kLangEntriesFactor;
179 if (langEntries == localLangEntries) {
180 // first allocation, copy from local buf
181 langEntries = (UALanguageEntry*)uprv_malloc(newMax*sizeof(UALanguageEntry));
182 if (langEntries == NULL) {
183 *err = U_MEMORY_ALLOCATION_ERROR;
184 break;
185 }
186 uprv_memcpy(langEntries, localLangEntries, entryCount*sizeof(UALanguageEntry));
187 } else {
188 langEntries = (UALanguageEntry*)uprv_realloc(langEntries, newMax*sizeof(UALanguageEntry));
189 if (langEntries == NULL) {
190 *err = U_MEMORY_ALLOCATION_ERROR;
191 break;
192 }
193 }
194 langEntriesMax = newMax;
195 }
196 uprv_strcpy(langEntries[entryCount].languageCode, langCodeWithScriptIfAmbig(langCode));
197 langEntries[entryCount].userFraction = userFraction;
198 langEntries[entryCount].status = langStatus;
199 }
200 entryCount++;
201 }
202 ures_close(langBund);
203 ures_close(rb);
204 if (U_FAILURE(*err)) {
205 if (langEntries != localLangEntries) {
206 free(langEntries);
207 }
208 return 0;
209 }
210 if (entries != NULL) {
211 // sort langEntries, copy entries that fit to provided array
212 qsort(langEntries, entryCount, sizeof(UALanguageEntry), compareLangEntries);
213 if (entryCount > entriesCapacity) {
214 entryCount = entriesCapacity;
215 }
216 uprv_memcpy(entries, langEntries, entryCount*sizeof(UALanguageEntry));
217 if (langEntries != localLangEntries) {
218 free(langEntries);
219 }
220 }
221 return entryCount;
222 }
223
224 static const char * forceParent[] = {
225 "en_150", "en_GB", // en for Europe
226 "en_AU", "en_GB",
227 "en_BD", "en_GB", // en for Bangladesh
228 "en_BE", "en_150", // en for Belgium goes to en for Europe
229 "en_DG", "en_GB",
230 "en_FK", "en_GB",
231 "en_GG", "en_GB",
232 "en_GI", "en_GB",
233 "en_HK", "en_GB", // en for Hong Kong
234 "en_IE", "en_GB",
235 "en_IM", "en_GB",
236 "en_IN", "en_GB",
237 "en_IO", "en_GB",
238 "en_JE", "en_GB",
239 "en_JM", "en_GB",
240 "en_MO", "en_GB",
241 "en_MT", "en_GB",
242 "en_MV", "en_GB", // for Maldives
243 "en_MY", "en_GB", // en for Malaysia
244 "en_NZ", "en_AU",
245 "en_PK", "en_GB", // en for Pakistan
246 "en_SG", "en_GB",
247 "en_SH", "en_GB",
248 "en_VG", "en_GB",
249 "yue", "yue_CN", // yue_CN has 71M users (5.2% of 1.37G), yue_HK has 6.5M (90% of 7.17M)
250 "yue_CN", "root",
251 "yue_HK", "root",
252 "yue_Hans","yue_CN",
253 "yue_Hant","yue_HK",
254 "zh", "zh_CN",
255 "zh_CN", "root",
256 "zh_Hant", "zh_TW",
257 "zh_TW", "root",
258 NULL
259 };
260
261 enum { kLocBaseNameMax = 16 };
262
263 U_CAPI int32_t U_EXPORT2
264 ualoc_getAppleParent(const char* localeID,
265 char * parent,
266 int32_t parentCapacity,
267 UErrorCode* err)
268 {
269 UResourceBundle *rb;
270 int32_t len;
271 UErrorCode tempStatus;
272 char locbuf[ULOC_FULLNAME_CAPACITY+1];
273 char * foundDoubleUnderscore;
274
275 if (U_FAILURE(*err)) {
276 return 0;
277 }
278 if ( (parent==NULL)? parentCapacity!=0: parentCapacity<0 ) {
279 *err = U_ILLEGAL_ARGUMENT_ERROR;
280 return 0;
281 }
282 len = uloc_getBaseName(localeID, locbuf, ULOC_FULLNAME_CAPACITY, err); /* canonicalize and strip keywords */
283 if (U_FAILURE(*err)) {
284 return 0;
285 }
286 if (*err == U_STRING_NOT_TERMINATED_WARNING) {
287 locbuf[ULOC_FULLNAME_CAPACITY] = 0;
288 *err = U_ZERO_ERROR;
289 }
290 foundDoubleUnderscore = uprv_strstr(locbuf, "__"); /* __ comes from bad/missing subtag or variant */
291 if (foundDoubleUnderscore != NULL) {
292 *foundDoubleUnderscore = 0; /* terminate at the __ */
293 len = uprv_strlen(locbuf);
294 }
295 if (len >= 2 && (uprv_strncmp(locbuf, "en", 2) == 0 || uprv_strncmp(locbuf, "zh", 2) == 0)) {
296 const char ** forceParentPtr = forceParent;
297 const char * testCurLoc;
298 while ( (testCurLoc = *forceParentPtr++) != NULL ) {
299 int cmp = uprv_strcmp(locbuf, testCurLoc);
300 if (cmp <= 0) {
301 if (cmp == 0) {
302 len = uprv_strlen(*forceParentPtr);
303 if (len < parentCapacity) {
304 uprv_strcpy(parent, *forceParentPtr);
305 } else {
306 *err = U_BUFFER_OVERFLOW_ERROR;
307 }
308 return len;
309 }
310 break;
311 }
312 forceParentPtr++;
313 }
314 }
315 tempStatus = U_ZERO_ERROR;
316 rb = ures_openDirect(NULL, locbuf, &tempStatus);
317 if (U_SUCCESS(tempStatus)) {
318 const char * actualLocale = ures_getLocaleByType(rb, ULOC_ACTUAL_LOCALE, &tempStatus);
319 ures_close(rb);
320 if (U_SUCCESS(tempStatus) && uprv_strcmp(locbuf, actualLocale) != 0) {
321 // we have followed an alias
322 len = uprv_strlen(actualLocale);
323 if (len < parentCapacity) {
324 uprv_strcpy(parent, actualLocale);
325 } else {
326 *err = U_BUFFER_OVERFLOW_ERROR;
327 }
328 return len;
329 }
330 }
331 tempStatus = U_ZERO_ERROR;
332 rb = ures_openDirect(NULL, "supplementalData", &tempStatus);
333 rb = ures_getByKey(rb, "parentLocales", rb, &tempStatus);
334 if (U_SUCCESS(tempStatus)) {
335 UResourceBundle * parentMapBundle = NULL;
336 int32_t childLen = 0;
337 while (childLen == 0) {
338 tempStatus = U_ZERO_ERROR;
339 parentMapBundle = ures_getNextResource(rb, parentMapBundle, &tempStatus);
340 if (U_FAILURE(tempStatus)) {
341 break; // no more parent bundles, normal exit
342 }
343 char childName[kLocBaseNameMax + 1];
344 childName[kLocBaseNameMax] = 0;
345 const char * childPtr = NULL;
346 if (ures_getType(parentMapBundle) == URES_STRING) {
347 childLen = kLocBaseNameMax;
348 childPtr = ures_getUTF8String(parentMapBundle, childName, &childLen, FALSE, &tempStatus);
349 if (U_FAILURE(tempStatus) || uprv_strncmp(locbuf, childPtr, kLocBaseNameMax) != 0) {
350 childLen = 0;
351 }
352 } else { // should be URES_ARRAY
353 int32_t childCur, childCount = ures_getSize(parentMapBundle);
354 for (childCur = 0; childCur < childCount && childLen == 0; childCur++) {
355 tempStatus = U_ZERO_ERROR;
356 childLen = kLocBaseNameMax;
357 childPtr = ures_getUTF8StringByIndex(parentMapBundle, childCur, childName, &childLen, FALSE, &tempStatus);
358 if (U_FAILURE(tempStatus) || uprv_strncmp(locbuf, childPtr, kLocBaseNameMax) != 0) {
359 childLen = 0;
360 }
361 }
362 }
363 }
364 ures_close(rb);
365 if (childLen > 0) {
366 // parentMapBundle key is the parent we are looking for
367 const char * keyStr = ures_getKey(parentMapBundle);
368 len = uprv_strlen(keyStr);
369 if (len < parentCapacity) {
370 uprv_strcpy(parent, keyStr);
371 } else {
372 *err = U_BUFFER_OVERFLOW_ERROR;
373 }
374 ures_close(parentMapBundle);
375 return len;
376 }
377 ures_close(parentMapBundle);
378 }
379
380 len = uloc_getParent(locbuf, parent, parentCapacity, err);
381 if (U_SUCCESS(*err) && len == 0) {
382 len = 4;
383 if (len < parentCapacity) {
384 uprv_strcpy(parent, "root");
385 } else {
386 *err = U_BUFFER_OVERFLOW_ERROR;
387 }
388 }
389 return len;
390 }
391
392 // =================
393 // Data and related functions for ualoc_localizationsToUse
394 // =================
395
396 static const char * appleAliasMap[][2] = {
397 // names are lowercase here because they are looked up after being processed by uloc_getBaseName
398 { "arabic", "ar" }, // T2
399 { "chinese", "zh_Hans" }, // T0
400 { "danish", "da" }, // T2
401 { "dutch", "nl" }, // T1, still in use
402 { "english", "en" }, // T0, still in use
403 { "finnish", "fi" }, // T2
404 { "french", "fr" }, // T0, still in use
405 { "german", "de" }, // T0, still in use
406 { "italian", "it" }, // T1, still in use
407 { "japanese", "ja" }, // T0, still in use
408 { "korean", "ko" }, // T1
409 { "no_NO", "nb_NO" }, // special
410 { "norwegian", "nb" }, // T2
411 { "polish", "pl" }, // T2
412 { "portuguese", "pt" }, // T2
413 { "russian", "ru" }, // T2
414 { "spanish", "es" }, // T1, still in use
415 { "swedish", "sv" }, // T2
416 { "thai", "th" }, // T2
417 { "turkish", "tr" }, // T2
418 { "yue", "yue_Hans"}, // special
419 { "zh", "zh_Hans" }, // special
420 };
421 enum { kAppleAliasMapCount = UPRV_LENGTHOF(appleAliasMap) };
422
423 static const char * appleParentMap[][2] = {
424 { "en_150", "en_GB" }, // Apple custom parent
425 { "en_AD", "en_150" }, // Apple locale addition
426 { "en_AG", "en_GB" }, // Antigua & Barbuda
427 { "en_AI", "en_GB" }, // Anguilla
428 { "en_AL", "en_150" }, // Apple locale addition
429 { "en_AT", "en_150" }, // Apple locale addition
430 { "en_AU", "en_GB" }, // Apple custom parent
431 { "en_BA", "en_150" }, // Apple locale addition
432 { "en_BB", "en_GB" }, // Barbados
433 { "en_BD", "en_GB" }, // Apple custom parent
434 { "en_BE", "en_150" }, // Apple custom parent
435 { "en_BM", "en_GB" }, // Bermuda
436 { "en_BS", "en_GB" }, // Bahamas
437 { "en_BW", "en_GB" }, // Botswana
438 { "en_BZ", "en_GB" }, // Belize
439 { "en_CC", "en_AU" }, // Cocos (Keeling) Islands
440 { "en_CH", "en_150" }, // Apple locale addition
441 { "en_CK", "en_AU" }, // Cook Islands (maybe to en_NZ instead?)
442 { "en_CX", "en_AU" }, // Christmas Island
443 { "en_CY", "en_150" }, // Apple locale addition
444 { "en_CZ", "en_150" }, // Apple locale addition
445 { "en_DE", "en_150" }, // Apple locale addition
446 { "en_DG", "en_GB" },
447 { "en_DK", "en_150" }, // Apple locale addition
448 { "en_DM", "en_GB" }, // Dominica
449 { "en_EE", "en_150" }, // Apple locale addition
450 { "en_ES", "en_150" }, // Apple locale addition
451 { "en_FI", "en_150" }, // Apple locale addition
452 { "en_FJ", "en_GB" }, // Fiji
453 { "en_FK", "en_GB" },
454 { "en_FR", "en_150" }, // Apple locale addition
455 { "en_GD", "en_GB" }, // Grenada
456 { "en_GG", "en_GB" },
457 { "en_GH", "en_GB" }, // Ghana
458 { "en_GI", "en_GB" },
459 { "en_GM", "en_GB" }, // Gambia
460 { "en_GR", "en_150" }, // Apple locale addition
461 { "en_GY", "en_GB" }, // Guyana
462 { "en_HK", "en_GB" }, // Apple custom parent
463 { "en_HR", "en_150" }, // Apple locale addition
464 { "en_HU", "en_150" }, // Apple locale addition
465 { "en_IE", "en_GB" },
466 { "en_IL", "en_001" }, // Apple locale addition
467 { "en_IM", "en_GB" },
468 { "en_IN", "en_GB" }, // Apple custom parent
469 { "en_IO", "en_GB" },
470 { "en_IS", "en_150" }, // Apple locale addition
471 { "en_IT", "en_150" }, // Apple locale addition
472 { "en_JE", "en_GB" },
473 { "en_JM", "en_GB" },
474 { "en_KE", "en_GB" }, // Kenya
475 { "en_KI", "en_GB" }, // Kiribati
476 { "en_KN", "en_GB" }, // St. Kitts & Nevis
477 { "en_KY", "en_GB" }, // Cayman Islands
478 { "en_LC", "en_GB" }, // St. Lucia
479 { "en_LS", "en_GB" }, // Lesotho
480 { "en_LT", "en_150" }, // Apple locale addition
481 { "en_LU", "en_150" }, // Apple locale addition
482 { "en_LV", "en_150" }, // Apple locale addition
483 { "en_ME", "en_150" }, // Apple locale addition
484 { "en_MO", "en_GB" },
485 { "en_MS", "en_GB" }, // Montserrat
486 { "en_MT", "en_GB" },
487 { "en_MU", "en_GB" }, // Mauritius
488 { "en_MV", "en_GB" },
489 { "en_MW", "en_GB" }, // Malawi
490 { "en_MY", "en_GB" }, // Apple custom parent
491 { "en_NA", "en_GB" }, // Namibia
492 { "en_NF", "en_AU" }, // Norfolk Island
493 { "en_NG", "en_GB" }, // Nigeria
494 { "en_NL", "en_150" }, // Apple locale addition
495 { "en_NO", "en_150" }, // Apple locale addition
496 { "en_NR", "en_AU" }, // Nauru
497 { "en_NU", "en_AU" }, // Niue (maybe to en_NZ instead?)
498 { "en_NZ", "en_AU" },
499 { "en_PG", "en_AU" }, // Papua New Guinea
500 { "en_PK", "en_GB" }, // Apple custom parent
501 { "en_PL", "en_150" }, // Apple locale addition
502 { "en_PN", "en_GB" }, // Pitcairn Islands
503 { "en_PT", "en_150" }, // Apple locale addition
504 { "en_RO", "en_150" }, // Apple locale addition
505 { "en_RU", "en_150" }, // Apple locale addition
506 { "en_SB", "en_GB" }, // Solomon Islands
507 { "en_SC", "en_GB" }, // Seychelles
508 { "en_SD", "en_GB" }, // Sudan
509 { "en_SE", "en_150" }, // Apple locale addition
510 { "en_SG", "en_GB" },
511 { "en_SH", "en_GB" },
512 { "en_SI", "en_150" }, // Apple locale addition
513 { "en_SK", "en_150" }, // Apple locale addition
514 { "en_SL", "en_GB" }, // Sierra Leone
515 { "en_SS", "en_GB" }, // South Sudan
516 { "en_SZ", "en_GB" }, // Swaziland
517 { "en_TC", "en_GB" }, // Tristan da Cunha
518 { "en_TO", "en_GB" }, // Tonga
519 { "en_TT", "en_GB" }, // Trinidad & Tobago
520 { "en_TV", "en_GB" }, // Tuvalu
521 { "en_TZ", "en_GB" }, // Tanzania
522 { "en_UG", "en_GB" }, // Uganda
523 { "en_VC", "en_GB" }, // St. Vincent & Grenadines
524 { "en_VG", "en_GB" },
525 { "en_VU", "en_GB" }, // Vanuatu
526 { "en_WS", "en_AU" }, // Samoa (maybe to en_NZ instead?)
527 { "en_ZA", "en_GB" }, // South Africa
528 { "en_ZM", "en_GB" }, // Zambia
529 { "en_ZW", "en_GB" }, // Zimbabwe
530 };
531 enum { kAppleParentMapCount = UPRV_LENGTHOF(appleParentMap) };
532
533 typedef struct {
534 const char * locale;
535 const char * parent;
536 int8_t distance;
537 } LocParentAndDistance;
538
539 static LocParentAndDistance locParentMap[] = {
540 // The localizations listed in the first column are in
541 // normalized form (e.g. zh_CN -> zh_Hans_CN, etc.).
542 // The distance is a rough measure of distance from
543 // the localization to its parent, used as a weight.
544 { "en_001", "en", 2 },
545 { "en_150", "en_GB", 1 },
546 { "en_AU", "en_GB", 1 },
547 { "en_GB", "en_001", 0 },
548 { "en_US", "en", 0 },
549 { "es_419", "es", 2 },
550 { "es_MX", "es_419", 0 },
551 { "pt_PT", "pt", 2 },
552 { "yue_Hans_CN","yue_Hans",0 },
553 { "yue_Hant_HK","yue_Hant",0 },
554 { "zh_Hans_CN", "zh_Hans", 0 },
555 { "zh_Hant_HK", "zh_Hant", 1 },
556 { "zh_Hant_TW", "zh_Hant", 0 },
557 };
558 enum { kLocParentMapCount = UPRV_LENGTHOF(locParentMap), kMaxParentDistance = 8 };
559
560 enum {
561 kStringsAllocSize = 4480, // cannot expand; current actual usage 4150
562 kParentMapInitCount = 205 // can expand; current actual usage 205
563 };
564
565 U_CDECL_BEGIN
566 static UBool U_CALLCONV ualocale_cleanup(void);
567 U_CDECL_END
568
569 U_NAMESPACE_BEGIN
570
571 static UInitOnce gUALocaleCacheInitOnce = U_INITONCE_INITIALIZER;
572
573 static int gMapDataState = 0; // 0 = not initialized, 1 = initialized, -1 = failure
574 static char* gStrings = NULL;
575 static UHashtable* gAliasMap = NULL;
576 static UHashtable* gParentMap = NULL;
577
578 U_NAMESPACE_END
579
580 U_CDECL_BEGIN
581
582 static UBool U_CALLCONV ualocale_cleanup(void)
583 {
584 U_NAMESPACE_USE
585
586 gUALocaleCacheInitOnce.reset();
587
588 if (gMapDataState > 0) {
589 uhash_close(gParentMap);
590 gParentMap = NULL;
591 uhash_close(gAliasMap);
592 gAliasMap = NULL;
593 uprv_free(gStrings);
594 gStrings = NULL;
595 }
596 gMapDataState = 0;
597 return TRUE;
598 }
599
600 static void initializeMapData() {
601 U_NAMESPACE_USE
602
603 UResourceBundle * curBundle;
604 char* stringsPtr;
605 char* stringsEnd;
606 UErrorCode status;
607 int32_t entryIndex, icuEntryCount;
608
609 ucln_common_registerCleanup(UCLN_COMMON_LOCALE, ualocale_cleanup);
610
611 gStrings = (char*)uprv_malloc(kStringsAllocSize);
612 if (gStrings) {
613 stringsPtr = gStrings;
614 stringsEnd = gStrings + kStringsAllocSize;
615 }
616
617 status = U_ZERO_ERROR;
618 curBundle = NULL;
619 icuEntryCount = 0;
620 if (gStrings) {
621 curBundle = ures_openDirect(NULL, "metadata", &status);
622 curBundle = ures_getByKey(curBundle, "alias", curBundle, &status);
623 curBundle = ures_getByKey(curBundle, "language", curBundle, &status); // language resource is URES_TABLE
624 if (U_SUCCESS(status)) {
625 icuEntryCount = ures_getSize(curBundle); // currently 331
626 }
627 }
628 status = U_ZERO_ERROR;
629 gAliasMap = uhash_openSize(uhash_hashIChars, uhash_compareIChars, uhash_compareIChars,
630 kAppleAliasMapCount + icuEntryCount, &status);
631 // defaults to keyDeleter NULL
632 if (U_SUCCESS(status)) {
633 for (entryIndex = 0; entryIndex < kAppleAliasMapCount && U_SUCCESS(status); entryIndex++) {
634 uhash_put(gAliasMap, (void*)appleAliasMap[entryIndex][0], (void*)appleAliasMap[entryIndex][1], &status);
635 }
636 status = U_ZERO_ERROR;
637 UResourceBundle * aliasMapBundle = NULL;
638 for (entryIndex = 0; entryIndex < icuEntryCount && U_SUCCESS(status); entryIndex++) {
639 aliasMapBundle = ures_getByIndex(curBundle, entryIndex, aliasMapBundle, &status);
640 if (U_FAILURE(status)) {
641 break; // error
642 }
643 const char * keyStr = ures_getKey(aliasMapBundle);
644 int32_t len = uprv_strlen(keyStr);
645 if (len >= stringsEnd - stringsPtr) {
646 break; // error
647 }
648 uprv_strcpy(stringsPtr, keyStr);
649 char * inLocStr = stringsPtr;
650 stringsPtr += len + 1;
651
652 len = stringsEnd - stringsPtr - 1;
653 ures_getUTF8StringByKey(aliasMapBundle, "replacement", stringsPtr, &len, TRUE, &status);
654 if (U_FAILURE(status)) {
655 break; // error
656 }
657 stringsPtr[len] = 0;
658 uhash_put(gAliasMap, inLocStr, stringsPtr, &status);
659 stringsPtr += len + 1;
660 }
661 ures_close(aliasMapBundle);
662 } else {
663 ures_close(curBundle);
664 uprv_free(gStrings);
665 gMapDataState = -1; // failure
666 return;
667 }
668 ures_close(curBundle);
669
670 status = U_ZERO_ERROR;
671 gParentMap = uhash_openSize(uhash_hashIChars, uhash_compareIChars, uhash_compareIChars,
672 kParentMapInitCount, &status);
673 // defaults to keyDeleter NULL
674 if (U_SUCCESS(status)) {
675 curBundle = ures_openDirect(NULL, "supplementalData", &status);
676 curBundle = ures_getByKey(curBundle, "parentLocales", curBundle, &status); // parentLocales resource is URES_TABLE
677 if (U_SUCCESS(status)) {
678 UResourceBundle * parentMapBundle = NULL;
679 while (TRUE) {
680 parentMapBundle = ures_getNextResource(curBundle, parentMapBundle, &status);
681 if (U_FAILURE(status)) {
682 break; // no more parent bundles, normal exit
683 }
684 const char * keyStr = ures_getKey(parentMapBundle);
685 int32_t len = uprv_strlen(keyStr);
686 if (len >= stringsEnd - stringsPtr) {
687 break; // error
688 }
689 uprv_strcpy(stringsPtr, keyStr);
690 char * parentStr = stringsPtr;
691 stringsPtr += len + 1;
692
693 if (ures_getType(parentMapBundle) == URES_STRING) {
694 len = stringsEnd - stringsPtr - 1;
695 ures_getUTF8String(parentMapBundle, stringsPtr, &len, TRUE, &status);
696 if (U_FAILURE(status)) {
697 break; // error
698 }
699 stringsPtr[len] = 0;
700 uhash_put(gParentMap, stringsPtr, parentStr, &status);
701 stringsPtr += len + 1;
702 } else {
703 // should be URES_ARRAY
704 icuEntryCount = ures_getSize(parentMapBundle);
705 for (entryIndex = 0; entryIndex < icuEntryCount && U_SUCCESS(status); entryIndex++) {
706 len = stringsEnd - stringsPtr - 1;
707 ures_getUTF8StringByIndex(parentMapBundle, entryIndex, stringsPtr, &len, TRUE, &status);
708 if (U_FAILURE(status)) {
709 break;
710 }
711 stringsPtr[len] = 0;
712 uhash_put(gParentMap, stringsPtr, parentStr, &status);
713 stringsPtr += len + 1;
714 }
715 }
716 }
717 ures_close(parentMapBundle);
718 }
719 ures_close(curBundle);
720
721 status = U_ZERO_ERROR;
722 for (entryIndex = 0; entryIndex < kAppleParentMapCount && U_SUCCESS(status); entryIndex++) {
723 uhash_put(gParentMap, (void*)appleParentMap[entryIndex][0], (void*)appleParentMap[entryIndex][1], &status);
724 }
725 } else {
726 uhash_close(gAliasMap);
727 gAliasMap = NULL;
728 uprv_free(gStrings);
729 gMapDataState = -1; // failure
730 return;
731 }
732
733 #if DEBUG_UALOC
734 printf("# gStrings size %ld\n", stringsPtr - gStrings);
735 printf("# gParentMap count %d\n", uhash_count(gParentMap));
736 #endif
737 gMapDataState = 1;
738 }
739
740 U_CDECL_END
741
742 // The following maps aliases, etc. Ensures 0-termination if no error.
743 static void ualoc_normalize(const char *locale, char *normalized, int32_t normalizedCapacity, UErrorCode *status)
744 {
745 if (U_FAILURE(*status)) {
746 return;
747 }
748 // uloc_minimizeSubtags(locale, normalized, normalizedCapacity, status);
749
750 const char *replacement = NULL;
751 if (icu::gMapDataState > 0) {
752 replacement = (const char *)uhash_get(icu::gAliasMap, locale);
753 }
754 if (replacement == NULL) {
755 replacement = locale;
756 }
757 int32_t len = strnlen(replacement, normalizedCapacity);
758 if (len < normalizedCapacity) { // allow for 0 termination
759 uprv_strcpy(normalized, replacement);
760 } else {
761 *status = U_BUFFER_OVERFLOW_ERROR;
762 }
763 }
764
765 static void ualoc_getParent(const char *locale, char *parent, int32_t parentCapacity, UErrorCode *status)
766 {
767 if (U_FAILURE(*status)) {
768 return;
769 }
770 if (icu::gMapDataState > 0) {
771 const char *replacement = (const char *)uhash_get(icu::gParentMap, locale);
772 if (replacement) {
773 int32_t len = uprv_strlen(replacement);
774 if (len < parentCapacity) { // allow for 0 termination
775 uprv_strcpy(parent, replacement);
776 } else {
777 *status = U_BUFFER_OVERFLOW_ERROR;
778 }
779 return;
780 }
781 }
782 uloc_getParent(locale, parent, parentCapacity - 1, status);
783 parent[parentCapacity - 1] = 0; // ensure 0 termination in case of U_STRING_NOT_TERMINATED_WARNING
784 }
785
786 // Might do something better for this, perhaps maximizing locales then stripping
787 static const char * getLocParent(const char *locale, int32_t* distance)
788 {
789 int32_t locParentIndex;
790 for (locParentIndex = 0; locParentIndex < kLocParentMapCount; locParentIndex++) {
791 if (uprv_strcmp(locale, locParentMap[locParentIndex].locale) == 0) {
792 *distance = locParentMap[locParentIndex].distance;
793 return locParentMap[locParentIndex].parent;
794 }
795 }
796 if (icu::gMapDataState > 0) {
797 const char *replacement = (const char *)uhash_get(icu::gParentMap, locale);
798 if (replacement) {
799 *distance = 1;
800 return replacement;
801 }
802 }
803 return NULL;
804 }
805
806 // this just checks if the *pointer* value is already in the array
807 static UBool locInArray(const char* *localizationsToUse, int32_t locsToUseCount, const char *locToCheck)
808 {
809 int32_t locIndex;
810 for (locIndex = 0; locIndex < locsToUseCount; locIndex++) {
811 if (locToCheck == localizationsToUse[locIndex]) {
812 return TRUE;
813 }
814 }
815 return FALSE;
816 }
817
818 enum { kLangScriptRegMaxLen = ULOC_LANG_CAPACITY + ULOC_SCRIPT_CAPACITY + ULOC_COUNTRY_CAPACITY }; // currently 22
819
820 int32_t
821 ualoc_localizationsToUse( const char* const *preferredLanguages,
822 int32_t preferredLanguagesCount,
823 const char* const *availableLocalizations,
824 int32_t availableLocalizationsCount,
825 const char* *localizationsToUse,
826 int32_t localizationsToUseCapacity,
827 UErrorCode *status )
828 {
829 if (U_FAILURE(*status)) {
830 return -1;
831 }
832 if (preferredLanguages == NULL || availableLocalizations == NULL || localizationsToUse == NULL) {
833 *status = U_ILLEGAL_ARGUMENT_ERROR;
834 return -1;
835 }
836 // get resource data, need to protect with mutex
837 if (icu::gMapDataState == 0) {
838 umtx_initOnce(icu::gUALocaleCacheInitOnce, initializeMapData);
839 }
840 int32_t locsToUseCount = 0;
841 int32_t prefLangIndex, availLocIndex = 0;
842 int32_t availLocIndexBackup = -1; // if >= 0, contains index of backup match
843 int32_t foundMatchPrefLangIndex = 0, backupMatchPrefLangIndex = 0;
844 char (*availLocBase)[kLangScriptRegMaxLen + 1] = NULL;
845 char (*availLocNorm)[kLangScriptRegMaxLen + 1] = NULL;
846 UBool foundMatch = FALSE;
847 UBool backupMatchPrefLang_pt_PT = FALSE;
848
849 #if DEBUG_UALOC
850 if (preferredLanguagesCount > 0 && availableLocalizationsCount > 0) {
851 printf("\n # ualoc_localizationsToUse start, preferredLanguages %d: %s, ..., availableLocalizations %d: %s, ...\n",
852 preferredLanguagesCount, preferredLanguages[0], availableLocalizationsCount, availableLocalizations[0]);
853 } else {
854 printf("\n # ualoc_localizationsToUse start, preferredLanguages %d: ..., availableLocalizations %d: ...\n",
855 preferredLanguagesCount, availableLocalizationsCount);
856 }
857 #endif
858
859 // Part 1, find the best matching localization, if any
860 for (prefLangIndex = 0; prefLangIndex < preferredLanguagesCount; prefLangIndex++) {
861 char prefLangBaseName[kLangScriptRegMaxLen + 1];
862 char prefLangNormName[kLangScriptRegMaxLen + 1];
863 char prefLangParentName[kLangScriptRegMaxLen + 1];
864 UErrorCode tmpStatus = U_ZERO_ERROR;
865
866 if (preferredLanguages[prefLangIndex] == NULL) {
867 continue; // skip NULL preferredLanguages entry, go to next one
868 }
869 // use underscores, fix bad capitalization, delete any keywords
870 uloc_getBaseName(preferredLanguages[prefLangIndex], prefLangBaseName, kLangScriptRegMaxLen, &tmpStatus);
871 if (U_FAILURE(tmpStatus) || prefLangBaseName[0] == 0 ||
872 uprv_strcmp(prefLangBaseName, "root") == 0 || prefLangBaseName[0] == '_') {
873 continue; // can't handle this preferredLanguages entry or it is invalid, go to next one
874 }
875 prefLangBaseName[kLangScriptRegMaxLen] = 0; // ensure 0 termination, could have U_STRING_NOT_TERMINATED_WARNING
876 #if DEBUG_UALOC
877 printf(" # loop: try prefLangBaseName %s\n", prefLangBaseName);
878 #endif
879
880 // if we have not already allocated and filled the array of
881 // base availableLocalizations, do so now.
882 if (availLocBase == NULL) {
883 availLocBase = (char (*)[kLangScriptRegMaxLen + 1])uprv_malloc(availableLocalizationsCount * (kLangScriptRegMaxLen + 1));
884 if (availLocBase == NULL) {
885 continue; // cannot further check this preferredLanguages entry, go to next one
886 }
887 #if DEBUG_UALOC
888 printf(" # allocate & fill availLocBase\n");
889 #endif
890 for (availLocIndex = 0; availLocIndex < availableLocalizationsCount; availLocIndex++) {
891 tmpStatus = U_ZERO_ERROR;
892 if (availableLocalizations[availLocIndex] == NULL) {
893 availLocBase[availLocIndex][0] = 0; // effectively remove this entry
894 continue;
895 }
896 uloc_getBaseName(availableLocalizations[availLocIndex], availLocBase[availLocIndex], kLangScriptRegMaxLen, &tmpStatus);
897 if (U_FAILURE(tmpStatus) || uprv_strcmp(availLocBase[availLocIndex], "root") == 0 || availLocBase[availLocIndex][0] == '_') {
898 availLocBase[availLocIndex][0] = 0; // effectively remove this entry
899 continue;
900 }
901 availLocBase[availLocIndex][kLangScriptRegMaxLen] = 0; // ensure 0 termination, could have U_STRING_NOT_TERMINATED_WARNING
902 #if DEBUG_UALOC
903 printf(" # add availLocBase %s\n", availLocBase[availLocIndex]);
904 #endif
905 }
906 }
907 // first compare base preferredLanguage to base versions of availableLocalizations names
908 for (availLocIndex = 0; availLocIndex < availableLocalizationsCount; availLocIndex++) {
909 if (uprv_strcmp(prefLangBaseName, availLocBase[availLocIndex]) == 0) {
910 foundMatch = TRUE; // availLocIndex records where
911 foundMatchPrefLangIndex = prefLangIndex;
912 #if DEBUG_UALOC
913 printf(" # FOUND: matched availLocBase %s -> actualLoc %s\n", availLocBase[availLocIndex], availableLocalizations[availLocIndex]);
914 #endif
915 break;
916 }
917 }
918 if (foundMatch) {
919 break; // found a loc for this preferredLanguages entry
920 }
921
922 // get normalized preferredLanguage
923 tmpStatus = U_ZERO_ERROR;
924 ualoc_normalize(prefLangBaseName, prefLangNormName, kLangScriptRegMaxLen + 1, &tmpStatus);
925 if (U_FAILURE(tmpStatus)) {
926 continue; // can't handle this preferredLanguages entry, go to next one
927 }
928 #if DEBUG_UALOC
929 printf(" # prefLangNormName %s\n", prefLangNormName);
930 #endif
931 // if we have not already allocated and filled the array of
932 // normalized availableLocalizations, do so now.
933 // Note: ualoc_normalize turns "zh_TW" into "zh_Hant_TW", zh_HK" into "zh_Hant_HK",
934 // and fixes deprecated codes "iw" > "he", "in" > "id" etc.
935 if (availLocNorm == NULL) {
936 availLocNorm = (char (*)[kLangScriptRegMaxLen + 1])uprv_malloc(availableLocalizationsCount * (kLangScriptRegMaxLen + 1));
937 if (availLocNorm == NULL) {
938 continue; // cannot further check this preferredLanguages entry, go to next one
939 }
940 #if DEBUG_UALOC
941 printf(" # allocate & fill availLocNorm\n");
942 #endif
943 for (availLocIndex = 0; availLocIndex < availableLocalizationsCount; availLocIndex++) {
944 tmpStatus = U_ZERO_ERROR;
945 ualoc_normalize(availLocBase[availLocIndex], availLocNorm[availLocIndex], kLangScriptRegMaxLen + 1, &tmpStatus);
946 if (U_FAILURE(tmpStatus)) {
947 availLocNorm[availLocIndex][0] = 0; // effectively remove this entry
948 #if DEBUG_UALOC
949 } else {
950 printf(" # actualLoc %-11s -> norm %s\n", availableLocalizations[availLocIndex], availLocNorm[availLocIndex]);
951 #endif
952 }
953 }
954 }
955 // now compare normalized preferredLanguage to normalized localization names
956 // if matches, copy *original* localization name
957 for (availLocIndex = 0; availLocIndex < availableLocalizationsCount; availLocIndex++) {
958 if (uprv_strcmp(prefLangNormName, availLocNorm[availLocIndex]) == 0) {
959 foundMatch = TRUE; // availLocIndex records where
960 foundMatchPrefLangIndex = prefLangIndex;
961 #if DEBUG_UALOC
962 printf(" # FOUND: matched availLocNorm %s -> actualLoc %s\n", availLocNorm[availLocIndex], availableLocalizations[availLocIndex]);
963 #endif
964 break;
965 }
966 }
967 if (foundMatch) {
968 break; // found a loc for this preferredLanguages entry
969 }
970
971 // now walk up the parent chain for preferredLanguage
972 // until we find a match or hit root
973 uprv_strcpy(prefLangBaseName, prefLangNormName);
974 while (!foundMatch) {
975 tmpStatus = U_ZERO_ERROR;
976 ualoc_getParent(prefLangBaseName, prefLangParentName, kLangScriptRegMaxLen + 1, &tmpStatus);
977 if (U_FAILURE(tmpStatus) || uprv_strcmp(prefLangParentName, "root") == 0 || prefLangParentName[0] == 0) {
978 break; // reached root or cannot proceed further
979 }
980 #if DEBUG_UALOC
981 printf(" # prefLangParentName %s\n", prefLangParentName);
982 #endif
983
984 // now compare this preferredLanguage parent to normalized localization names
985 // if matches, copy *original* localization name
986 for (availLocIndex = 0; availLocIndex < availableLocalizationsCount; availLocIndex++) {
987 if (uprv_strcmp(prefLangParentName, availLocNorm[availLocIndex]) == 0) {
988 foundMatch = TRUE; // availLocIndex records where
989 foundMatchPrefLangIndex = prefLangIndex;
990 #if DEBUG_UALOC
991 printf(" # FOUND: matched availLocNorm %s -> actualLoc %s\n", availLocNorm[availLocIndex], availableLocalizations[availLocIndex]);
992 #endif
993 break;
994 }
995 }
996 uprv_strcpy(prefLangBaseName, prefLangParentName);
997 }
998 if (foundMatch) {
999 break; // found a loc for this preferredLanguages entry
1000 }
1001
1002 // last try, use parents of selected language to try for backup match
1003 // if we have not already found one
1004 if (availLocIndexBackup < 0) {
1005 // now walk up the parent chain for preferredLanguage again
1006 // checking against parents of selected availLocNorm entries
1007 // but this time start with current prefLangNormName
1008 uprv_strcpy(prefLangBaseName, prefLangNormName);
1009 int32_t minDistance = kMaxParentDistance;
1010 while (TRUE) {
1011 // now compare this preferredLanguage to normalized localization names
1012 // parent if have one for this; if matches, copy *original* localization name
1013 #if DEBUG_UALOC
1014 printf(" # BACKUP: trying prefLangBaseName %s\n", prefLangBaseName);
1015 #endif
1016 for (availLocIndex = 0; availLocIndex < availableLocalizationsCount; availLocIndex++) {
1017 char availLocMinOrParent[kLangScriptRegMaxLen + 1];
1018 int32_t distance;
1019 // first check for special Apple parents of availLocNorm; the number
1020 // of locales with such parents is small.
1021 // If no such parent, or if parent has an intermediate numeric region,
1022 // then try stripping the original region.
1023 int32_t availLocParentLen = 0;
1024 const char *availLocParent = getLocParent(availLocNorm[availLocIndex], &distance);
1025 if (availLocParent) {
1026 #if DEBUG_UALOC
1027 printf(" # availLocAppleParentName %s\n", availLocParent);
1028 #endif
1029 if (uprv_strcmp(prefLangBaseName, availLocParent) == 0 && distance < minDistance) {
1030 availLocIndexBackup = availLocIndex; // records where the match occurred
1031 backupMatchPrefLangIndex = prefLangIndex;
1032 minDistance = distance;
1033 #if DEBUG_UALOC
1034 printf(" # BACKUP: LocAppleParent matched prefLangNormName with distance %d\n", distance);
1035 #endif
1036 continue;
1037 }
1038 availLocParentLen = uprv_strlen(availLocParent);
1039 }
1040 if (minDistance <= 1) {
1041 continue; // we can't get any closer in the rest of this iteration
1042 }
1043 if (availLocParent == NULL || (availLocParentLen >= 6 && isdigit(availLocParent[availLocParentLen-1]))) {
1044 tmpStatus = U_ZERO_ERROR;
1045 int32_t regLen = uloc_getCountry(availLocNorm[availLocIndex], availLocMinOrParent, kLangScriptRegMaxLen, &tmpStatus);
1046 if (U_SUCCESS(tmpStatus) && regLen > 1) {
1047 uloc_addLikelySubtags(availLocNorm[availLocIndex], availLocMinOrParent, kLangScriptRegMaxLen, &tmpStatus);
1048 if (U_SUCCESS(tmpStatus)) {
1049 availLocMinOrParent[kLangScriptRegMaxLen] = 0; // ensure 0 termination, could have U_STRING_NOT_TERMINATED_WARNING
1050 #if DEBUG_UALOC
1051 printf(" # availLocRegMaxName %s\n", availLocMinOrParent);
1052 #endif
1053 char availLocTemp[kLangScriptRegMaxLen + 1];
1054 uloc_getParent(availLocMinOrParent, availLocTemp, kLangScriptRegMaxLen, &tmpStatus);
1055 if (U_SUCCESS(tmpStatus)) {
1056 availLocTemp[kLangScriptRegMaxLen] = 0;
1057 uloc_minimizeSubtags(availLocTemp, availLocMinOrParent, kLangScriptRegMaxLen, &tmpStatus);
1058 if (U_SUCCESS(tmpStatus)) {
1059 availLocMinOrParent[kLangScriptRegMaxLen] = 0;
1060 #if DEBUG_UALOC
1061 printf(" # availLocNoRegParentName %s\n", availLocMinOrParent);
1062 #endif
1063 if (uprv_strcmp(prefLangBaseName, availLocMinOrParent) == 0) {
1064 availLocIndexBackup = availLocIndex; // records where the match occurred
1065 backupMatchPrefLangIndex = prefLangIndex;
1066 minDistance = 1;
1067 backupMatchPrefLang_pt_PT = (uprv_strcmp(prefLangNormName, "pt_PT") == 0);
1068 #if DEBUG_UALOC
1069 printf(" # BACKUP: LocNoRegParent matched prefLangNormName with distance 1\n");
1070 #endif
1071 continue;
1072 }
1073 }
1074 }
1075 }
1076 }
1077 }
1078 // then check against minimized version of availLocNorm
1079 tmpStatus = U_ZERO_ERROR;
1080 uloc_minimizeSubtags(availLocNorm[availLocIndex], availLocMinOrParent, kLangScriptRegMaxLen, &tmpStatus);
1081 if (U_FAILURE(tmpStatus)) {
1082 continue;
1083 }
1084 availLocMinOrParent[kLangScriptRegMaxLen] = 0; // ensure 0 termination, could have U_STRING_NOT_TERMINATED_WARNING
1085 #if DEBUG_UALOC
1086 printf(" # availLocMinimized %s\n", availLocMinOrParent);
1087 #endif
1088 if (uprv_strcmp(prefLangBaseName, availLocMinOrParent) == 0) {
1089 availLocIndexBackup = availLocIndex; // records where the match occurred
1090 backupMatchPrefLangIndex = prefLangIndex;
1091 minDistance = 1;
1092 #if DEBUG_UALOC
1093 printf(" # BACKUP: LocMinimized matched prefLangNormName with distance 1\n");
1094 #endif
1095 }
1096 }
1097 if (availLocIndexBackup >= 0) {
1098 break;
1099 }
1100 tmpStatus = U_ZERO_ERROR;
1101 ualoc_getParent(prefLangBaseName, prefLangParentName, kLangScriptRegMaxLen + 1, &tmpStatus);
1102 if (U_FAILURE(tmpStatus) || uprv_strcmp(prefLangParentName, "root") == 0 || prefLangParentName[0] == 0) {
1103 break; // reached root or cannot proceed further
1104 }
1105 uprv_strcpy(prefLangBaseName, prefLangParentName);
1106 }
1107 }
1108 }
1109 // If we have a backup match, decide what to do
1110 if (availLocIndexBackup >= 0) {
1111 if (!foundMatch) {
1112 // no main match, just use the backup
1113 availLocIndex = availLocIndexBackup;
1114 foundMatch = TRUE;
1115 #if DEBUG_UALOC
1116 printf(" # no main match, have backup => use availLocIndexBackup %d\n", availLocIndexBackup);
1117 #endif
1118 } else if (backupMatchPrefLangIndex < foundMatchPrefLangIndex && (!backupMatchPrefLang_pt_PT || uprv_strcmp(availLocNorm[availLocIndexBackup], "pt_BR") != 0)) {
1119 // have a main match but backup match was higher in the prefs, use it if for a different language
1120 #if DEBUG_UALOC
1121 printf(" # have backup match higher in prefs, comparing its language and script to main match\n");
1122 #endif
1123 char mainLang[ULOC_LANG_CAPACITY + 1];
1124 char backupLang[ULOC_LANG_CAPACITY + 1];
1125 UErrorCode tmpStatus = U_ZERO_ERROR;
1126 uloc_getLanguage(availLocNorm[availLocIndex], mainLang, ULOC_LANG_CAPACITY, &tmpStatus);
1127 mainLang[ULOC_LANG_CAPACITY] = 0; // ensure zero termination
1128 uloc_getLanguage(availLocNorm[availLocIndexBackup], backupLang, ULOC_LANG_CAPACITY, &tmpStatus);
1129 backupLang[ULOC_LANG_CAPACITY] = 0; // ensure zero termination
1130 if (U_SUCCESS(tmpStatus)) {
1131 if (uprv_strncmp(mainLang, backupLang, ULOC_LANG_CAPACITY) != 0) {
1132 // backup match has different language than main match
1133 availLocIndex = availLocIndexBackup;
1134 // foundMatch is already TRUE
1135 #if DEBUG_UALOC
1136 printf(" # main match but backup is for a different lang higher in prefs => use availLocIndexBackup %d\n", availLocIndexBackup);
1137 #endif
1138 } else {
1139 // backup match has same language as main match, check scripts too
1140 char availLocMaximized[kLangScriptRegMaxLen + 1];
1141
1142 uloc_addLikelySubtags(availLocNorm[availLocIndex], availLocMaximized, kLangScriptRegMaxLen, &tmpStatus);
1143 availLocMaximized[kLangScriptRegMaxLen] = 0;
1144 uloc_getScript(availLocMaximized, mainLang, ULOC_LANG_CAPACITY, &tmpStatus);
1145 mainLang[ULOC_LANG_CAPACITY] = 0;
1146
1147 uloc_addLikelySubtags(availLocNorm[availLocIndexBackup], availLocMaximized, kLangScriptRegMaxLen, &tmpStatus);
1148 availLocMaximized[kLangScriptRegMaxLen] = 0;
1149 uloc_getScript(availLocMaximized, backupLang, ULOC_LANG_CAPACITY, &tmpStatus);
1150 backupLang[ULOC_LANG_CAPACITY] = 0;
1151
1152 if (U_SUCCESS(tmpStatus) && uprv_strncmp(mainLang, backupLang, ULOC_LANG_CAPACITY) != 0) {
1153 // backup match has different script than main match
1154 availLocIndex = availLocIndexBackup;
1155 // foundMatch is already TRUE
1156 #if DEBUG_UALOC
1157 printf(" # main match but backup is for a different script higher in prefs => use availLocIndexBackup %d\n", availLocIndexBackup);
1158 #endif
1159 }
1160 }
1161 }
1162 }
1163 }
1164
1165 // Part 2, if we found a matching localization, then walk up its parent tree to find any fallback matches in availableLocalizations
1166 if (foundMatch) {
1167 // Here availLocIndex corresponds to the first matched localization
1168 UErrorCode tmpStatus = U_ZERO_ERROR;
1169 int32_t availLocMatchIndex = availLocIndex;
1170 if (locsToUseCount < localizationsToUseCapacity) {
1171 localizationsToUse[locsToUseCount++] = availableLocalizations[availLocMatchIndex];
1172 }
1173 // at this point we must have availLocBase, and minimally matched against that.
1174 // if we have not already allocated and filled the array of
1175 // normalized availableLocalizations, do so now, but don't require it
1176 if (availLocNorm == NULL) {
1177 availLocNorm = (char (*)[kLangScriptRegMaxLen + 1])uprv_malloc(availableLocalizationsCount * (kLangScriptRegMaxLen + 1));
1178 if (availLocNorm != NULL) {
1179 for (availLocIndex = 0; availLocIndex < availableLocalizationsCount; availLocIndex++) {
1180 tmpStatus = U_ZERO_ERROR;
1181 ualoc_normalize(availLocBase[availLocIndex], availLocNorm[availLocIndex], kLangScriptRegMaxLen + 1, &tmpStatus);
1182 if (U_FAILURE(tmpStatus)) {
1183 availLocNorm[availLocIndex][0] = 0; // effectively remove this entry
1184 }
1185 }
1186 }
1187 }
1188
1189 // add normalized form of matching loc, if different and in availLocBase
1190 if (locsToUseCount < localizationsToUseCapacity) {
1191 tmpStatus = U_ZERO_ERROR;
1192 char matchedLocNormName[kLangScriptRegMaxLen + 1];
1193 char matchedLocParentName[kLangScriptRegMaxLen + 1];
1194 // get normalized form of matching loc
1195 if (availLocNorm != NULL) {
1196 uprv_strcpy(matchedLocNormName, availLocNorm[availLocMatchIndex]);
1197 } else {
1198 ualoc_normalize(availLocBase[availLocMatchIndex], matchedLocNormName, kLangScriptRegMaxLen + 1, &tmpStatus);
1199 }
1200 if (U_SUCCESS(tmpStatus)) {
1201 // add normalized form of matching loc, if different and in availLocBase
1202 if (uprv_strcmp(matchedLocNormName, localizationsToUse[0]) != 0) {
1203 // normalization of matched localization is different, see if we have the normalization in availableLocalizations
1204 // from this point on, availLocIndex no longer corresponds to the matched localization.
1205 for (availLocIndex = 0; availLocIndex < availableLocalizationsCount; availLocIndex++) {
1206 if ( (uprv_strcmp(matchedLocNormName, availLocBase[availLocIndex]) == 0
1207 || (availLocNorm != NULL && uprv_strcmp(matchedLocNormName, availLocNorm[availLocIndex]) == 0))
1208 && !locInArray(localizationsToUse, locsToUseCount, availableLocalizations[availLocIndex])) {
1209 localizationsToUse[locsToUseCount++] = availableLocalizations[availLocIndex];
1210 break;
1211 }
1212 }
1213 }
1214
1215 // now walk up the parent chain from matchedLocNormName, adding parents if they are in availLocBase
1216 while (locsToUseCount < localizationsToUseCapacity) {
1217 ualoc_getParent(matchedLocNormName, matchedLocParentName, kLangScriptRegMaxLen + 1, &tmpStatus);
1218 if (U_FAILURE(tmpStatus) || uprv_strcmp(matchedLocParentName, "root") == 0 || matchedLocParentName[0] == 0) {
1219 break; // reached root or cannot proceed further
1220 }
1221
1222 // now compare this matchedLocParentName parent to base localization names (and norm ones if we have them)
1223 for (availLocIndex = 0; availLocIndex < availableLocalizationsCount; availLocIndex++) {
1224 if ( (uprv_strcmp(matchedLocParentName, availLocBase[availLocIndex]) == 0
1225 || (availLocNorm != NULL && uprv_strcmp(matchedLocParentName, availLocNorm[availLocIndex]) == 0))
1226 && !locInArray(localizationsToUse, locsToUseCount, availableLocalizations[availLocIndex])) {
1227 localizationsToUse[locsToUseCount++] = availableLocalizations[availLocIndex];
1228 break;
1229 }
1230 }
1231 uprv_strcpy(matchedLocNormName, matchedLocParentName);
1232 }
1233
1234 // The above still fails to include "zh_TW" if it is in availLocBase and the matched localization
1235 // base name is "zh_HK" or "zh_MO". One option would be to walk up the parent chain from
1236 // matchedLocNormName again, comparing against parents of of selected availLocNorm entries.
1237 // But this picks up too many matches that are not parents of the matched localization. So
1238 // we just handle these specially.
1239 if ( locsToUseCount < localizationsToUseCapacity
1240 && (uprv_strcmp(availLocBase[availLocMatchIndex], "zh_HK") == 0
1241 || uprv_strcmp(availLocBase[availLocMatchIndex], "zh_MO") == 0) ) {
1242 int32_t zhTW_matchIndex = -1;
1243 UBool zhHant_found = FALSE;
1244 for (availLocIndex = 0; availLocIndex < availableLocalizationsCount; availLocIndex++) {
1245 if ( zhTW_matchIndex < 0 && uprv_strcmp("zh_TW", availLocBase[availLocIndex]) == 0 ) {
1246 zhTW_matchIndex = availLocIndex;
1247 }
1248 if ( !zhHant_found && uprv_strcmp("zh_Hant", availLocBase[availLocIndex]) == 0 ) {
1249 zhHant_found = TRUE;
1250 }
1251 }
1252 if (zhTW_matchIndex >= 0 && !zhHant_found
1253 && !locInArray(localizationsToUse, locsToUseCount, availableLocalizations[zhTW_matchIndex])) {
1254 localizationsToUse[locsToUseCount++] = availableLocalizations[zhTW_matchIndex];
1255 }
1256 }
1257 }
1258 }
1259 }
1260
1261 uprv_free(availLocNorm);
1262 uprv_free(availLocBase);
1263 return locsToUseCount;
1264 }
1265