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