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