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