language_selector.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. //
  5. // This file defines a helper class for selecting a supported language from a
  6. // set of candidates. It is used to get localized strings that are directly
  7. // embedded into the executable / library instead of stored in external
  8. // .pak files.
  9. #include "base/win/embedded_i18n/language_selector.h"
  10. #include <algorithm>
  11. #include <functional>
  12. #include "base/check_op.h"
  13. #include "base/strings/string_util.h"
  14. #include "base/strings/utf_string_conversions.h"
  15. #include "base/win/i18n.h"
  16. namespace base {
  17. namespace win {
  18. namespace i18n {
  19. namespace {
  20. using LangToOffset = LanguageSelector::LangToOffset;
  21. // Holds pointers to LangToOffset pairs for specific languages that are the
  22. // targets of exceptions (where one language is mapped to another) or wildcards
  23. // (where a raw language identifier is mapped to a specific localization).
  24. struct AvailableLanguageAliases {
  25. const LangToOffset* en_gb_language_offset;
  26. const LangToOffset* en_us_language_offset;
  27. const LangToOffset* es_language_offset;
  28. const LangToOffset* es_419_language_offset;
  29. const LangToOffset* fil_language_offset;
  30. const LangToOffset* iw_language_offset;
  31. const LangToOffset* no_language_offset;
  32. const LangToOffset* pt_br_language_offset;
  33. const LangToOffset* zh_cn_language_offset;
  34. const LangToOffset* zh_tw_language_offset;
  35. };
  36. #if DCHECK_IS_ON()
  37. // Returns true if the items in the given range are sorted and lower cased.
  38. bool IsArraySortedAndLowerCased(span<const LangToOffset> languages_to_offset) {
  39. return std::is_sorted(languages_to_offset.begin(),
  40. languages_to_offset.end()) &&
  41. std::all_of(languages_to_offset.begin(), languages_to_offset.end(),
  42. [](const auto& lang) {
  43. auto language = AsStringPiece16(lang.first);
  44. return ToLowerASCII(language) == language;
  45. });
  46. }
  47. #endif // DCHECK_IS_ON()
  48. // Determines the availability of all languages that may be used as aliases in
  49. // GetAliasedLanguageOffset or GetCompatibleNeutralLanguageOffset
  50. AvailableLanguageAliases DetermineAvailableAliases(
  51. span<const LangToOffset> languages_to_offset) {
  52. AvailableLanguageAliases available_aliases = {};
  53. for (const LangToOffset& lang_to_offset : languages_to_offset) {
  54. if (lang_to_offset.first == L"en-gb")
  55. available_aliases.en_gb_language_offset = &lang_to_offset;
  56. else if (lang_to_offset.first == L"en-us")
  57. available_aliases.en_us_language_offset = &lang_to_offset;
  58. else if (lang_to_offset.first == L"es")
  59. available_aliases.es_language_offset = &lang_to_offset;
  60. else if (lang_to_offset.first == L"es-419")
  61. available_aliases.es_419_language_offset = &lang_to_offset;
  62. else if (lang_to_offset.first == L"fil")
  63. available_aliases.fil_language_offset = &lang_to_offset;
  64. else if (lang_to_offset.first == L"iw")
  65. available_aliases.iw_language_offset = &lang_to_offset;
  66. else if (lang_to_offset.first == L"no")
  67. available_aliases.no_language_offset = &lang_to_offset;
  68. else if (lang_to_offset.first == L"pt-br")
  69. available_aliases.pt_br_language_offset = &lang_to_offset;
  70. else if (lang_to_offset.first == L"zh-cn")
  71. available_aliases.zh_cn_language_offset = &lang_to_offset;
  72. else if (lang_to_offset.first == L"zh-tw")
  73. available_aliases.zh_tw_language_offset = &lang_to_offset;
  74. }
  75. // Fallback language must exist.
  76. DCHECK(available_aliases.en_us_language_offset);
  77. return available_aliases;
  78. }
  79. // Returns true if a LangToOffset entry can be found in |languages_to_offset|
  80. // that matches the |language| exactly. |offset| will store the offset of the
  81. // language that matches if any. |languages_to_offset| must be sorted by
  82. // language and all languages must lower case.
  83. bool GetExactLanguageOffset(span<const LangToOffset> languages_to_offset,
  84. const std::wstring& language,
  85. const LangToOffset** matched_language_to_offset) {
  86. DCHECK(matched_language_to_offset);
  87. // Binary search in the sorted arrays to find the offset corresponding
  88. // to a given language |name|.
  89. auto search_result = std::lower_bound(
  90. languages_to_offset.begin(), languages_to_offset.end(), language,
  91. [](const LangToOffset& left, const std::wstring& to_find) {
  92. return left.first < to_find;
  93. });
  94. if (languages_to_offset.end() != search_result &&
  95. search_result->first == language) {
  96. *matched_language_to_offset = &*search_result;
  97. return true;
  98. }
  99. return false;
  100. }
  101. // Returns true if the current language can be aliased to another language.
  102. bool GetAliasedLanguageOffset(const AvailableLanguageAliases& available_aliases,
  103. const std::wstring& language,
  104. const LangToOffset** matched_language_to_offset) {
  105. DCHECK(matched_language_to_offset);
  106. // Alias some English variants to British English (all others wildcard to
  107. // US).
  108. if (available_aliases.en_gb_language_offset &&
  109. (language == L"en-au" || language == L"en-ca" || language == L"en-nz" ||
  110. language == L"en-za")) {
  111. *matched_language_to_offset = available_aliases.en_gb_language_offset;
  112. return true;
  113. }
  114. // Alias es-es to es (all others wildcard to es-419).
  115. if (available_aliases.es_language_offset && language == L"es-es") {
  116. *matched_language_to_offset = available_aliases.es_language_offset;
  117. return true;
  118. }
  119. // Google web properties use iw for he. Handle both just to be safe.
  120. if (available_aliases.iw_language_offset && language == L"he") {
  121. *matched_language_to_offset = available_aliases.iw_language_offset;
  122. return true;
  123. }
  124. // Google web properties use no for nb. Handle both just to be safe.
  125. if (available_aliases.no_language_offset && language == L"nb") {
  126. *matched_language_to_offset = available_aliases.no_language_offset;
  127. return true;
  128. }
  129. // Some Google web properties use tl for fil. Handle both just to be safe.
  130. // They're not completely identical, but alias it here.
  131. if (available_aliases.fil_language_offset && language == L"tl") {
  132. *matched_language_to_offset = available_aliases.fil_language_offset;
  133. return true;
  134. }
  135. if (available_aliases.zh_cn_language_offset &&
  136. // Pre-Vista alias for Chinese w/ script subtag.
  137. (language == L"zh-chs" ||
  138. // Vista+ alias for Chinese w/ script subtag.
  139. language == L"zh-hans" ||
  140. // Although the wildcard entry for zh would result in this, alias zh-sg
  141. // so that it will win if it precedes another valid tag in a list of
  142. // candidates.
  143. language == L"zh-sg")) {
  144. *matched_language_to_offset = available_aliases.zh_cn_language_offset;
  145. return true;
  146. }
  147. if (available_aliases.zh_tw_language_offset &&
  148. // Pre-Vista alias for Chinese w/ script subtag.
  149. (language == L"zh-cht" ||
  150. // Vista+ alias for Chinese w/ script subtag.
  151. language == L"zh-hant" ||
  152. // Alias Hong Kong and Macau to Taiwan.
  153. language == L"zh-hk" || language == L"zh-mo")) {
  154. *matched_language_to_offset = available_aliases.zh_tw_language_offset;
  155. return true;
  156. }
  157. return false;
  158. }
  159. // Returns true if the current neutral language can be aliased to another
  160. // language.
  161. bool GetCompatibleNeutralLanguageOffset(
  162. const AvailableLanguageAliases& available_aliases,
  163. const std::wstring& neutral_language,
  164. const LangToOffset** matched_language_to_offset) {
  165. DCHECK(matched_language_to_offset);
  166. if (available_aliases.en_us_language_offset && neutral_language == L"en") {
  167. // Use the U.S. region for anything English.
  168. *matched_language_to_offset = available_aliases.en_us_language_offset;
  169. return true;
  170. }
  171. if (available_aliases.es_419_language_offset && neutral_language == L"es") {
  172. // Use the Latin American region for anything Spanish.
  173. *matched_language_to_offset = available_aliases.es_419_language_offset;
  174. return true;
  175. }
  176. if (available_aliases.pt_br_language_offset && neutral_language == L"pt") {
  177. // Use the Brazil region for anything Portugese.
  178. *matched_language_to_offset = available_aliases.pt_br_language_offset;
  179. return true;
  180. }
  181. if (available_aliases.zh_cn_language_offset && neutral_language == L"zh") {
  182. // Use the P.R.C. region for anything Chinese.
  183. *matched_language_to_offset = available_aliases.zh_cn_language_offset;
  184. return true;
  185. }
  186. return false;
  187. }
  188. // Runs through the set of candidates, sending their downcased representation
  189. // through |select_predicate|. Returns true if the predicate selects a
  190. // candidate, in which case |matched_name| is assigned the value of the
  191. // candidate and |matched_offset| is assigned the language offset of the
  192. // selected translation.
  193. // static
  194. bool SelectIf(const std::vector<std::wstring>& candidates,
  195. span<const LangToOffset> languages_to_offset,
  196. const AvailableLanguageAliases& available_aliases,
  197. const LangToOffset** matched_language_to_offset,
  198. std::wstring* matched_name) {
  199. DCHECK(matched_language_to_offset);
  200. DCHECK(matched_name);
  201. // Note: always perform the exact match first so that an alias is never
  202. // selected in place of a future translation.
  203. // An earlier candidate entry matching on an exact match or alias match takes
  204. // precedence over a later candidate entry matching on an exact match.
  205. for (const std::wstring& scan : candidates) {
  206. std::wstring lower_case_candidate =
  207. AsWString(ToLowerASCII(AsStringPiece16(scan)));
  208. if (GetExactLanguageOffset(languages_to_offset, lower_case_candidate,
  209. matched_language_to_offset) ||
  210. GetAliasedLanguageOffset(available_aliases, lower_case_candidate,
  211. matched_language_to_offset)) {
  212. matched_name->assign(scan);
  213. return true;
  214. }
  215. }
  216. // If no candidate matches exactly or by alias, try to match by locale neutral
  217. // language.
  218. for (const std::wstring& scan : candidates) {
  219. std::wstring lower_case_candidate =
  220. AsWString(ToLowerASCII(AsStringPiece16(scan)));
  221. // Extract the locale neutral language from the language to search and try
  222. // to find an exact match for that language in the provided table.
  223. std::wstring neutral_language =
  224. lower_case_candidate.substr(0, lower_case_candidate.find(L'-'));
  225. if (GetCompatibleNeutralLanguageOffset(available_aliases, neutral_language,
  226. matched_language_to_offset)) {
  227. matched_name->assign(scan);
  228. return true;
  229. }
  230. }
  231. return false;
  232. }
  233. void SelectLanguageMatchingCandidate(
  234. const std::vector<std::wstring>& candidates,
  235. span<const LangToOffset> languages_to_offset,
  236. size_t* selected_offset,
  237. std::wstring* matched_candidate,
  238. std::wstring* selected_language) {
  239. DCHECK(selected_offset);
  240. DCHECK(matched_candidate);
  241. DCHECK(selected_language);
  242. DCHECK(!languages_to_offset.empty());
  243. DCHECK_EQ(static_cast<size_t>(*selected_offset), languages_to_offset.size());
  244. DCHECK(matched_candidate->empty());
  245. DCHECK(selected_language->empty());
  246. // Note: While DCHECK_IS_ON() seems redundant here, this is required to avoid
  247. // compilation errors, since IsArraySortedAndLowerCased is not defined
  248. // otherwise.
  249. #if DCHECK_IS_ON()
  250. DCHECK(IsArraySortedAndLowerCased(languages_to_offset))
  251. << "languages_to_offset is not sorted and lower cased";
  252. #endif // DCHECK_IS_ON()
  253. // Get which languages that are commonly used as aliases and wildcards are
  254. // available for use to match candidates.
  255. AvailableLanguageAliases available_aliases =
  256. DetermineAvailableAliases(languages_to_offset);
  257. // The fallback must exist.
  258. DCHECK(available_aliases.en_us_language_offset);
  259. // Try to find the first matching candidate from all the language mappings
  260. // that are given. Failing that, used en-us as the fallback language.
  261. const LangToOffset* matched_language_to_offset = nullptr;
  262. if (!SelectIf(candidates, languages_to_offset, available_aliases,
  263. &matched_language_to_offset, matched_candidate)) {
  264. matched_language_to_offset = available_aliases.en_us_language_offset;
  265. *matched_candidate =
  266. std::wstring(available_aliases.en_us_language_offset->first);
  267. }
  268. DCHECK(matched_language_to_offset);
  269. // Get the real language being used for the matched candidate.
  270. *selected_language = std::wstring(matched_language_to_offset->first);
  271. *selected_offset = matched_language_to_offset->second;
  272. }
  273. std::vector<std::wstring> GetCandidatesFromSystem(
  274. WStringPiece preferred_language) {
  275. std::vector<std::wstring> candidates;
  276. // Get the initial candidate list for this particular implementation (if
  277. // applicable).
  278. if (!preferred_language.empty())
  279. candidates.emplace_back(preferred_language);
  280. // Now try the UI languages. Use the thread preferred ones since that will
  281. // kindly return us a list of all kinds of fallbacks.
  282. win::i18n::GetThreadPreferredUILanguageList(&candidates);
  283. return candidates;
  284. }
  285. } // namespace
  286. LanguageSelector::LanguageSelector(WStringPiece preferred_language,
  287. span<const LangToOffset> languages_to_offset)
  288. : LanguageSelector(GetCandidatesFromSystem(preferred_language),
  289. languages_to_offset) {}
  290. LanguageSelector::LanguageSelector(const std::vector<std::wstring>& candidates,
  291. span<const LangToOffset> languages_to_offset)
  292. : selected_offset_(languages_to_offset.size()) {
  293. SelectLanguageMatchingCandidate(candidates, languages_to_offset,
  294. &selected_offset_, &matched_candidate_,
  295. &selected_language_);
  296. }
  297. LanguageSelector::~LanguageSelector() = default;
  298. } // namespace i18n
  299. } // namespace win
  300. } // namespace base