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