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