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