Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(52)

Side by Side Diff: chrome/installer/util/language_selector.cc

Issue 4139010: The UI language rather than the locale is now used to pick Chrome's language ... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 10 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2010 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.
7
8 #include "chrome/installer/util/language_selector.h"
9
10 #include <algorithm>
11 #include <functional>
12
13 #include "base/logging.h"
14 #include "base/string_util.h"
15 #include "base/win/i18n.h"
16 #include "chrome/installer/util/google_update_settings.h"
17
18 #include "installer_util_strings.h"
19
20 namespace {
21
22 struct LangToOffset {
23 const wchar_t* language;
24 int offset;
25 };
26
27 // The langauge we fall back upon when all else fails.
28 const wchar_t kFallbackLanguage[] = L"en-us";
29 const int kFallbackLanguageOffset = IDS_L10N_OFFSET_EN_US;
30
31 // http://tools.ietf.org/html/rfc5646 Section 2.3.3
32 const std::wstring::size_type kScriptSubtagLength = 4;
33
34 // A sorted array of language identifiers (and their offsets) for which
35 // translations are available. The contents of the array are generated by
36 // create_string_rc.py.
37 const LangToOffset kLanguageOffsetPairs[] = {
38 #if defined(GOOGLE_CHROME_BUILD)
39 #define HANDLE_LANGUAGE(l_, o_) { L#l_, o_ },
40 DO_LANGUAGES
41 #undef HANDLE_LANGUAGE
42 #else // defined(GOOGLE_CHROME_BUILD)
43 { &kFallbackLanguage[0], kFallbackLanguageOffset }
44 #endif // !defined(GOOGLE_CHROME_BUILD)
45 };
46
47 // A sorted array of language identifiers that are aliases to other languages
48 // for which translations are available.
49 const LangToOffset kLanguageToOffsetExceptions[] = {
50 #if defined(GOOGLE_CHROME_BUILD)
51 // Google web properties use iw for he. Handle both just to be safe.
52 { L"he", IDS_L10N_OFFSET_IW },
53 // Google web properties use no for nb. Handle both just to be safe.
54 { L"nb", IDS_L10N_OFFSET_NO },
55 // Some Google web properties use tl for fil. Handle both just to be safe.
56 // They're not completely identical, but alias it here.
57 { L"tl", IDS_L10N_OFFSET_FIL },
58 // Pre-Vista aliases for Chinese w/ script subtag.
59 { L"zh-chs", IDS_L10N_OFFSET_ZH_CN },
60 { L"zh-cht", IDS_L10N_OFFSET_ZH_TW },
61 // Vista+ aliases for Chinese w/ script subtag.
62 { L"zh-hans", IDS_L10N_OFFSET_ZH_CN },
63 { L"zh-hant", IDS_L10N_OFFSET_ZH_TW },
64 // Alias Macau and Hong Kong to Taiwan.
65 { L"zh-hk", IDS_L10N_OFFSET_ZH_TW },
66 { L"zh-mk", IDS_L10N_OFFSET_ZH_TW },
67 // Windows uses "mo" for Macau.
68 { L"zh-mo", IDS_L10N_OFFSET_ZH_TW }
jungshik at Google 2010/11/11 20:30:45 Does Windows have zh-SG (Singpore)? If so, zh-sg h
grt (UTC plus 2) 2010/11/12 04:22:02 Yes, Windows does. This is handled by the wildcar
69 #else // defined(GOOGLE_CHROME_BUILD)
70 // An empty array is no good, so repeat the fallback.
71 { &kFallbackLanguage[0], kFallbackLanguageOffset }
72 #endif // !defined(GOOGLE_CHROME_BUILD)
73 };
74
75 // A sorted array of neutral language identifiers that are wildcard aliases to
76 // other languages for which translations are available.
77 const LangToOffset kLanguageToOffsetWildcards[] = {
78 // Use the U.S. region for anything English.
79 { L"en", IDS_L10N_OFFSET_EN_US },
80 #if defined(GOOGLE_CHROME_BUILD)
81 // Use the Latin American region for anything Spanish.
82 { L"es", IDS_L10N_OFFSET_ES_419 },
83 // Use the Portugal region for anything Portugese.
jungshik at Google 2010/11/11 20:30:45 I guess it's better to map this to pt_BR (Brazil i
grt (UTC plus 2) 2010/11/12 04:22:02 The previous behavior was to only use pt-br when i
84 { L"pt", IDS_L10N_OFFSET_PT_PT },
85 // Use the P.R.C. region for anything Chinese.
86 { L"zh", IDS_L10N_OFFSET_ZH_CN }
87 #endif // defined(GOOGLE_CHROME_BUILD)
88 };
89
90 #if !defined(NDEBUG)
91 // Returns true if the items in the given range are sorted. If
92 // |byNameAndOffset| is true, the items must be sorted by both name and offset.
93 bool IsArraySorted(const LangToOffset* first, const LangToOffset* last,
94 bool byNameAndOffset) {
95 if (last - first > 1) {
96 for (--last; first != last; ++first) {
97 if (!(std::wstring(first->language) < (first + 1)->language) ||
98 byNameAndOffset && !(first->offset < (first + 1)->offset)) {
99 return false;
100 }
101 }
102 }
103 return true;
104 }
105
106 // Validates that the static read-only mappings are properly sorted.
107 void ValidateMappings() {
108 // Ensure that kLanguageOffsetPairs is sorted.
109 DCHECK(IsArraySorted(&kLanguageOffsetPairs[0],
110 &kLanguageOffsetPairs[arraysize(kLanguageOffsetPairs)],
111 true)) << "kOffsetToLanguageId is not sorted";
112
113 // Ensure that kLanguageToOffsetExceptions is sorted.
114 DCHECK(IsArraySorted(
115 &kLanguageToOffsetExceptions[0],
116 &kLanguageToOffsetExceptions[arraysize(kLanguageToOffsetExceptions)],
117 false)) << "kLanguageToOffsetExceptions is not sorted";
118
119 // Ensure that kLanguageToOffsetWildcards is sorted.
120 DCHECK(IsArraySorted(
121 &kLanguageToOffsetWildcards[0],
122 &kLanguageToOffsetWildcards[arraysize(kLanguageToOffsetWildcards)],
123 false)) << "kLanguageToOffsetWildcards is not sorted";
124 }
125 #endif // !defined(NDEBUG)
126
127 // A less-than overload to do slightly more efficient searches in the
128 // sorted arrays.
129 bool operator<(const LangToOffset& left, const std::wstring& right) {
130 return left.language < right;
131 }
132
133 // A less-than overload to do slightly more efficient searches in the
134 // sorted arrays.
135 bool operator<(const std::wstring& left, const LangToOffset& right) {
136 return left < right.language;
137 }
138
139 // A not-so-efficient less-than overload for the same uses as above.
140 bool operator<(const LangToOffset& left, const LangToOffset& right) {
141 return std::wstring(left.language) < right.language;
142 }
143
144 // A compare function for searching in a sorted array by offset.
145 bool IsOffsetLessThan(const LangToOffset& left, const LangToOffset& right) {
146 return left.offset < right.offset;
147 }
148
149 // Binary search in one of the sorted arrays to find the offset corresponding to
150 // a given language |name|.
151 bool TryFindOffset(const LangToOffset* first, const LangToOffset* last,
152 const std::wstring& name, int* offset) {
153 const LangToOffset* search_result = std::lower_bound(first, last, name);
154 if (last != search_result && search_result->language == name) {
155 *offset = search_result->offset;
156 return true;
157 }
158 return false;
159 }
160
161 // Splits a language tag into |language_part|[-|script_part|][-|region_part|].
162 void CrackLanguageTag(const std::wstring& language, std::wstring* language_part,
163 std::wstring* script_part, std::wstring* region_part) {
jungshik at Google 2010/11/11 20:30:45 Have you considered using ICU's Locale class? That
grt (UTC plus 2) 2010/11/12 04:22:02 Thanks for the pointer. As it happens, I'd have t
164 std::wstring::size_type first_dash_pos = language.find(L'-');
165 std::wstring::size_type second_dash_pos =
166 language.find(L'-', first_dash_pos + 1);
167 DCHECK(std::wstring::npos == second_dash_pos ||
168 std::wstring::npos == language.find(L'-', second_dash_pos + 1))
169 << "Language tags with more than three subtags are not supported.";
170
171 // Everything before the first dash is the primary language subtag.
172 if (0 != first_dash_pos && std::wstring::npos != first_dash_pos) {
173 language_part->assign(language, 0, first_dash_pos);
174 }
175
176 if (second_dash_pos == std::wstring::npos) {
177 // There are only two subtags. If the second is four characters, consider
178 // it to be the script subtag; otherwise, consider it the region subtag.
179 if (language.size() - kScriptSubtagLength - 1 == first_dash_pos) {
180 script_part->assign(language, first_dash_pos + 1, kScriptSubtagLength);
181 region_part->clear();
182 } else {
183 script_part->clear();
184 region_part->assign(language, first_dash_pos + 1, std::wstring::npos);
185 }
186 } else {
187 // There are three or more subtags. Consider the second to be a script
188 // subtag regardless of its length (pre-Vista had things like zh-chs).
189 VLOG_IF(1, second_dash_pos - kScriptSubtagLength - 1 != first_dash_pos)
190 << "Returning a script subtag that is not four chars long.";
191 script_part->assign(language, first_dash_pos + 1,
192 second_dash_pos - first_dash_pos - 1);
193 region_part->assign(language, second_dash_pos + 1, std::wstring::npos);
194 }
195 }
196
197 // A predicate function for LanguageSelector::SelectIf that searches for the
198 // offset of a translated language. The search first tries to find an exact
199 // match. Failing that, an exact match with an alias is attempted.
200 bool GetLanguageOffset(const std::wstring& language, int* offset) {
201 // Note: always perform the exact match first so that an alias is never
202 // selected in place of a future translation.
203 return
204 TryFindOffset(
205 &kLanguageOffsetPairs[0],
206 &kLanguageOffsetPairs[arraysize(kLanguageOffsetPairs)],
207 language, offset) ||
208 TryFindOffset(
209 &kLanguageToOffsetExceptions[0],
210 &kLanguageToOffsetExceptions[arraysize(kLanguageToOffsetExceptions)],
211 language, offset);
212 }
213
214 // A predicate function for LanguageSelector::SelectIf that searches for a
215 // wildcard match with |language|'s primary language subtag.
216 bool MatchLanguageOffset(const std::wstring& language, int* offset) {
217 std::wstring language_part;
218 std::wstring script_part;
219 std::wstring region_part;
220
221 // split lang[-script][-reg]
222 CrackLanguageTag(language, &language_part, &script_part, &region_part);
223
224 // Now check for wildcards.
225 return
226 TryFindOffset(
227 &kLanguageToOffsetWildcards[0],
228 &kLanguageToOffsetWildcards[arraysize(kLanguageToOffsetWildcards)],
229 language_part, offset);
230 }
231
232 // Adds to |candidates| the eligible languages on the system. Any language
233 // setting specified by Omaha takes precedence over the operating system's
234 // configured languages.
235 void GetCandidatesFromSystem(std::vector<std::wstring>* candidates) {
236 DCHECK(candidates);
237 std::wstring language;
238
239 // Omaha gets first pick.
240 GoogleUpdateSettings::GetLanguage(&language);
241 if (!language.empty()) {
242 candidates->push_back(language);
243 }
244
245 // Now try the Windows UI languages. Use the thread preferred since that will
246 // kindly return us a list of all kinds of fallbacks.
247 base::win::i18n::GetThreadPreferredUILanguageList(candidates);
248 }
249
250 } // namespace
251
252 namespace installer_util {
253
254 LanguageSelector::LanguageSelector()
255 : offset_(arraysize(kLanguageOffsetPairs)) {
256 #if !defined(NDEBUG)
257 ValidateMappings();
258 #endif // !defined(NDEBUG)
259 std::vector<std::wstring> candidates;
260
261 GetCandidatesFromSystem(&candidates);
262 DoSelect(candidates);
263 }
264
265 LanguageSelector::LanguageSelector(const std::vector<std::wstring>& candidates)
266 : offset_(arraysize(kLanguageOffsetPairs)) {
267 #if !defined(NDEBUG)
268 ValidateMappings();
269 #endif // !defined(NDEBUG)
270 DoSelect(candidates);
271 }
272
273 LanguageSelector::~LanguageSelector() {
274 }
275
276 // static
277 std::wstring LanguageSelector::GetLanguageName(int offset) {
278 DCHECK_GE(offset, 0);
279 DCHECK_LT(static_cast<size_t>(offset), arraysize(kLanguageOffsetPairs));
280
281 LangToOffset value = { NULL, offset };
282 const LangToOffset* search_result =
283 std::lower_bound(&kLanguageOffsetPairs[0],
284 &kLanguageOffsetPairs[arraysize(kLanguageOffsetPairs)],
285 value, IsOffsetLessThan);
286 if (&kLanguageOffsetPairs[arraysize(kLanguageOffsetPairs)] != search_result &&
287 search_result->offset == offset) {
288 return search_result->language;
289 }
290 NOTREACHED() << "Unknown language offset.";
291 return std::wstring(&kFallbackLanguage[0], arraysize(kFallbackLanguage) - 1);
292 }
293
294 // Runs through the set of candidates, sending their downcased representation
295 // through |select_predicate|. Returns true if the predicate selects a
296 // candidate, in which case |matched_name| is assigned the value of the
297 // candidate and |matched_offset| is assigned the language offset of the
298 // selected translation.
299 // static
300 bool LanguageSelector::SelectIf(const std::vector<std::wstring>& candidates,
301 SelectPred_Fn select_predicate,
302 std::wstring* matched_name,
303 int* matched_offset) {
304 std::wstring candidate;
305 for (std::vector<std::wstring>::const_iterator scan = candidates.begin(),
306 end = candidates.end(); scan != end; ++scan) {
307 candidate.assign(*scan);
308 StringToLowerASCII(&candidate);
309 if (select_predicate(candidate, matched_offset)) {
310 matched_name->assign(*scan);
311 return true;
312 }
313 }
314
315 return false;
316 }
317
318 // Select the best-fit translation from the ordered list |candidates|.
319 // At the conclusion, this instance's |matched_candidate_| and |offset_| members
320 // are set to the name of the selected candidate and the offset of the matched
321 // translation. If no translation is selected, the fallback's name and offset
322 // are selected.
323 void LanguageSelector::DoSelect(const std::vector<std::wstring>& candidates) {
324 // Make a pass through the candidates looking for an exact or alias match.
325 // Failing that, make another pass looking for a wildcard match.
326 if (!SelectIf(candidates, &GetLanguageOffset, &matched_candidate_,
327 &offset_) &&
328 !SelectIf(candidates, &MatchLanguageOffset, &matched_candidate_,
329 &offset_)) {
330 VLOG(1) << "No suitable language found for any candidates.";
331
332 // Our fallback is "en-us"
333 matched_candidate_.assign(&kFallbackLanguage[0],
334 arraysize(kFallbackLanguage) - 1);
335 offset_ = kFallbackLanguageOffset;
336 }
337 }
338
339 } // namespace installer_util
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698