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 status_t 178 BLocaleRoster::GetLanguage(const char* languageCode, 179 BLanguage** _language) const 180 { 181 if (_language == NULL || languageCode == NULL || languageCode[0] == '\0') 182 return B_BAD_VALUE; 183 184 BLanguage* language = new(std::nothrow) BLanguage(languageCode); 185 if (language == NULL) 186 return B_NO_MEMORY; 187 188 *_language = language; 189 return B_OK; 190 } 191 192 193 status_t 194 BLocaleRoster::GetPreferredLanguages(BMessage* languages) const 195 { 196 if (!languages) 197 return B_BAD_VALUE; 198 199 BAutolock lock(fData->fLock); 200 if (!lock.IsLocked()) 201 return B_ERROR; 202 203 *languages = fData->fPreferredLanguages; 204 205 return B_OK; 206 } 207 208 209 /** 210 * \brief Fills \c message with 'language'-fields containing the language- 211 * ID(s) of all available languages. 212 */ 213 status_t 214 BLocaleRoster::GetAvailableLanguages(BMessage* languages) const 215 { 216 if (!languages) 217 return B_BAD_VALUE; 218 219 int32_t localeCount; 220 const Locale* icuLocaleList = Locale::getAvailableLocales(localeCount); 221 222 for (int i = 0; i < localeCount; i++) 223 languages->AddString("language", icuLocaleList[i].getName()); 224 225 return B_OK; 226 } 227 228 229 status_t 230 BLocaleRoster::GetAvailableCountries(BMessage* countries) const 231 { 232 if (!countries) 233 return B_BAD_VALUE; 234 235 int32 i; 236 const char* const* countryList = uloc_getISOCountries(); 237 238 for (i = 0; countryList[i] != NULL; i++) 239 countries->AddString("country", countryList[i]); 240 241 return B_OK; 242 } 243 244 245 status_t 246 BLocaleRoster::GetAvailableTimeZones(BMessage* timeZones) const 247 { 248 if (!timeZones) 249 return B_BAD_VALUE; 250 251 status_t status = B_OK; 252 253 StringEnumeration* zoneList = TimeZone::createEnumeration(); 254 255 UErrorCode icuStatus = U_ZERO_ERROR; 256 int32 count = zoneList->count(icuStatus); 257 if (U_SUCCESS(icuStatus)) { 258 for (int i = 0; i < count; ++i) { 259 const char* zoneID = zoneList->next(NULL, icuStatus); 260 if (zoneID == NULL || !U_SUCCESS(icuStatus)) { 261 status = B_ERROR; 262 break; 263 } 264 timeZones->AddString("timeZone", zoneID); 265 } 266 } else 267 status = B_ERROR; 268 269 delete zoneList; 270 271 return status; 272 } 273 274 275 status_t 276 BLocaleRoster::GetAvailableTimeZonesWithRegionInfo(BMessage* timeZones) const 277 { 278 if (!timeZones) 279 return B_BAD_VALUE; 280 281 status_t status = B_OK; 282 283 UErrorCode icuStatus = U_ZERO_ERROR; 284 285 StringEnumeration* zoneList = TimeZone::createTimeZoneIDEnumeration( 286 UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, icuStatus); 287 288 int32 count = zoneList->count(icuStatus); 289 if (U_SUCCESS(icuStatus)) { 290 for (int i = 0; i < count; ++i) { 291 const char* zoneID = zoneList->next(NULL, icuStatus); 292 if (zoneID == NULL || !U_SUCCESS(icuStatus)) { 293 status = B_ERROR; 294 break; 295 } 296 timeZones->AddString("timeZone", zoneID); 297 298 char region[5]; 299 icuStatus = U_ZERO_ERROR; 300 TimeZone::getRegion(zoneID, region, 5, icuStatus); 301 if (!U_SUCCESS(icuStatus)) { 302 status = B_ERROR; 303 break; 304 } 305 timeZones->AddString("region", region); 306 } 307 } else 308 status = B_ERROR; 309 310 delete zoneList; 311 312 return status; 313 } 314 315 316 status_t 317 BLocaleRoster::GetAvailableTimeZonesForCountry(BMessage* timeZones, 318 const char* countryCode) const 319 { 320 if (!timeZones) 321 return B_BAD_VALUE; 322 323 status_t status = B_OK; 324 325 StringEnumeration* zoneList = TimeZone::createEnumeration(countryCode); 326 // countryCode == NULL will yield all timezones not bound to a country 327 328 UErrorCode icuStatus = U_ZERO_ERROR; 329 int32 count = zoneList->count(icuStatus); 330 if (U_SUCCESS(icuStatus)) { 331 for (int i = 0; i < count; ++i) { 332 const char* zoneID = zoneList->next(NULL, icuStatus); 333 if (zoneID == NULL || !U_SUCCESS(icuStatus)) { 334 status = B_ERROR; 335 break; 336 } 337 timeZones->AddString("timeZone", zoneID); 338 } 339 } else 340 status = B_ERROR; 341 342 delete zoneList; 343 344 return status; 345 } 346 347 348 status_t 349 BLocaleRoster::GetFlagIconForCountry(BBitmap* flagIcon, const char* countryCode) 350 { 351 if (countryCode == NULL) 352 return B_BAD_VALUE; 353 354 BAutolock lock(fData->fLock); 355 if (!lock.IsLocked()) 356 return B_ERROR; 357 358 BResources* resources; 359 status_t status = fData->GetResources(&resources); 360 if (status != B_OK) 361 return status; 362 363 // Normalize the country code: 2 letters uppercase 364 // filter things out so that "pt_BR" gives the flag for brazil 365 366 int codeLength = strlen(countryCode); 367 if (codeLength < 2) 368 return B_BAD_VALUE; 369 370 char normalizedCode[3]; 371 normalizedCode[0] = toupper(countryCode[codeLength - 2]); 372 normalizedCode[1] = toupper(countryCode[codeLength - 1]); 373 normalizedCode[2] = '\0'; 374 375 size_t size; 376 const void* buffer = resources->LoadResource(B_VECTOR_ICON_TYPE, 377 normalizedCode, &size); 378 if (buffer == NULL || size == 0) 379 return B_NAME_NOT_FOUND; 380 381 return BIconUtils::GetVectorIcon(static_cast<const uint8*>(buffer), size, 382 flagIcon); 383 } 384 385 386 status_t 387 BLocaleRoster::GetFlagIconForLanguage(BBitmap* flagIcon, 388 const char* languageCode) 389 { 390 if (languageCode == NULL || languageCode[0] == '\0' 391 || languageCode[1] == '\0') 392 return B_BAD_VALUE; 393 394 BAutolock lock(fData->fLock); 395 if (!lock.IsLocked()) 396 return B_ERROR; 397 398 BResources* resources; 399 status_t status = fData->GetResources(&resources); 400 if (status != B_OK) 401 return status; 402 403 // Normalize the language code: first two letters, lowercase 404 405 char normalizedCode[3]; 406 normalizedCode[0] = tolower(languageCode[0]); 407 normalizedCode[1] = tolower(languageCode[1]); 408 normalizedCode[2] = '\0'; 409 410 size_t size; 411 const void* buffer = resources->LoadResource(B_VECTOR_ICON_TYPE, 412 normalizedCode, &size); 413 if (buffer != NULL && size != 0) { 414 return BIconUtils::GetVectorIcon(static_cast<const uint8*>(buffer), 415 size, flagIcon); 416 } 417 418 // There is no language flag, try to get the default country's flag for 419 // the language instead. 420 421 BLanguage language(languageCode); 422 const char* countryCode = country_code_for_language(language); 423 if (countryCode == NULL) 424 return B_NAME_NOT_FOUND; 425 426 return GetFlagIconForCountry(flagIcon, countryCode); 427 } 428 429 430 status_t 431 BLocaleRoster::GetAvailableCatalogs(BMessage* languageList, 432 const char* sigPattern, const char* langPattern, int32 fingerprint) const 433 { 434 if (languageList == NULL) 435 return B_BAD_VALUE; 436 437 BAutolock lock(fData->fLock); 438 if (!lock.IsLocked()) 439 return B_ERROR; 440 441 int32 count = fData->fCatalogAddOnInfos.CountItems(); 442 for (int32 i = 0; i < count; ++i) { 443 CatalogAddOnInfo* info 444 = (CatalogAddOnInfo*)fData->fCatalogAddOnInfos.ItemAt(i); 445 446 if (!info->MakeSureItsLoaded() || !info->fLanguagesFunc) 447 continue; 448 449 info->fLanguagesFunc(languageList, sigPattern, langPattern, 450 fingerprint); 451 } 452 453 return B_OK; 454 } 455 456 457 bool 458 BLocaleRoster::IsFilesystemTranslationPreferred() const 459 { 460 BAutolock lock(fData->fLock); 461 if (!lock.IsLocked()) 462 return B_ERROR; 463 464 return fData->fIsFilesystemTranslationPreferred; 465 } 466 467 468 /*! \brief Looks up a localized filename from a catalog. 469 \param localizedFileName A pre-allocated BString object for the result 470 of the lookup. 471 \param ref An entry_ref with an attribute holding data for catalog lookup. 472 \param traverse A boolean to decide if symlinks are to be traversed. 473 \return 474 - \c B_OK: success 475 - \c B_ENTRY_NOT_FOUND: failure. Attribute not found, entry not found 476 in catalog, etc 477 - other error codes: failure 478 479 Attribute format: "signature:context:string" 480 (no colon in any of signature, context and string) 481 482 Lookup is done for the top preferred language, only. 483 Lookup fails if a comment is present in the catalog entry. 484 */ 485 status_t 486 BLocaleRoster::GetLocalizedFileName(BString& localizedFileName, 487 const entry_ref& ref, bool traverse) 488 { 489 BString signature; 490 BString context; 491 BString string; 492 493 status_t status = _PrepareCatalogEntry(ref, signature, context, string, 494 traverse); 495 496 if (status != B_OK) 497 return status; 498 499 // Try to get entry_ref for signature from above 500 BRoster roster; 501 entry_ref catalogRef; 502 // The signature is missing application/ 503 signature.Prepend("application/"); 504 status = roster.FindApp(signature, &catalogRef); 505 if (status != B_OK) 506 return status; 507 508 BCatalog catalog(catalogRef); 509 const char* temp = catalog.GetString(string, context); 510 511 if (temp == NULL) 512 return B_ENTRY_NOT_FOUND; 513 514 localizedFileName = temp; 515 return B_OK; 516 } 517 518 519 static status_t 520 _InitializeCatalog(void* param) 521 { 522 BCatalog* catalog = (BCatalog*)param; 523 524 // figure out image (shared object) from catalog address 525 image_info info; 526 int32 cookie = 0; 527 bool found = false; 528 529 while (get_next_image_info(0, &cookie, &info) == B_OK) { 530 if ((char*)info.data < (char*)catalog && (char*)info.data 531 + info.data_size > (char*)catalog) { 532 found = true; 533 break; 534 } 535 } 536 537 if (!found) 538 return B_NAME_NOT_FOUND; 539 540 // load the catalog for this mimetype 541 entry_ref ref; 542 if (BEntry(info.name).GetRef(&ref) == B_OK && catalog->SetTo(ref) == B_OK); 543 return B_OK; 544 545 return B_ERROR; 546 } 547 548 549 BCatalog* 550 BLocaleRoster::_GetCatalog(BCatalog* catalog, int32* catalogInitStatus) 551 { 552 // This function is used in the translation macros, so it can't return a 553 // status_t. Maybe it could throw exceptions ? 554 555 __init_once(catalogInitStatus, _InitializeCatalog, catalog); 556 return catalog; 557 } 558 559 560 status_t 561 BLocaleRoster::_PrepareCatalogEntry(const entry_ref& ref, BString& signature, 562 BString& context, BString& string, bool traverse) 563 { 564 BEntry entry(&ref, traverse); 565 if (!entry.Exists()) 566 return B_ENTRY_NOT_FOUND; 567 568 BNode node(&entry); 569 status_t status = node.InitCheck(); 570 if (status != B_OK) 571 return status; 572 573 status = node.ReadAttrString("SYS:NAME", &signature); 574 if (status != B_OK) 575 return status; 576 577 int32 first = signature.FindFirst(':'); 578 int32 last = signature.FindLast(':'); 579 if (first == last) 580 return B_ENTRY_NOT_FOUND; 581 582 context = signature; 583 string = signature; 584 585 signature.Truncate(first); 586 context.Truncate(last); 587 context.Remove(0, first + 1); 588 string.Remove(0, last + 1); 589 590 if (signature.Length() == 0 || context.Length() == 0 591 || string.Length() == 0) 592 return B_ENTRY_NOT_FOUND; 593 594 return B_OK; 595 } 596