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