xref: /haiku/src/apps/haikudepot/util/LocaleUtils.cpp (revision 909af08f4328301fbdef1ffb41f566c3b5bec0c7)
1 /*
2  * Copyright 2019, Andrew Lindesay <apl@lindesay.co.nz>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 #include "LocaleUtils.h"
6 
7 #include <stdlib.h>
8 
9 #include <Catalog.h>
10 #include <Collator.h>
11 #include <DateFormat.h>
12 #include <DateTimeFormat.h>
13 #include <Locale.h>
14 #include <LocaleRoster.h>
15 #include <StringFormat.h>
16 
17 #include "HaikuDepotConstants.h"
18 #include "Logger.h"
19 #include "StringUtils.h"
20 
21 
22 #undef B_TRANSLATION_CONTEXT
23 #define B_TRANSLATION_CONTEXT "LocaleUtils"
24 
25 
26 BCollator* LocaleUtils::sSharedCollator = NULL;
27 BString LocaleUtils::sForcedSystemDefaultLanguageID;
28 
29 
30 /*static*/ BCollator*
31 LocaleUtils::GetSharedCollator()
32 {
33 	if (sSharedCollator == NULL) {
34 		sSharedCollator = new BCollator();
35 		_GetCollator(sSharedCollator);
36 	}
37 
38 	return sSharedCollator;
39 }
40 
41 
42 /*static*/ void
43 LocaleUtils::_GetCollator(BCollator* collator)
44 {
45 	const BLocale* locale = BLocaleRoster::Default()->GetDefaultLocale();
46 
47 	if (locale->GetCollator(collator) != B_OK)
48 		HDFATAL("unable to get the locale's collator");
49 }
50 
51 
52 /*static*/ BString
53 LocaleUtils::TimestampToDateTimeString(uint64 millis)
54 {
55 	if (millis == 0)
56 		return "?";
57 
58 	BDateTimeFormat format;
59 	BString buffer;
60 	if (format.Format(buffer, millis / 1000, B_SHORT_DATE_FORMAT,
61 			B_SHORT_TIME_FORMAT) != B_OK)
62 		return "!";
63 
64 	return buffer;
65 }
66 
67 
68 /*static*/ BString
69 LocaleUtils::TimestampToDateString(uint64 millis)
70 {
71 	if (millis == 0)
72 		return "?";
73 
74 	BDateFormat format;
75 	BString buffer;
76 	if (format.Format(buffer, millis / 1000, B_SHORT_DATE_FORMAT)
77 			!= B_OK)
78 		return "!";
79 
80 	return buffer;
81 }
82 
83 
84 /*!	This is used in situations where the user is required to confirm that they
85 	are as old or older than some minimal age.  This is associated with agreeing
86 	to the user usage conditions.
87 */
88 
89 /*static*/ BString
90 LocaleUtils::CreateTranslatedIAmMinimumAgeSlug(int minimumAge)
91 {
92 	BString slug;
93 	static BStringFormat format(B_TRANSLATE("{0, plural,"
94  		"one{I am at least one year old}"
95 		"other{I am # years of age or older}}"));
96 	format.Format(slug, minimumAge);
97 	return slug;
98 }
99 
100 
101 /*!	This will derive the default language.  If there are no other
102 	possible languages configured then the default language will be
103 	assumed to exist.  Otherwise if there is a set of possible languages
104 	then this method will ensure that the default language is in that
105 	set.
106 */
107 
108 /*static*/ LanguageRef
109 LocaleUtils::DeriveDefaultLanguage(LanguageRepository* repository)
110 {
111 	LanguageRef defaultLanguage = _DeriveSystemDefaultLanguage();
112 	HDDEBUG("derived system default language [%s]", defaultLanguage->ID());
113 
114 	// if there are no supported languages; as is the case to start with as the
115 	// application starts, the default language from the system is used anyway.
116 	// The data queried in HDS will handle the case where the language is not
117 	// 'known' at the HDS end so it doesn't matter if it is invalid when the
118 	// HaikuDepot application requests data from the HaikuDepotServer system.
119 
120 	if (repository->IsEmpty()) {
121 		HDTRACE("no supported languages --> will use default language");
122 		return defaultLanguage;
123 	}
124 
125 	// if there are supported languages defined then the preferred language
126 	// needs to be one of the supported ones.
127 
128 	LanguageRef foundSupportedLanguage = _FindBestMatchingLanguage(repository,
129 		defaultLanguage->Code(), defaultLanguage->CountryCode(), defaultLanguage->ScriptCode());
130 
131 	if (!foundSupportedLanguage.IsSet()) {
132 		Language appDefaultLanguage(LANGUAGE_DEFAULT_ID, "English", true);
133 		HDERROR("unable to find the language [%s] so will look for app default [%s]",
134 			defaultLanguage->ID(), appDefaultLanguage.ID());
135 		foundSupportedLanguage = _FindBestMatchingLanguage(repository, appDefaultLanguage.Code(),
136 			appDefaultLanguage.CountryCode(), appDefaultLanguage.ScriptCode());
137 
138 		if (!foundSupportedLanguage.IsSet()) {
139 			foundSupportedLanguage = repository->LanguageAtIndex(0);
140 			HDERROR("unable to find the app default language [%s] in the supported language so"
141 					" will use the first supported language [%s]",
142 				appDefaultLanguage.ID(), foundSupportedLanguage->ID());
143 		}
144 	} else {
145 		HDTRACE("did find supported language [%s] as best match to [%s] from %" B_PRIu32
146 				" supported languages",
147 			foundSupportedLanguage->ID(), defaultLanguage->ID(), repository->CountLanguages());
148 	}
149 
150 	return foundSupportedLanguage;
151 }
152 
153 
154 /*static*/ void
155 LocaleUtils::SetForcedSystemDefaultLanguageID(const BString& id)
156 {
157 	sForcedSystemDefaultLanguageID = id;
158 }
159 
160 
161 /*static*/ LanguageRef
162 LocaleUtils::_DeriveSystemDefaultLanguage()
163 {
164 	if (!sForcedSystemDefaultLanguageID.IsEmpty()) {
165 		return LanguageRef(
166 			new Language(sForcedSystemDefaultLanguageID, sForcedSystemDefaultLanguageID, true));
167 	}
168 
169 	BLocaleRoster* localeRoster = BLocaleRoster::Default();
170 	if (localeRoster != NULL) {
171 		BMessage preferredLanguages;
172 		if (localeRoster->GetPreferredLanguages(&preferredLanguages) == B_OK) {
173 			BString language;
174 			if (preferredLanguages.FindString("language", 0, &language) == B_OK)
175 				return LanguageRef(new Language(language, language, true));
176 		}
177 	}
178 
179 	return LanguageRef(new Language(LANGUAGE_DEFAULT_ID, "English", true), true);
180 }
181 
182 
183 /*! This method will take the supplied codes and will attempt to find the
184 	supported language that best matches the codes. If there is really no
185 	match then it will return `NULL`.
186 */
187 
188 /*static*/ LanguageRef
189 LocaleUtils::_FindBestMatchingLanguage(LanguageRepository* repository, const char* code,
190 	const char* countryCode, const char* scriptCode)
191 {
192 	int32 index = _IndexOfBestMatchingLanguage(repository, code, countryCode, scriptCode);
193 	if (-1 != index)
194 		return repository->LanguageAtIndex(index);
195 	return LanguageRef();
196 }
197 
198 
199 /*! This will find the first supported language that matches the arguments
200 	provided. In the case where one of the arguments is `NULL`, is will not
201 	be considered.
202 */
203 
204 /*static*/ int32
205 LocaleUtils::_IndexOfBestMatchingLanguage(LanguageRepository* repository, const char* code,
206 	const char* countryCode, const char* scriptCode)
207 {
208 	int32 languagesCount = repository->CountLanguages();
209 
210 	if (NULL != scriptCode) {
211 		int32 index = repository->IndexOfLanguage(code, countryCode, scriptCode);
212 			// looking for an exact match
213 		if (-1 == index)
214 			return index;
215 	}
216 
217 	if (NULL != countryCode) {
218 		for (int32 i = 0; i < languagesCount; i++) {
219 			LanguageRef language = repository->LanguageAtIndex(i);
220 			if (0 == StringUtils::NullSafeCompare(code, language->Code())
221 				&& 0 == StringUtils::NullSafeCompare(countryCode, language->CountryCode())) {
222 				return i;
223 			}
224 		}
225 	}
226 
227 	if (NULL != code) {
228 		for (int32 i = 0; i < languagesCount; i++) {
229 			LanguageRef language = repository->LanguageAtIndex(i);
230 			if (0 == StringUtils::NullSafeCompare(code, language->Code()))
231 				return i;
232 		}
233 	}
234 
235 	return -1;
236 }
237