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
42using BPrivate::CatalogAddOnInfo;
43using BPrivate::MutableLocaleRoster;
44
45
46/*
47 * several attributes/resource-IDs used within the Locale Kit:
48 */
49const char* BLocaleRoster::kCatLangAttr = "BEOS:LOCALE_LANGUAGE";
50	// name of catalog language, lives in every catalog file
51const char* BLocaleRoster::kCatSigAttr = "BEOS:LOCALE_SIGNATURE";
52	// catalog signature, lives in every catalog file
53const char* BLocaleRoster::kCatFingerprintAttr = "BEOS:LOCALE_FINGERPRINT";
54	// catalog fingerprint, may live in catalog file
55
56const 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
59int32 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
65static const char*
66country_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
126BLocaleRoster::BLocaleRoster()
127	:
128	fData(new(std::nothrow) BPrivate::LocaleRosterData(BLanguage("en_US"),
129		BFormattingConventions("en_US")))
130{
131}
132
133
134BLocaleRoster::~BLocaleRoster()
135{
136	delete fData;
137}
138
139
140/*static*/ BLocaleRoster*
141BLocaleRoster::Default()
142{
143	return MutableLocaleRoster::Default();
144}
145
146
147status_t
148BLocaleRoster::Refresh()
149{
150	return fData->Refresh();
151}
152
153
154status_t
155BLocaleRoster::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
170const BLocale*
171BLocaleRoster::GetDefaultLocale() const
172{
173	return &fData->fDefaultLocale;
174}
175
176status_t
177BLocaleRoster::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
192status_t
193BLocaleRoster::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 */
212status_t
213BLocaleRoster::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
228status_t
229BLocaleRoster::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
244status_t
245BLocaleRoster::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
274status_t
275BLocaleRoster::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
315status_t
316BLocaleRoster::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
347status_t
348BLocaleRoster::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
385status_t
386BLocaleRoster::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
429status_t
430BLocaleRoster::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
456bool
457BLocaleRoster::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*/
484status_t
485BLocaleRoster::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
518BCatalog*
519BLocaleRoster::_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
554status_t
555BLocaleRoster::_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