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