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