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* 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 129 BLocaleRoster::BLocaleRoster() 130 : 131 fData(new(std::nothrow) BPrivate::LocaleRosterData(BLanguage("en_US"), 132 BFormattingConventions("en_US"))) 133 { 134 } 135 136 137 BLocaleRoster::~BLocaleRoster() 138 { 139 delete fData; 140 } 141 142 143 /*static*/ BLocaleRoster* 144 BLocaleRoster::Default() 145 { 146 return MutableLocaleRoster::Default(); 147 } 148 149 150 status_t 151 BLocaleRoster::Refresh() 152 { 153 return fData->Refresh(); 154 } 155 156 157 status_t 158 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* 174 BLocaleRoster::GetDefaultLocale() const 175 { 176 return &fData->fDefaultLocale; 177 } 178 179 180 status_t 181 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 197 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 217 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 233 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 249 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 279 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 320 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 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 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 435 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 462 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 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 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* 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 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