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