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