1 /*
2 * Copyright 2003-2012, Haiku. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Axel Dörfler, axeld@pinc-software.de
7 * Oliver Tappe, zooey@hirschkaefer.de
8 */
9
10
11 #include <unicode/uversion.h>
12 #include <LocaleRoster.h>
13
14 #include <assert.h>
15 #include <ctype.h>
16
17 #include <new>
18
19 #include <Autolock.h>
20 #include <Bitmap.h>
21 #include <Catalog.h>
22 #include <Entry.h>
23 #include <FormattingConventions.h>
24 #include <fs_attr.h>
25 #include <IconUtils.h>
26 #include <Language.h>
27 #include <Locale.h>
28 #include <LocaleRosterData.h>
29 #include <MutableLocaleRoster.h>
30 #include <Node.h>
31 #include <Roster.h>
32 #include <String.h>
33 #include <TimeZone.h>
34
35 #include <ICUWrapper.h>
36 #include <locks.h>
37
38 // ICU includes
39 #include <unicode/locdspnm.h>
40 #include <unicode/locid.h>
41 #include <unicode/timezone.h>
42
43
44 using BPrivate::CatalogAddOnInfo;
45 using BPrivate::MutableLocaleRoster;
46 U_NAMESPACE_USE
47
48
49 /*
50 * several attributes/resource-IDs used within the Locale Kit:
51 */
52 const char* BLocaleRoster::kCatLangAttr = "BEOS:LOCALE_LANGUAGE";
53 // name of catalog language, lives in every catalog file
54 const char* BLocaleRoster::kCatSigAttr = "BEOS:LOCALE_SIGNATURE";
55 // catalog signature, lives in every catalog file
56 const char* BLocaleRoster::kCatFingerprintAttr = "BEOS:LOCALE_FINGERPRINT";
57 // catalog fingerprint, may live in catalog file
58
59 const char* BLocaleRoster::kEmbeddedCatAttr = "BEOS:LOCALE_EMBEDDED_CATALOG";
60 // attribute which contains flattened data of embedded catalog
61 // this may live in an app- or add-on-file
62 int32 BLocaleRoster::kEmbeddedCatResId = 0xCADA;
63 // a unique value used to identify the resource (=> embedded CAtalog DAta)
64 // which contains flattened data of embedded catalog.
65 // this may live in an app- or add-on-file
66
67
68 static const char*
country_code_for_language(const BLanguage & language)69 country_code_for_language(const BLanguage& language)
70 {
71 if (language.IsCountrySpecific())
72 return language.CountryCode();
73
74 // TODO: implement for real! For now, we just map some well known
75 // languages to countries to make FirstBootPrompt happy.
76 switch ((tolower(language.Code()[0]) << 8) | tolower(language.Code()[1])) {
77 case 'be': // Belarus
78 return "BY";
79 case 'cs': // Czech Republic
80 return "CZ";
81 case 'da': // Denmark
82 return "DK";
83 case 'el': // Greece
84 return "GR";
85 case 'en': // United Kingdom
86 return "GB";
87 case 'hi': // India
88 return "IN";
89 case 'ja': // Japan
90 return "JP";
91 case 'ko': // South Korea
92 return "KR";
93 case 'nb': // Norway
94 return "NO";
95 case 'pa': // Pakistan
96 return "PK";
97 case 'sv': // Sweden
98 return "SE";
99 case 'uk': // Ukraine
100 return "UA";
101 case 'zh': // China
102 return "CN";
103
104 // Languages with a matching country name
105 case 'de': // Germany
106 case 'es': // Spain
107 case 'fi': // Finland
108 case 'fr': // France
109 case 'hr': // Croatia
110 case 'hu': // Hungary
111 case 'it': // Italy
112 case 'lt': // Lithuania
113 case 'nl': // Netherlands
114 case 'pl': // Poland
115 case 'pt': // Portugal
116 case 'ro': // Romania
117 case 'ru': // Russia
118 case 'sk': // Slovakia
119 return language.Code();
120 }
121
122 return NULL;
123 }
124
125
126 // #pragma mark -
127
128
BLocaleRoster()129 BLocaleRoster::BLocaleRoster()
130 :
131 fData(new(std::nothrow) BPrivate::LocaleRosterData(BLanguage("en_US"),
132 BFormattingConventions("en_US")))
133 {
134 }
135
136
~BLocaleRoster()137 BLocaleRoster::~BLocaleRoster()
138 {
139 delete fData;
140 }
141
142
143 /*static*/ BLocaleRoster*
Default()144 BLocaleRoster::Default()
145 {
146 return MutableLocaleRoster::Default();
147 }
148
149
150 status_t
Refresh()151 BLocaleRoster::Refresh()
152 {
153 return fData->Refresh();
154 }
155
156
157 status_t
GetDefaultTimeZone(BTimeZone * timezone) const158 BLocaleRoster::GetDefaultTimeZone(BTimeZone* timezone) const
159 {
160 if (!timezone)
161 return B_BAD_VALUE;
162
163 BAutolock lock(fData->fLock);
164 if (!lock.IsLocked())
165 return B_ERROR;
166
167 *timezone = fData->fDefaultTimeZone;
168
169 return B_OK;
170 }
171
172
173 const BLocale*
GetDefaultLocale() const174 BLocaleRoster::GetDefaultLocale() const
175 {
176 return &fData->fDefaultLocale;
177 }
178
179
180 status_t
GetLanguage(const char * languageCode,BLanguage ** _language) const181 BLocaleRoster::GetLanguage(const char* languageCode,
182 BLanguage** _language) const
183 {
184 if (_language == NULL || languageCode == NULL || languageCode[0] == '\0')
185 return B_BAD_VALUE;
186
187 BLanguage* language = new(std::nothrow) BLanguage(languageCode);
188 if (language == NULL)
189 return B_NO_MEMORY;
190
191 *_language = language;
192 return B_OK;
193 }
194
195
196 status_t
GetPreferredLanguages(BMessage * languages) const197 BLocaleRoster::GetPreferredLanguages(BMessage* languages) const
198 {
199 if (!languages)
200 return B_BAD_VALUE;
201
202 BAutolock lock(fData->fLock);
203 if (!lock.IsLocked())
204 return B_ERROR;
205
206 *languages = fData->fPreferredLanguages;
207
208 return B_OK;
209 }
210
211
212 /**
213 * \brief Fills \c message with 'language'-fields containing the language-
214 * ID(s) of all available languages.
215 */
216 status_t
GetAvailableLanguages(BMessage * languages) const217 BLocaleRoster::GetAvailableLanguages(BMessage* languages) const
218 {
219 if (!languages)
220 return B_BAD_VALUE;
221
222 int32_t localeCount;
223 const Locale* icuLocaleList = Locale::getAvailableLocales(localeCount);
224
225 for (int i = 0; i < localeCount; i++)
226 languages->AddString("language", icuLocaleList[i].getName());
227
228 return B_OK;
229 }
230
231
232 status_t
GetAvailableCountries(BMessage * countries) const233 BLocaleRoster::GetAvailableCountries(BMessage* countries) const
234 {
235 if (!countries)
236 return B_BAD_VALUE;
237
238 int32 i;
239 const char* const* countryList = uloc_getISOCountries();
240
241 for (i = 0; countryList[i] != NULL; i++)
242 countries->AddString("country", countryList[i]);
243
244 return B_OK;
245 }
246
247
248 status_t
GetAvailableTimeZones(BMessage * timeZones) const249 BLocaleRoster::GetAvailableTimeZones(BMessage* timeZones) const
250 {
251 if (!timeZones)
252 return B_BAD_VALUE;
253
254 status_t status = B_OK;
255
256 StringEnumeration* zoneList = TimeZone::createEnumeration();
257
258 UErrorCode icuStatus = U_ZERO_ERROR;
259 int32 count = zoneList->count(icuStatus);
260 if (U_SUCCESS(icuStatus)) {
261 for (int i = 0; i < count; ++i) {
262 const char* zoneID = zoneList->next(NULL, icuStatus);
263 if (zoneID == NULL || !U_SUCCESS(icuStatus)) {
264 status = B_ERROR;
265 break;
266 }
267 timeZones->AddString("timeZone", zoneID);
268 }
269 } else
270 status = B_ERROR;
271
272 delete zoneList;
273
274 return status;
275 }
276
277
278 status_t
GetAvailableTimeZonesWithRegionInfo(BMessage * timeZones) const279 BLocaleRoster::GetAvailableTimeZonesWithRegionInfo(BMessage* timeZones) const
280 {
281 if (!timeZones)
282 return B_BAD_VALUE;
283
284 status_t status = B_OK;
285
286 UErrorCode icuStatus = U_ZERO_ERROR;
287
288 StringEnumeration* zoneList = TimeZone::createTimeZoneIDEnumeration(
289 UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, icuStatus);
290
291 int32 count = zoneList->count(icuStatus);
292 if (U_SUCCESS(icuStatus)) {
293 for (int i = 0; i < count; ++i) {
294 const char* zoneID = zoneList->next(NULL, icuStatus);
295 if (zoneID == NULL || !U_SUCCESS(icuStatus)) {
296 status = B_ERROR;
297 break;
298 }
299 timeZones->AddString("timeZone", zoneID);
300
301 char region[5];
302 icuStatus = U_ZERO_ERROR;
303 TimeZone::getRegion(zoneID, region, 5, icuStatus);
304 if (!U_SUCCESS(icuStatus)) {
305 status = B_ERROR;
306 break;
307 }
308 timeZones->AddString("region", region);
309 }
310 } else
311 status = B_ERROR;
312
313 delete zoneList;
314
315 return status;
316 }
317
318
319 status_t
GetAvailableTimeZonesForCountry(BMessage * timeZones,const char * countryCode) const320 BLocaleRoster::GetAvailableTimeZonesForCountry(BMessage* timeZones,
321 const char* countryCode) const
322 {
323 if (!timeZones)
324 return B_BAD_VALUE;
325
326 status_t status = B_OK;
327
328 StringEnumeration* zoneList = TimeZone::createEnumeration(countryCode);
329 // countryCode == NULL will yield all timezones not bound to a country
330
331 UErrorCode icuStatus = U_ZERO_ERROR;
332 int32 count = zoneList->count(icuStatus);
333 if (U_SUCCESS(icuStatus)) {
334 for (int i = 0; i < count; ++i) {
335 const char* zoneID = zoneList->next(NULL, icuStatus);
336 if (zoneID == NULL || !U_SUCCESS(icuStatus)) {
337 status = B_ERROR;
338 break;
339 }
340 timeZones->AddString("timeZone", zoneID);
341 }
342 } else
343 status = B_ERROR;
344
345 delete zoneList;
346
347 return status;
348 }
349
350
351 status_t
GetFlagIconForCountry(BBitmap * flagIcon,const char * countryCode)352 BLocaleRoster::GetFlagIconForCountry(BBitmap* flagIcon, const char* countryCode)
353 {
354 if (countryCode == NULL)
355 return B_BAD_VALUE;
356
357 BAutolock lock(fData->fLock);
358 if (!lock.IsLocked())
359 return B_ERROR;
360
361 BResources* resources;
362 status_t status = fData->GetResources(&resources);
363 if (status != B_OK)
364 return status;
365
366 // Normalize the country code: 2 letters uppercase
367 // filter things out so that "pt_BR" gives the flag for brazil
368
369 int codeLength = strlen(countryCode);
370 if (codeLength < 2)
371 return B_BAD_VALUE;
372
373 char normalizedCode[8];
374 strcpy(normalizedCode, "flag-");
375 normalizedCode[5] = tolower(countryCode[codeLength - 2]);
376 normalizedCode[6] = tolower(countryCode[codeLength - 1]);
377 normalizedCode[7] = '\0';
378
379 size_t size;
380 const void* buffer = resources->LoadResource(B_VECTOR_ICON_TYPE,
381 normalizedCode, &size);
382 if (buffer == NULL || size == 0)
383 return B_NAME_NOT_FOUND;
384
385 return BIconUtils::GetVectorIcon(static_cast<const uint8*>(buffer), size,
386 flagIcon);
387 }
388
389
390 status_t
GetFlagIconForLanguage(BBitmap * flagIcon,const char * languageCode)391 BLocaleRoster::GetFlagIconForLanguage(BBitmap* flagIcon,
392 const char* languageCode)
393 {
394 if (languageCode == NULL || languageCode[0] == '\0'
395 || languageCode[1] == '\0')
396 return B_BAD_VALUE;
397
398 BAutolock lock(fData->fLock);
399 if (!lock.IsLocked())
400 return B_ERROR;
401
402 BResources* resources;
403 status_t status = fData->GetResources(&resources);
404 if (status != B_OK)
405 return status;
406
407 // Normalize the language code: first two letters, lowercase
408
409 char normalizedCode[3];
410 normalizedCode[0] = tolower(languageCode[0]);
411 normalizedCode[1] = tolower(languageCode[1]);
412 normalizedCode[2] = '\0';
413
414 size_t size;
415 const void* buffer = resources->LoadResource(B_VECTOR_ICON_TYPE,
416 normalizedCode, &size);
417 if (buffer != NULL && size != 0) {
418 return BIconUtils::GetVectorIcon(static_cast<const uint8*>(buffer),
419 size, flagIcon);
420 }
421
422 // There is no language flag, try to get the default country's flag for
423 // the language instead.
424
425 BLanguage language(languageCode);
426 const char* countryCode = country_code_for_language(language);
427 if (countryCode == NULL)
428 return B_NAME_NOT_FOUND;
429
430 return GetFlagIconForCountry(flagIcon, countryCode);
431 }
432
433
434 status_t
GetAvailableCatalogs(BMessage * languageList,const char * sigPattern,const char * langPattern,int32 fingerprint) const435 BLocaleRoster::GetAvailableCatalogs(BMessage* languageList,
436 const char* sigPattern, const char* langPattern, int32 fingerprint) const
437 {
438 if (languageList == NULL)
439 return B_BAD_VALUE;
440
441 BAutolock lock(fData->fLock);
442 if (!lock.IsLocked())
443 return B_ERROR;
444
445 int32 count = fData->fCatalogAddOnInfos.CountItems();
446 for (int32 i = 0; i < count; ++i) {
447 CatalogAddOnInfo* info
448 = (CatalogAddOnInfo*)fData->fCatalogAddOnInfos.ItemAt(i);
449
450 if (!info->fLanguagesFunc)
451 continue;
452
453 info->fLanguagesFunc(languageList, sigPattern, langPattern,
454 fingerprint);
455 }
456
457 return B_OK;
458 }
459
460
461 bool
IsFilesystemTranslationPreferred() const462 BLocaleRoster::IsFilesystemTranslationPreferred() const
463 {
464 BAutolock lock(fData->fLock);
465 if (!lock.IsLocked())
466 return B_ERROR;
467
468 return fData->fIsFilesystemTranslationPreferred;
469 }
470
471
472 /*! \brief Looks up a localized filename from a catalog.
473 \param localizedFileName A pre-allocated BString object for the result
474 of the lookup.
475 \param ref An entry_ref with an attribute holding data for catalog lookup.
476 \param traverse A boolean to decide if symlinks are to be traversed.
477 \return
478 - \c B_OK: success
479 - \c B_ENTRY_NOT_FOUND: failure. Attribute not found, entry not found
480 in catalog, etc
481 - other error codes: failure
482
483 Attribute format: "signature:context:string"
484 (no colon in any of signature, context and string)
485
486 Lookup is done for the top preferred language, only.
487 Lookup fails if a comment is present in the catalog entry.
488 */
489 status_t
GetLocalizedFileName(BString & localizedFileName,const entry_ref & ref,bool traverse)490 BLocaleRoster::GetLocalizedFileName(BString& localizedFileName,
491 const entry_ref& ref, bool traverse)
492 {
493 BString signature;
494 BString context;
495 BString string;
496
497 status_t status = _PrepareCatalogEntry(ref, signature, context, string,
498 traverse);
499
500 if (status != B_OK)
501 return status;
502
503 // Try to get entry_ref for signature from above
504 BRoster roster;
505 entry_ref catalogRef;
506 // The signature is missing application/
507 signature.Prepend("application/");
508 status = roster.FindApp(signature, &catalogRef);
509 if (status != B_OK)
510 return status;
511
512 BCatalog catalog(catalogRef);
513 const char* temp = catalog.GetString(string, context);
514
515 if (temp == NULL)
516 return B_ENTRY_NOT_FOUND;
517
518 localizedFileName = temp;
519 return B_OK;
520 }
521
522
523 static status_t
_InitializeCatalog(void * param)524 _InitializeCatalog(void* param)
525 {
526 BCatalog* catalog = (BCatalog*)param;
527
528 // figure out image (shared object) from catalog address
529 image_info info;
530 int32 cookie = 0;
531 bool found = false;
532
533 while (get_next_image_info(0, &cookie, &info) == B_OK) {
534 if ((char*)info.data < (char*)catalog && (char*)info.data
535 + info.data_size > (char*)catalog) {
536 found = true;
537 break;
538 }
539 }
540
541 if (!found)
542 return B_NAME_NOT_FOUND;
543
544 // load the catalog for this mimetype
545 entry_ref ref;
546 if (BEntry(info.name).GetRef(&ref) == B_OK && catalog->SetTo(ref) == B_OK)
547 return B_OK;
548
549 return B_ERROR;
550 }
551
552
553 BCatalog*
_GetCatalog(BCatalog * catalog,int32 * catalogInitStatus)554 BLocaleRoster::_GetCatalog(BCatalog* catalog, int32* catalogInitStatus)
555 {
556 // This function is used in the translation macros, so it can't return a
557 // status_t. Maybe it could throw exceptions ?
558
559 __init_once(catalogInitStatus, _InitializeCatalog, catalog);
560 return catalog;
561 }
562
563
564 status_t
_PrepareCatalogEntry(const entry_ref & ref,BString & signature,BString & context,BString & string,bool traverse)565 BLocaleRoster::_PrepareCatalogEntry(const entry_ref& ref, BString& signature,
566 BString& context, BString& string, bool traverse)
567 {
568 BEntry entry(&ref, traverse);
569 if (!entry.Exists())
570 return B_ENTRY_NOT_FOUND;
571
572 BNode node(&entry);
573 status_t status = node.InitCheck();
574 if (status != B_OK)
575 return status;
576
577 status = node.ReadAttrString("SYS:NAME", &signature);
578 if (status != B_OK)
579 return status;
580
581 int32 first = signature.FindFirst(':');
582 int32 last = signature.FindLast(':');
583 if (first == last)
584 return B_ENTRY_NOT_FOUND;
585
586 context = signature;
587 string = signature;
588
589 signature.Truncate(first);
590 context.Truncate(last);
591 context.Remove(0, first + 1);
592 string.Remove(0, last + 1);
593
594 if (signature.Length() == 0 || context.Length() == 0
595 || string.Length() == 0)
596 return B_ENTRY_NOT_FOUND;
597
598 return B_OK;
599 }
600