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 'en': // United Kingdom 106 return "GB"; 107 case 'ja': // Japan 108 return "JP"; 109 case 'ko': // South Korea 110 return "KR"; 111 case 'nb': // Norway 112 return "NO"; 113 case 'sv': // Sweden 114 return "SE"; 115 case 'uk': // Ukraine 116 return "UA"; 117 case 'zh': // China 118 return "CN"; 119 120 // Languages with a matching country name 121 case 'de': // Germany 122 case 'es': // Spain 123 case 'fi': // Finland 124 case 'fr': // France 125 case 'hu': // Hungary 126 case 'it': // Italy 127 case 'lt': // Lithuania 128 case 'nl': // Netherlands 129 case 'pl': // Poland 130 case 'pt': // Portugal 131 case 'ro': // Romania 132 case 'ru': // Russia 133 case 'sk': // Slovakia 134 return language.Code(); 135 } 136 137 return NULL; 138 } 139 140 141 // #pragma mark - 142 143 144 BLocaleRoster::BLocaleRoster() 145 { 146 } 147 148 149 BLocaleRoster::~BLocaleRoster() 150 { 151 } 152 153 154 /*static*/ BLocaleRoster* 155 BLocaleRoster::Default() 156 { 157 return MutableLocaleRoster::Default(); 158 } 159 160 161 status_t 162 BLocaleRoster::Refresh() 163 { 164 return RosterData::Default()->Refresh(); 165 } 166 167 168 status_t 169 BLocaleRoster::GetDefaultTimeZone(BTimeZone* timezone) const 170 { 171 if (!timezone) 172 return B_BAD_VALUE; 173 174 RosterData* rosterData = RosterData::Default(); 175 BAutolock lock(rosterData->fLock); 176 if (!lock.IsLocked()) 177 return B_ERROR; 178 179 *timezone = rosterData->fDefaultTimeZone; 180 181 return B_OK; 182 } 183 184 185 status_t 186 BLocaleRoster::GetLanguage(const char* languageCode, 187 BLanguage** _language) const 188 { 189 if (_language == NULL || languageCode == NULL || languageCode[0] == '\0') 190 return B_BAD_VALUE; 191 192 BLanguage* language = new(std::nothrow) BLanguage(languageCode); 193 if (language == NULL) 194 return B_NO_MEMORY; 195 196 *_language = language; 197 return B_OK; 198 } 199 200 201 status_t 202 BLocaleRoster::GetPreferredLanguages(BMessage* languages) const 203 { 204 if (!languages) 205 return B_BAD_VALUE; 206 207 RosterData* rosterData = RosterData::Default(); 208 BAutolock lock(rosterData->fLock); 209 if (!lock.IsLocked()) 210 return B_ERROR; 211 212 *languages = rosterData->fPreferredLanguages; 213 214 return B_OK; 215 } 216 217 218 /** 219 * \brief Fills \c message with 'language'-fields containing the language- 220 * ID(s) of all available languages. 221 */ 222 status_t 223 BLocaleRoster::GetAvailableLanguages(BMessage* languages) const 224 { 225 if (!languages) 226 return B_BAD_VALUE; 227 228 int32_t localeCount; 229 const Locale* icuLocaleList = Locale::getAvailableLocales(localeCount); 230 231 for (int i = 0; i < localeCount; i++) 232 languages->AddString("language", icuLocaleList[i].getName()); 233 234 return B_OK; 235 } 236 237 238 status_t 239 BLocaleRoster::GetAvailableCountries(BMessage* countries) const 240 { 241 if (!countries) 242 return B_BAD_VALUE; 243 244 int32 i; 245 const char* const* countryList = uloc_getISOCountries(); 246 247 for (i = 0; countryList[i] != NULL; i++) 248 countries->AddString("country", countryList[i]); 249 250 return B_OK; 251 } 252 253 254 status_t 255 BLocaleRoster::GetAvailableTimeZones(BMessage* timeZones) const 256 { 257 if (!timeZones) 258 return B_BAD_VALUE; 259 260 status_t status = B_OK; 261 262 StringEnumeration* zoneList = TimeZone::createEnumeration(); 263 264 UErrorCode icuStatus = U_ZERO_ERROR; 265 int32 count = zoneList->count(icuStatus); 266 if (U_SUCCESS(icuStatus)) { 267 for (int i = 0; i < count; ++i) { 268 const char* zoneID = zoneList->next(NULL, icuStatus); 269 if (zoneID == NULL || !U_SUCCESS(icuStatus)) { 270 status = B_ERROR; 271 break; 272 } 273 timeZones->AddString("timeZone", zoneID); 274 } 275 } else 276 status = B_ERROR; 277 278 delete zoneList; 279 280 return status; 281 } 282 283 284 status_t 285 BLocaleRoster::GetAvailableTimeZonesWithRegionInfo(BMessage* timeZones) const 286 { 287 if (!timeZones) 288 return B_BAD_VALUE; 289 290 status_t status = B_OK; 291 292 UErrorCode icuStatus = U_ZERO_ERROR; 293 294 StringEnumeration* zoneList = TimeZone::createTimeZoneIDEnumeration( 295 UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, icuStatus); 296 297 int32 count = zoneList->count(icuStatus); 298 if (U_SUCCESS(icuStatus)) { 299 for (int i = 0; i < count; ++i) { 300 const char* zoneID = zoneList->next(NULL, icuStatus); 301 if (zoneID == NULL || !U_SUCCESS(icuStatus)) { 302 status = B_ERROR; 303 break; 304 } 305 timeZones->AddString("timeZone", zoneID); 306 307 char region[5]; 308 icuStatus = U_ZERO_ERROR; 309 TimeZone::getRegion(zoneID, region, 5, icuStatus); 310 if (!U_SUCCESS(icuStatus)) { 311 status = B_ERROR; 312 break; 313 } 314 timeZones->AddString("region", region); 315 } 316 } else 317 status = B_ERROR; 318 319 delete zoneList; 320 321 return status; 322 } 323 324 325 status_t 326 BLocaleRoster::GetAvailableTimeZonesForCountry(BMessage* timeZones, 327 const char* countryCode) const 328 { 329 if (!timeZones) 330 return B_BAD_VALUE; 331 332 status_t status = B_OK; 333 334 StringEnumeration* zoneList = TimeZone::createEnumeration(countryCode); 335 // countryCode == NULL will yield all timezones not bound to a country 336 337 UErrorCode icuStatus = U_ZERO_ERROR; 338 int32 count = zoneList->count(icuStatus); 339 if (U_SUCCESS(icuStatus)) { 340 for (int i = 0; i < count; ++i) { 341 const char* zoneID = zoneList->next(NULL, icuStatus); 342 if (zoneID == NULL || !U_SUCCESS(icuStatus)) { 343 status = B_ERROR; 344 break; 345 } 346 timeZones->AddString("timeZone", zoneID); 347 } 348 } else 349 status = B_ERROR; 350 351 delete zoneList; 352 353 return status; 354 } 355 356 357 status_t 358 BLocaleRoster::GetFlagIconForCountry(BBitmap* flagIcon, const char* countryCode) 359 { 360 if (countryCode == NULL) 361 return B_BAD_VALUE; 362 363 RosterData* rosterData = RosterData::Default(); 364 BAutolock lock(rosterData->fLock); 365 if (!lock.IsLocked()) 366 return B_ERROR; 367 368 status_t status = load_resources_if_needed(rosterData); 369 if (status != B_OK) 370 return status; 371 372 // Normalize the country code: 2 letters uppercase 373 // filter things out so that "pt_BR" gives the flag for brazil 374 375 int codeLength = strlen(countryCode); 376 if (codeLength < 2) 377 return B_BAD_VALUE; 378 379 char normalizedCode[3]; 380 normalizedCode[0] = toupper(countryCode[codeLength - 2]); 381 normalizedCode[1] = toupper(countryCode[codeLength - 1]); 382 normalizedCode[2] = '\0'; 383 384 size_t size; 385 const void* buffer = rosterData->fResources.LoadResource( 386 B_VECTOR_ICON_TYPE, normalizedCode, &size); 387 if (buffer == NULL || size == 0) 388 return B_NAME_NOT_FOUND; 389 390 return BIconUtils::GetVectorIcon(static_cast<const uint8*>(buffer), size, 391 flagIcon); 392 } 393 394 395 status_t 396 BLocaleRoster::GetFlagIconForLanguage(BBitmap* flagIcon, 397 const char* languageCode) 398 { 399 if (languageCode == NULL || languageCode[0] == '\0' 400 || languageCode[1] == '\0') 401 return B_BAD_VALUE; 402 403 RosterData* rosterData = RosterData::Default(); 404 BAutolock lock(rosterData->fLock); 405 if (!lock.IsLocked()) 406 return B_ERROR; 407 408 status_t status = load_resources_if_needed(rosterData); 409 if (status != B_OK) 410 return status; 411 412 // Normalize the language code: first two letters, lowercase 413 414 char normalizedCode[3]; 415 normalizedCode[0] = tolower(languageCode[0]); 416 normalizedCode[1] = tolower(languageCode[1]); 417 normalizedCode[2] = '\0'; 418 419 size_t size; 420 const void* buffer = rosterData->fResources.LoadResource( 421 B_VECTOR_ICON_TYPE, normalizedCode, &size); 422 if (buffer != NULL && size != 0) { 423 return BIconUtils::GetVectorIcon(static_cast<const uint8*>(buffer), 424 size, flagIcon); 425 } 426 427 // There is no language flag, try to get the default country's flag for 428 // the language instead. 429 430 BLanguage language(languageCode); 431 const char* countryCode = country_code_for_language(language); 432 if (countryCode == NULL) 433 return B_NAME_NOT_FOUND; 434 435 return GetFlagIconForCountry(flagIcon, countryCode); 436 } 437 438 439 status_t 440 BLocaleRoster::GetAvailableCatalogs(BMessage* languageList, 441 const char* sigPattern, const char* langPattern, int32 fingerprint) const 442 { 443 if (languageList == NULL) 444 return B_BAD_VALUE; 445 446 RosterData* rosterData = RosterData::Default(); 447 BAutolock lock(rosterData->fLock); 448 if (!lock.IsLocked()) 449 return B_ERROR; 450 451 int32 count = rosterData->fCatalogAddOnInfos.CountItems(); 452 for (int32 i = 0; i < count; ++i) { 453 CatalogAddOnInfo* info 454 = (CatalogAddOnInfo*)rosterData->fCatalogAddOnInfos.ItemAt(i); 455 456 if (!info->MakeSureItsLoaded() || !info->fLanguagesFunc) 457 continue; 458 459 info->fLanguagesFunc(languageList, sigPattern, langPattern, 460 fingerprint); 461 } 462 463 return B_OK; 464 } 465 466 467 bool 468 BLocaleRoster::IsFilesystemTranslationPreferred() const 469 { 470 RosterData* rosterData = RosterData::Default(); 471 BAutolock lock(rosterData->fLock); 472 if (!lock.IsLocked()) 473 return B_ERROR; 474 475 return rosterData->fIsFilesystemTranslationPreferred; 476 } 477 478 479 /*! \brief Looks up a localized filename from a catalog. 480 \param localizedFileName A pre-allocated BString object for the result 481 of the lookup. 482 \param ref An entry_ref with an attribute holding data for catalog lookup. 483 \param traverse A boolean to decide if symlinks are to be traversed. 484 \return 485 - \c B_OK: success 486 - \c B_ENTRY_NOT_FOUND: failure. Attribute not found, entry not found 487 in catalog, etc 488 - other error codes: failure 489 490 Attribute format: "signature:context:string" 491 (no colon in any of signature, context and string) 492 493 Lookup is done for the top preferred language, only. 494 Lookup fails if a comment is present in the catalog entry. 495 */ 496 status_t 497 BLocaleRoster::GetLocalizedFileName(BString& localizedFileName, 498 const entry_ref& ref, bool traverse) 499 { 500 BString signature; 501 BString context; 502 BString string; 503 504 status_t status = _PrepareCatalogEntry(ref, signature, context, string, 505 traverse); 506 507 if (status != B_OK) 508 return status; 509 510 BCatalog catalog(ref); 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 BCatalog* 522 BLocaleRoster::_GetCatalog(BCatalog* catalog, vint32* catalogInitStatus) 523 { 524 // This function is used in the translation macros, so it can't return a 525 // status_t. Maybe it could throw exceptions ? 526 527 if (*catalogInitStatus == true) { 528 // Catalog already loaded - nothing else to do 529 return catalog; 530 } 531 532 // figure out image (shared object) from catalog address 533 image_info info; 534 int32 cookie = 0; 535 bool found = false; 536 537 while (get_next_image_info(0, &cookie, &info) == B_OK) { 538 if ((char*)info.data < (char*)catalog && (char*)info.data 539 + info.data_size > (char*)catalog) { 540 found = true; 541 break; 542 } 543 } 544 545 if (!found) { 546 log_team(LOG_DEBUG, "Catalog %x doesn't belong to any image!", 547 catalog); 548 return catalog; 549 } 550 551 // load the catalog for this mimetype and return it to the app 552 entry_ref ref; 553 if (BEntry(info.name).GetRef(&ref) == B_OK && catalog->SetTo(ref) == B_OK) 554 *catalogInitStatus = true; 555 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