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*
GetSharedCollator()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
_GetCollator(BCollator * collator)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
TimestampToDateTimeString(uint64 millis)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
TimestampToDateString(uint64 millis)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
CreateTranslatedIAmMinimumAgeSlug(int minimumAge)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
DeriveDefaultLanguage(LanguageRepository * repository)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
SetForcedSystemDefaultLanguageID(const BString & id)155 LocaleUtils::SetForcedSystemDefaultLanguageID(const BString& id)
156 {
157 sForcedSystemDefaultLanguageID = id;
158 }
159
160
161 /*static*/ LanguageRef
_DeriveSystemDefaultLanguage()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
_FindBestMatchingLanguage(LanguageRepository * repository,const char * code,const char * countryCode,const char * scriptCode)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
_IndexOfBestMatchingLanguage(LanguageRepository * repository,const char * code,const char * countryCode,const char * scriptCode)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