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