/* * Copyright 2003-2012, Haiku. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Axel Dörfler, axeld@pinc-software.de * Oliver Tappe, zooey@hirschkaefer.de */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ICU includes #include #include #include using BPrivate::CatalogAddOnInfo; using BPrivate::MutableLocaleRoster; U_NAMESPACE_USE /* * several attributes/resource-IDs used within the Locale Kit: */ const char* BLocaleRoster::kCatLangAttr = "BEOS:LOCALE_LANGUAGE"; // name of catalog language, lives in every catalog file const char* BLocaleRoster::kCatSigAttr = "BEOS:LOCALE_SIGNATURE"; // catalog signature, lives in every catalog file const char* BLocaleRoster::kCatFingerprintAttr = "BEOS:LOCALE_FINGERPRINT"; // catalog fingerprint, may live in catalog file const char* BLocaleRoster::kEmbeddedCatAttr = "BEOS:LOCALE_EMBEDDED_CATALOG"; // attribute which contains flattened data of embedded catalog // this may live in an app- or add-on-file int32 BLocaleRoster::kEmbeddedCatResId = 0xCADA; // a unique value used to identify the resource (=> embedded CAtalog DAta) // which contains flattened data of embedded catalog. // this may live in an app- or add-on-file static const char* country_code_for_language(const BLanguage& language) { if (language.IsCountrySpecific()) return language.CountryCode(); // TODO: implement for real! For now, we just map some well known // languages to countries to make FirstBootPrompt happy. switch ((tolower(language.Code()[0]) << 8) | tolower(language.Code()[1])) { case 'be': // Belarus return "BY"; case 'cs': // Czech Republic return "CZ"; case 'da': // Denmark return "DK"; case 'el': // Greece return "GR"; case 'en': // United Kingdom return "GB"; case 'hi': // India return "IN"; case 'ja': // Japan return "JP"; case 'ko': // South Korea return "KR"; case 'nb': // Norway return "NO"; case 'pa': // Pakistan return "PK"; case 'sv': // Sweden return "SE"; case 'uk': // Ukraine return "UA"; case 'zh': // China return "CN"; // Languages with a matching country name case 'de': // Germany case 'es': // Spain case 'fi': // Finland case 'fr': // France case 'hr': // Croatia case 'hu': // Hungary case 'it': // Italy case 'lt': // Lithuania case 'nl': // Netherlands case 'pl': // Poland case 'pt': // Portugal case 'ro': // Romania case 'ru': // Russia case 'sk': // Slovakia return language.Code(); } return NULL; } // #pragma mark - BLocaleRoster::BLocaleRoster() : fData(new(std::nothrow) BPrivate::LocaleRosterData(BLanguage("en_US"), BFormattingConventions("en_US"))) { } BLocaleRoster::~BLocaleRoster() { delete fData; } /*static*/ BLocaleRoster* BLocaleRoster::Default() { return MutableLocaleRoster::Default(); } status_t BLocaleRoster::Refresh() { return fData->Refresh(); } status_t BLocaleRoster::GetDefaultTimeZone(BTimeZone* timezone) const { if (!timezone) return B_BAD_VALUE; BAutolock lock(fData->fLock); if (!lock.IsLocked()) return B_ERROR; *timezone = fData->fDefaultTimeZone; return B_OK; } const BLocale* BLocaleRoster::GetDefaultLocale() const { return &fData->fDefaultLocale; } status_t BLocaleRoster::GetLanguage(const char* languageCode, BLanguage** _language) const { if (_language == NULL || languageCode == NULL || languageCode[0] == '\0') return B_BAD_VALUE; BLanguage* language = new(std::nothrow) BLanguage(languageCode); if (language == NULL) return B_NO_MEMORY; *_language = language; return B_OK; } status_t BLocaleRoster::GetPreferredLanguages(BMessage* languages) const { if (!languages) return B_BAD_VALUE; BAutolock lock(fData->fLock); if (!lock.IsLocked()) return B_ERROR; *languages = fData->fPreferredLanguages; return B_OK; } /** * \brief Fills \c message with 'language'-fields containing the language- * ID(s) of all available languages. */ status_t BLocaleRoster::GetAvailableLanguages(BMessage* languages) const { if (!languages) return B_BAD_VALUE; int32_t localeCount; const Locale* icuLocaleList = Locale::getAvailableLocales(localeCount); for (int i = 0; i < localeCount; i++) languages->AddString("language", icuLocaleList[i].getName()); return B_OK; } status_t BLocaleRoster::GetAvailableCountries(BMessage* countries) const { if (!countries) return B_BAD_VALUE; int32 i; const char* const* countryList = uloc_getISOCountries(); for (i = 0; countryList[i] != NULL; i++) countries->AddString("country", countryList[i]); return B_OK; } status_t BLocaleRoster::GetAvailableTimeZones(BMessage* timeZones) const { if (!timeZones) return B_BAD_VALUE; status_t status = B_OK; StringEnumeration* zoneList = TimeZone::createEnumeration(); UErrorCode icuStatus = U_ZERO_ERROR; int32 count = zoneList->count(icuStatus); if (U_SUCCESS(icuStatus)) { for (int i = 0; i < count; ++i) { const char* zoneID = zoneList->next(NULL, icuStatus); if (zoneID == NULL || !U_SUCCESS(icuStatus)) { status = B_ERROR; break; } timeZones->AddString("timeZone", zoneID); } } else status = B_ERROR; delete zoneList; return status; } status_t BLocaleRoster::GetAvailableTimeZonesWithRegionInfo(BMessage* timeZones) const { if (!timeZones) return B_BAD_VALUE; status_t status = B_OK; UErrorCode icuStatus = U_ZERO_ERROR; StringEnumeration* zoneList = TimeZone::createTimeZoneIDEnumeration( UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, icuStatus); int32 count = zoneList->count(icuStatus); if (U_SUCCESS(icuStatus)) { for (int i = 0; i < count; ++i) { const char* zoneID = zoneList->next(NULL, icuStatus); if (zoneID == NULL || !U_SUCCESS(icuStatus)) { status = B_ERROR; break; } timeZones->AddString("timeZone", zoneID); char region[5]; icuStatus = U_ZERO_ERROR; TimeZone::getRegion(zoneID, region, 5, icuStatus); if (!U_SUCCESS(icuStatus)) { status = B_ERROR; break; } timeZones->AddString("region", region); } } else status = B_ERROR; delete zoneList; return status; } status_t BLocaleRoster::GetAvailableTimeZonesForCountry(BMessage* timeZones, const char* countryCode) const { if (!timeZones) return B_BAD_VALUE; status_t status = B_OK; StringEnumeration* zoneList = TimeZone::createEnumeration(countryCode); // countryCode == NULL will yield all timezones not bound to a country UErrorCode icuStatus = U_ZERO_ERROR; int32 count = zoneList->count(icuStatus); if (U_SUCCESS(icuStatus)) { for (int i = 0; i < count; ++i) { const char* zoneID = zoneList->next(NULL, icuStatus); if (zoneID == NULL || !U_SUCCESS(icuStatus)) { status = B_ERROR; break; } timeZones->AddString("timeZone", zoneID); } } else status = B_ERROR; delete zoneList; return status; } status_t BLocaleRoster::GetFlagIconForCountry(BBitmap* flagIcon, const char* countryCode) { if (countryCode == NULL) return B_BAD_VALUE; BAutolock lock(fData->fLock); if (!lock.IsLocked()) return B_ERROR; BResources* resources; status_t status = fData->GetResources(&resources); if (status != B_OK) return status; // Normalize the country code: 2 letters uppercase // filter things out so that "pt_BR" gives the flag for brazil int codeLength = strlen(countryCode); if (codeLength < 2) return B_BAD_VALUE; char normalizedCode[8]; strcpy(normalizedCode, "flag-"); normalizedCode[5] = tolower(countryCode[codeLength - 2]); normalizedCode[6] = tolower(countryCode[codeLength - 1]); normalizedCode[7] = '\0'; size_t size; const void* buffer = resources->LoadResource(B_VECTOR_ICON_TYPE, normalizedCode, &size); if (buffer == NULL || size == 0) return B_NAME_NOT_FOUND; return BIconUtils::GetVectorIcon(static_cast(buffer), size, flagIcon); } status_t BLocaleRoster::GetFlagIconForLanguage(BBitmap* flagIcon, const char* languageCode) { if (languageCode == NULL || languageCode[0] == '\0' || languageCode[1] == '\0') return B_BAD_VALUE; BAutolock lock(fData->fLock); if (!lock.IsLocked()) return B_ERROR; BResources* resources; status_t status = fData->GetResources(&resources); if (status != B_OK) return status; // Normalize the language code: first two letters, lowercase char normalizedCode[3]; normalizedCode[0] = tolower(languageCode[0]); normalizedCode[1] = tolower(languageCode[1]); normalizedCode[2] = '\0'; size_t size; const void* buffer = resources->LoadResource(B_VECTOR_ICON_TYPE, normalizedCode, &size); if (buffer != NULL && size != 0) { return BIconUtils::GetVectorIcon(static_cast(buffer), size, flagIcon); } // There is no language flag, try to get the default country's flag for // the language instead. BLanguage language(languageCode); const char* countryCode = country_code_for_language(language); if (countryCode == NULL) return B_NAME_NOT_FOUND; return GetFlagIconForCountry(flagIcon, countryCode); } status_t BLocaleRoster::GetAvailableCatalogs(BMessage* languageList, const char* sigPattern, const char* langPattern, int32 fingerprint) const { if (languageList == NULL) return B_BAD_VALUE; BAutolock lock(fData->fLock); if (!lock.IsLocked()) return B_ERROR; int32 count = fData->fCatalogAddOnInfos.CountItems(); for (int32 i = 0; i < count; ++i) { CatalogAddOnInfo* info = (CatalogAddOnInfo*)fData->fCatalogAddOnInfos.ItemAt(i); if (!info->fLanguagesFunc) continue; info->fLanguagesFunc(languageList, sigPattern, langPattern, fingerprint); } return B_OK; } bool BLocaleRoster::IsFilesystemTranslationPreferred() const { BAutolock lock(fData->fLock); if (!lock.IsLocked()) return B_ERROR; return fData->fIsFilesystemTranslationPreferred; } /*! \brief Looks up a localized filename from a catalog. \param localizedFileName A pre-allocated BString object for the result of the lookup. \param ref An entry_ref with an attribute holding data for catalog lookup. \param traverse A boolean to decide if symlinks are to be traversed. \return - \c B_OK: success - \c B_ENTRY_NOT_FOUND: failure. Attribute not found, entry not found in catalog, etc - other error codes: failure Attribute format: "signature:context:string" (no colon in any of signature, context and string) Lookup is done for the top preferred language, only. Lookup fails if a comment is present in the catalog entry. */ status_t BLocaleRoster::GetLocalizedFileName(BString& localizedFileName, const entry_ref& ref, bool traverse) { BString signature; BString context; BString string; status_t status = _PrepareCatalogEntry(ref, signature, context, string, traverse); if (status != B_OK) return status; // Try to get entry_ref for signature from above BRoster roster; entry_ref catalogRef; // The signature is missing application/ signature.Prepend("application/"); status = roster.FindApp(signature, &catalogRef); if (status != B_OK) return status; BCatalog catalog(catalogRef); const char* temp = catalog.GetString(string, context); if (temp == NULL) return B_ENTRY_NOT_FOUND; localizedFileName = temp; return B_OK; } static status_t _InitializeCatalog(void* param) { BCatalog* catalog = (BCatalog*)param; // figure out image (shared object) from catalog address image_info info; int32 cookie = 0; bool found = false; while (get_next_image_info(0, &cookie, &info) == B_OK) { if ((char*)info.data < (char*)catalog && (char*)info.data + info.data_size > (char*)catalog) { found = true; break; } } if (!found) return B_NAME_NOT_FOUND; // load the catalog for this mimetype entry_ref ref; if (BEntry(info.name).GetRef(&ref) == B_OK && catalog->SetTo(ref) == B_OK) return B_OK; return B_ERROR; } BCatalog* BLocaleRoster::_GetCatalog(BCatalog* catalog, int32* catalogInitStatus) { // This function is used in the translation macros, so it can't return a // status_t. Maybe it could throw exceptions ? __init_once(catalogInitStatus, _InitializeCatalog, catalog); return catalog; } status_t BLocaleRoster::_PrepareCatalogEntry(const entry_ref& ref, BString& signature, BString& context, BString& string, bool traverse) { BEntry entry(&ref, traverse); if (!entry.Exists()) return B_ENTRY_NOT_FOUND; BNode node(&entry); status_t status = node.InitCheck(); if (status != B_OK) return status; status = node.ReadAttrString("SYS:NAME", &signature); if (status != B_OK) return status; int32 first = signature.FindFirst(':'); int32 last = signature.FindLast(':'); if (first == last) return B_ENTRY_NOT_FOUND; context = signature; string = signature; signature.Truncate(first); context.Truncate(last); context.Remove(0, first + 1); string.Remove(0, last + 1); if (signature.Length() == 0 || context.Length() == 0 || string.Length() == 0) return B_ENTRY_NOT_FOUND; return B_OK; }