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