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
44using BPrivate::CatalogAddOnInfo;
45using BPrivate::MutableLocaleRoster;
46U_NAMESPACE_USE
47
48
49/*
50 * several attributes/resource-IDs used within the Locale Kit:
51 */
52const char* BLocaleRoster::kCatLangAttr = "BEOS:LOCALE_LANGUAGE";
53	// name of catalog language, lives in every catalog file
54const char* BLocaleRoster::kCatSigAttr = "BEOS:LOCALE_SIGNATURE";
55	// catalog signature, lives in every catalog file
56const char* BLocaleRoster::kCatFingerprintAttr = "BEOS:LOCALE_FINGERPRINT";
57	// catalog fingerprint, may live in catalog file
58
59const 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
62int32 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
68static const char*
69country_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
129BLocaleRoster::BLocaleRoster()
130	:
131	fData(new(std::nothrow) BPrivate::LocaleRosterData(BLanguage("en_US"),
132		BFormattingConventions("en_US")))
133{
134}
135
136
137BLocaleRoster::~BLocaleRoster()
138{
139	delete fData;
140}
141
142
143/*static*/ BLocaleRoster*
144BLocaleRoster::Default()
145{
146	return MutableLocaleRoster::Default();
147}
148
149
150status_t
151BLocaleRoster::Refresh()
152{
153	return fData->Refresh();
154}
155
156
157status_t
158BLocaleRoster::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
173const BLocale*
174BLocaleRoster::GetDefaultLocale() const
175{
176	return &fData->fDefaultLocale;
177}
178
179
180status_t
181BLocaleRoster::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
196status_t
197BLocaleRoster::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 */
216status_t
217BLocaleRoster::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
232status_t
233BLocaleRoster::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
248status_t
249BLocaleRoster::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
278status_t
279BLocaleRoster::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
319status_t
320BLocaleRoster::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
351status_t
352BLocaleRoster::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
390status_t
391BLocaleRoster::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
434status_t
435BLocaleRoster::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
461bool
462BLocaleRoster::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*/
489status_t
490BLocaleRoster::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
523static 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
553BCatalog*
554BLocaleRoster::_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
564status_t
565BLocaleRoster::_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