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