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