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