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