diff options
author | grt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-16 15:28:45 +0000 |
---|---|---|
committer | grt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-16 15:28:45 +0000 |
commit | 337ca0791390f606472d7530abf7603ad2f24ca8 (patch) | |
tree | 660625c6c058dab2acee91549ba258258bdc70f5 /chrome | |
parent | 40519596601f58e38c22b3ff9f2d8a47301e7c25 (diff) | |
download | chromium_src-337ca0791390f606472d7530abf7603ad2f24ca8.zip chromium_src-337ca0791390f606472d7530abf7603ad2f24ca8.tar.gz chromium_src-337ca0791390f606472d7530abf7603ad2f24ca8.tar.bz2 |
The UI language rather than the locale is now used to pick Chrome's language on Windows. (Also fixed some unrelated lint errors.) With this change, l10n_util::GetApplicationLocale first checks for an override of the "configured locale" (in base::i18n) containing the list of preferred Windows UI languages. The browser triggers an override early in startup before the ApplicationLocale is determined. In effect, the browser no longer uses ICU on Windows for language detection. (This locale override mechanism is borrowed from the OS X port.)
Changes in Chrome Frame are largely a refactor, as some Win32 code in there has been moved into base/win.
Also cleaned up language selection in installer_util so that the proper language is chosen for the EULA, installer messages, and shortcuts. In so doing, replaced hand-crafted lists of supported languages with either auto-generated lists (static consts) or logic so that the addition of translations in the future doesn't require code motion (that being said, there may be reason to update the alias and/or wildcard tables in language_selector.cc). In so doing, this change unlocks Amharic, Farsi, and Swahili translations for installer messages and shortcuts.
BUG=39986,40496,26470
TEST=New MUI/Win32 calls are tested in base/win/i18n_unittest.cc. To test the overall functionality, uninstall Chrome, remove intl.app_locale user pref, switch to a supported display language (via the "Keyboards and Languages" tab of Win7's "Regional and Language" control panel, and install with { "distribution": { "require_eula": true } } in master_preferences (via -installerdata arg to setup.exe). If all goes well, both EULA and outer frame are in the same language as Windows. Also, from gwilson: "Install system-level Chrome in audit mode on a new machine, then go through the out-of-box-experience, select a language, and the in -product EULA (triggered by "require_eula" : true) and Chrome's UI should be in the language that the user selected."
Review URL: http://codereview.chromium.org/4139010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@66275 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/browser_main_win.cc | 9 | ||||
-rw-r--r-- | chrome/chrome_installer.gypi | 1 | ||||
-rw-r--r-- | chrome/chrome_installer_util.gypi | 2 | ||||
-rw-r--r-- | chrome/installer/util/l10n_string_util.cc | 226 | ||||
-rw-r--r-- | chrome/installer/util/language_selector.cc | 301 | ||||
-rw-r--r-- | chrome/installer/util/language_selector.h | 60 | ||||
-rw-r--r-- | chrome/installer/util/language_selector_unittest.cc | 128 | ||||
-rwxr-xr-x | chrome/installer/util/prebuild/create_string_rc.py | 20 |
8 files changed, 530 insertions, 217 deletions
diff --git a/chrome/browser/browser_main_win.cc b/chrome/browser/browser_main_win.cc index 7536a3b..de6f2f1 100644 --- a/chrome/browser/browser_main_win.cc +++ b/chrome/browser/browser_main_win.cc @@ -11,6 +11,7 @@ #include <algorithm> #include "app/l10n_util.h" +#include "app/l10n_util_win.h" #include "app/win_util.h" #include "base/command_line.h" #include "base/environment.h" @@ -26,6 +27,7 @@ #include "chrome/browser/views/uninstall_view.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/env_vars.h" +#include "chrome/common/main_function_params.h" #include "chrome/common/result_codes.h" #include "chrome/installer/util/helper.h" #include "chrome/installer/util/install_util.h" @@ -217,6 +219,13 @@ class BrowserMainPartsWin : public BrowserMainParts { virtual void PreMainMessageLoopStart() { OleInitialize(NULL); + + // If we're running tests (ui_task is non-null), then the ResourceBundle + // has already been initialized. + if (!parameters().ui_task) { + // Override the configured locale with the user's preferred UI language. + l10n_util::OverrideLocaleWithUILanguageList(); + } } private: diff --git a/chrome/chrome_installer.gypi b/chrome/chrome_installer.gypi index 8aeea23..c431fac 100644 --- a/chrome/chrome_installer.gypi +++ b/chrome/chrome_installer.gypi @@ -88,6 +88,7 @@ 'installer/util/helper_unittest.cc', 'installer/util/installer_util_unittests.rc', 'installer/util/installer_util_unittests_resource.h', + 'installer/util/language_selector_unittest.cc', 'installer/util/lzma_util_unittest.cc', 'installer/util/master_preferences_unittest.cc', 'installer/util/move_tree_work_item_unittest.cc', diff --git a/chrome/chrome_installer_util.gypi b/chrome/chrome_installer_util.gypi index 721d62b..02603bf 100644 --- a/chrome/chrome_installer_util.gypi +++ b/chrome/chrome_installer_util.gypi @@ -35,6 +35,8 @@ 'installer/util/install_util.h', 'installer/util/l10n_string_util.cc', 'installer/util/l10n_string_util.h', + 'installer/util/language_selector.cc', + 'installer/util/language_selector.h', 'installer/util/master_preferences_constants.cc', 'installer/util/master_preferences_constants.h', 'installer/util/move_tree_work_item.cc', diff --git a/chrome/installer/util/l10n_string_util.cc b/chrome/installer/util/l10n_string_util.cc index 2f571de..ef30a01 100644 --- a/chrome/installer/util/l10n_string_util.cc +++ b/chrome/installer/util/l10n_string_util.cc @@ -6,167 +6,18 @@ // Google Chrome. #include <atlbase.h> -#include <shlwapi.h> - -#include <map> #include "base/logging.h" #include "base/scoped_ptr.h" #include "base/string_util.h" - -#include "google_update_settings.h" -#include "installer_util_strings.h" +#include "chrome/installer/util/language_selector.h" namespace { -// Gets the language from the OS, while taking a parameter to optionally pull -// the language from Omaha's settings first. -std::wstring GetSystemLanguage(const bool use_omaha_language) { - static std::wstring language; - if (!language.empty()) - return language; - - if (use_omaha_language) { - // First try to get the language from Omaha, if one is set already. - GoogleUpdateSettings::GetLanguage(&language); - if (!language.empty()) - return language; - } - - // We don't have ICU at this point, so we use win32 apis. - LCID id = GetThreadLocale(); - int length = GetLocaleInfo(id, LOCALE_SISO639LANGNAME, 0, 0); - if (0 == length) { - language = L"en-us"; - return language; - } - length = GetLocaleInfo(id, LOCALE_SISO639LANGNAME, - WriteInto(&language, length), length); - DCHECK(length == static_cast<int>(language.length() + 1)); - StringToLowerASCII(&language); - - // Add the country if we need it. - std::wstring country; - length = GetLocaleInfo(id, LOCALE_SISO3166CTRYNAME, 0, 0); - if (0 != length) { - length = GetLocaleInfo(id, LOCALE_SISO3166CTRYNAME, - WriteInto(&country, length), length); - DCHECK(length == static_cast<int>(country.length() + 1)); - StringToLowerASCII(&country); - if (L"en" == language) { - if (L"gb" == country) { - language.append(L"-gb"); - } else { - language.append(L"-us"); - } - } else if (L"es" == language && L"es" != country) { - language.append(L"-419"); - } else if (L"pt" == language) { - if (L"br" == country) { - language.append(L"-br"); - } else { - language.append(L"-pt"); - } - } else if (L"zh" == language) { - if (L"tw" == country || L"mk" == country || L"hk" == country) { - language.append(L"-tw"); - } else { - language.append(L"-cn"); - } - } - } +const installer_util::LanguageSelector& GetLanguageSelector() { + static const installer_util::LanguageSelector instance; - if (language.empty()) - language = L"en-us"; - - return language; -} - -// Gets the language from the OS. If we're unable to get the system language, -// defaults to en-us. -std::wstring GetSystemLanguage() { - return GetSystemLanguage(false); -} - -// This method returns the appropriate language offset given the language as a -// string. Note: This method is not thread safe because of how we create -// |offset_map|. -int GetLanguageOffset(const std::wstring& language) { - static std::map<std::wstring, int> offset_map; - if (offset_map.empty()) { -#if defined(GOOGLE_CHROME_BUILD) - offset_map[L"ar"] = IDS_L10N_OFFSET_AR; - offset_map[L"bg"] = IDS_L10N_OFFSET_BG; - offset_map[L"bn"] = IDS_L10N_OFFSET_BN; - offset_map[L"ca"] = IDS_L10N_OFFSET_CA; - offset_map[L"cs"] = IDS_L10N_OFFSET_CS; - offset_map[L"da"] = IDS_L10N_OFFSET_DA; - offset_map[L"de"] = IDS_L10N_OFFSET_DE; - offset_map[L"el"] = IDS_L10N_OFFSET_EL; - offset_map[L"en-gb"] = IDS_L10N_OFFSET_EN_GB; - offset_map[L"en-us"] = IDS_L10N_OFFSET_EN_US; - offset_map[L"es"] = IDS_L10N_OFFSET_ES; - offset_map[L"es-419"] = IDS_L10N_OFFSET_ES_419; - offset_map[L"et"] = IDS_L10N_OFFSET_ET; - offset_map[L"fi"] = IDS_L10N_OFFSET_FI; - offset_map[L"fil"] = IDS_L10N_OFFSET_FIL; - offset_map[L"fr"] = IDS_L10N_OFFSET_FR; - offset_map[L"gu"] = IDS_L10N_OFFSET_GU; - offset_map[L"he"] = IDS_L10N_OFFSET_IW; - offset_map[L"hi"] = IDS_L10N_OFFSET_HI; - offset_map[L"hr"] = IDS_L10N_OFFSET_HR; - offset_map[L"hu"] = IDS_L10N_OFFSET_HU; - offset_map[L"id"] = IDS_L10N_OFFSET_ID; - offset_map[L"it"] = IDS_L10N_OFFSET_IT; - // Google web properties use iw for he. Handle both just to be safe. - offset_map[L"iw"] = IDS_L10N_OFFSET_IW; - offset_map[L"ja"] = IDS_L10N_OFFSET_JA; - offset_map[L"kn"] = IDS_L10N_OFFSET_KN; - offset_map[L"ko"] = IDS_L10N_OFFSET_KO; - offset_map[L"lt"] = IDS_L10N_OFFSET_LT; - offset_map[L"lv"] = IDS_L10N_OFFSET_LV; - offset_map[L"ml"] = IDS_L10N_OFFSET_ML; - offset_map[L"mr"] = IDS_L10N_OFFSET_MR; - // Google web properties use no for nb. Handle both just to be safe. - offset_map[L"nb"] = IDS_L10N_OFFSET_NO; - offset_map[L"nl"] = IDS_L10N_OFFSET_NL; - offset_map[L"no"] = IDS_L10N_OFFSET_NO; - offset_map[L"pl"] = IDS_L10N_OFFSET_PL; - offset_map[L"pt-br"] = IDS_L10N_OFFSET_PT_BR; - offset_map[L"pt-pt"] = IDS_L10N_OFFSET_PT_PT; - offset_map[L"ro"] = IDS_L10N_OFFSET_RO; - offset_map[L"ru"] = IDS_L10N_OFFSET_RU; - offset_map[L"sk"] = IDS_L10N_OFFSET_SK; - offset_map[L"sl"] = IDS_L10N_OFFSET_SL; - offset_map[L"sr"] = IDS_L10N_OFFSET_SR; - offset_map[L"sv"] = IDS_L10N_OFFSET_SV; - offset_map[L"ta"] = IDS_L10N_OFFSET_TA; - offset_map[L"te"] = IDS_L10N_OFFSET_TE; - offset_map[L"th"] = IDS_L10N_OFFSET_TH; - // Some Google web properties use tl for fil. Handle both just to be safe. - // They're not completely identical, but alias it here. - offset_map[L"tl"] = IDS_L10N_OFFSET_FIL; - offset_map[L"tr"] = IDS_L10N_OFFSET_TR; - offset_map[L"uk"] = IDS_L10N_OFFSET_UK; - offset_map[L"vi"] = IDS_L10N_OFFSET_VI; - offset_map[L"zh-cn"] = IDS_L10N_OFFSET_ZH_CN; - offset_map[L"zh-tw"] = IDS_L10N_OFFSET_ZH_TW; -#else // GOOGLE_CHROME_BUILD not defined - offset_map[L"en-us"] = IDS_L10N_OFFSET_EN_US; -#endif // if defined(GOOGLE_CHROME_BUILD) - } - - std::map<std::wstring, int>::iterator it = offset_map.find( - StringToLowerASCII(language)); - if (it != offset_map.end()) - return it->second; - -#if defined(GOOGLE_CHROME_BUILD) - NOTREACHED() << "unknown system language-country"; -#endif // if defined(GOOGLE_CHROME_BUILD) - - // Fallback on the en-US offset just in case. - return IDS_L10N_OFFSET_EN_US; + return instance; } } // namespace @@ -174,10 +25,9 @@ int GetLanguageOffset(const std::wstring& language) { namespace installer_util { std::wstring GetLocalizedString(int base_message_id) { - std::wstring language = GetSystemLanguage(); std::wstring localized_string; - int message_id = base_message_id + GetLanguageOffset(language); + int message_id = base_message_id + GetLanguageSelector().offset(); const ATLSTRINGRESOURCEIMAGE* image = AtlGetStringResourceImage( _AtlBaseModule.GetModuleInstance(), message_id); if (image) { @@ -196,66 +46,22 @@ std::wstring GetLocalizedEulaResource() { int len = ::GetModuleFileName(NULL, full_exe_path, MAX_PATH); if (len == 0 || len == MAX_PATH) return L""; - std::wstring language = GetSystemLanguage(true); - const wchar_t* resource = L"IDR_OEMPG_EN.HTML"; - static std::map<int, wchar_t*> html_map; - if (html_map.empty()) { -#if defined(GOOGLE_CHROME_BUILD) - html_map[IDS_L10N_OFFSET_AR] = L"IDR_OEMPG_AR.HTML"; - html_map[IDS_L10N_OFFSET_BG] = L"IDR_OEMPG_BG.HTML"; - html_map[IDS_L10N_OFFSET_CA] = L"IDR_OEMPG_CA.HTML"; - html_map[IDS_L10N_OFFSET_CS] = L"IDR_OEMPG_CS.HTML"; - html_map[IDS_L10N_OFFSET_DA] = L"IDR_OEMPG_DA.HTML"; - html_map[IDS_L10N_OFFSET_DE] = L"IDR_OEMPG_DE.HTML"; - html_map[IDS_L10N_OFFSET_EL] = L"IDR_OEMPG_EL.HTML"; - html_map[IDS_L10N_OFFSET_EN_US] = L"IDR_OEMPG_EN.HTML"; - html_map[IDS_L10N_OFFSET_EN_GB] = L"IDR_OEMPG_EN_GB.HTML"; - html_map[IDS_L10N_OFFSET_ES] = L"IDR_OEMPG_ES.HTML"; - html_map[IDS_L10N_OFFSET_ES_419] = L"IDR_OEMPG_ES_419.HTML"; - html_map[IDS_L10N_OFFSET_ET] = L"IDR_OEMPG_ET.HTML"; - html_map[IDS_L10N_OFFSET_FI] = L"IDR_OEMPG_FI.HTML"; - html_map[IDS_L10N_OFFSET_FIL] = L"IDR_OEMPG_FIL.HTML"; - html_map[IDS_L10N_OFFSET_FR] = L"IDR_OEMPG_FR.HTML"; - html_map[IDS_L10N_OFFSET_HI] = L"IDR_OEMPG_HI.HTML"; - html_map[IDS_L10N_OFFSET_HR] = L"IDR_OEMPG_HR.HTML"; - html_map[IDS_L10N_OFFSET_HU] = L"IDR_OEMPG_HU.HTML"; - html_map[IDS_L10N_OFFSET_ID] = L"IDR_OEMPG_ID.HTML"; - html_map[IDS_L10N_OFFSET_IT] = L"IDR_OEMPG_IT.HTML"; - html_map[IDS_L10N_OFFSET_JA] = L"IDR_OEMPG_JA.HTML"; - html_map[IDS_L10N_OFFSET_KO] = L"IDR_OEMPG_KO.HTML"; - html_map[IDS_L10N_OFFSET_LT] = L"IDR_OEMPG_LT.HTML"; - html_map[IDS_L10N_OFFSET_LV] = L"IDR_OEMPG_LV.HTML"; - html_map[IDS_L10N_OFFSET_NL] = L"IDR_OEMPG_NL.HTML"; - html_map[IDS_L10N_OFFSET_NO] = L"IDR_OEMPG_NO.HTML"; - html_map[IDS_L10N_OFFSET_PL] = L"IDR_OEMPG_PL.HTML"; - html_map[IDS_L10N_OFFSET_PT_BR] = L"IDR_OEMPG_PT_BR.HTML"; - html_map[IDS_L10N_OFFSET_PT_PT] = L"IDR_OEMPG_PT_PT.HTML"; - html_map[IDS_L10N_OFFSET_RO] = L"IDR_OEMPG_RO.HTML"; - html_map[IDS_L10N_OFFSET_RU] = L"IDR_OEMPG_RU.HTML"; - html_map[IDS_L10N_OFFSET_SK] = L"IDR_OEMPG_SK.HTML"; - html_map[IDS_L10N_OFFSET_SL] = L"IDR_OEMPG_SL.HTML"; - html_map[IDS_L10N_OFFSET_SR] = L"IDR_OEMPG_SR.HTML"; - html_map[IDS_L10N_OFFSET_SV] = L"IDR_OEMPG_SV.HTML"; - html_map[IDS_L10N_OFFSET_TH] = L"IDR_OEMPG_TH.HTML"; - html_map[IDS_L10N_OFFSET_TR] = L"IDR_OEMPG_TR.HTML"; - html_map[IDS_L10N_OFFSET_UK] = L"IDR_OEMPG_UK.HTML"; - html_map[IDS_L10N_OFFSET_VI] = L"IDR_OEMPG_VI.HTML"; - html_map[IDS_L10N_OFFSET_ZH_CN] = L"IDR_OEMPG_ZH_CN.HTML"; - html_map[IDS_L10N_OFFSET_ZH_TW] = L"IDR_OEMPG_ZH_TW.HTML"; -#else // GOOGLE_CHROME_BUILD not defined - html_map[IDS_L10N_OFFSET_EN_US] = L"IDR_OEMPG_EN.HTML"; -#endif // if defined(GOOGLE_CHROME_BUILD) - } + // The resource names are more or less the upcased language names. + std::wstring language(GetLanguageSelector().selected_translation()); + std::replace(language.begin(), language.end(), L'-', L'_'); + StringToUpperASCII(&language); + + std::wstring resource(L"IDR_OEMPG_"); + resource.append(language).append(L".HTML"); - std::map<int, wchar_t*>::iterator it = html_map.find( - GetLanguageOffset(language)); - if (it != html_map.end()) - resource = it->second; + // Fall back on "en" if we don't have a resource for this language. + if (NULL == FindResource(NULL, resource.c_str(), RT_HTML)) + resource = L"IDR_OEMPG_EN.HTML"; // Spaces and DOS paths must be url encoded. std::wstring url_path = - StringPrintf(L"res://%ls/#23/%ls", full_exe_path, resource); + StringPrintf(L"res://%ls/#23/%ls", full_exe_path, resource.c_str()); // The cast is safe because url_path has limited length // (see the definition of full_exe_path and resource). diff --git a/chrome/installer/util/language_selector.cc b/chrome/installer/util/language_selector.cc new file mode 100644 index 0000000..8aa6fd6 --- /dev/null +++ b/chrome/installer/util/language_selector.cc @@ -0,0 +1,301 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file defines a helper class for selecting a supported language from a +// set of candidates. + +#include "chrome/installer/util/language_selector.h" + +#include <algorithm> +#include <functional> + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/win/i18n.h" +#include "chrome/installer/util/google_update_settings.h" + +#include "installer_util_strings.h" + +namespace { + +struct LangToOffset { + const wchar_t* language; + int offset; +}; + +// The language we fall back upon when all else fails. +const wchar_t kFallbackLanguage[] = L"en-us"; +const int kFallbackLanguageOffset = IDS_L10N_OFFSET_EN_US; + +// http://tools.ietf.org/html/rfc5646 Section 2.3.3 +const std::wstring::size_type kScriptSubtagLength = 4; + +// A sorted array of language identifiers (and their offsets) for which +// translations are available. The contents of the array are generated by +// create_string_rc.py. +const LangToOffset kLanguageOffsetPairs[] = { +#if defined(GOOGLE_CHROME_BUILD) +#define HANDLE_LANGUAGE(l_, o_) { L#l_, o_ }, + DO_LANGUAGES +#undef HANDLE_LANGUAGE +#else // defined(GOOGLE_CHROME_BUILD) + { &kFallbackLanguage[0], kFallbackLanguageOffset } +#endif // !defined(GOOGLE_CHROME_BUILD) +}; + +// A sorted array of language identifiers that are aliases to other languages +// for which translations are available. +const LangToOffset kLanguageToOffsetExceptions[] = { +#if defined(GOOGLE_CHROME_BUILD) + // Google web properties use iw for he. Handle both just to be safe. + { L"he", IDS_L10N_OFFSET_IW }, + // Google web properties use no for nb. Handle both just to be safe. + { L"nb", IDS_L10N_OFFSET_NO }, + // Some Google web properties use tl for fil. Handle both just to be safe. + // They're not completely identical, but alias it here. + { L"tl", IDS_L10N_OFFSET_FIL }, + // Pre-Vista aliases for Chinese w/ script subtag. + { L"zh-chs", IDS_L10N_OFFSET_ZH_CN }, + { L"zh-cht", IDS_L10N_OFFSET_ZH_TW }, + // Vista+ aliases for Chinese w/ script subtag. + { L"zh-hans", IDS_L10N_OFFSET_ZH_CN }, + { L"zh-hant", IDS_L10N_OFFSET_ZH_TW }, + // Alias Macau and Hong Kong to Taiwan. + { L"zh-hk", IDS_L10N_OFFSET_ZH_TW }, + { L"zh-mk", IDS_L10N_OFFSET_ZH_TW }, + // Windows uses "mo" for Macau. + { L"zh-mo", IDS_L10N_OFFSET_ZH_TW }, + // Although the wildcard entry for zh would result in this, alias zh-sg so + // that it will win if it precedes another valid tag in a list of candidates. + { L"zh-sg", IDS_L10N_OFFSET_ZH_CN } +#else // defined(GOOGLE_CHROME_BUILD) + // An empty array is no good, so repeat the fallback. + { &kFallbackLanguage[0], kFallbackLanguageOffset } +#endif // !defined(GOOGLE_CHROME_BUILD) +}; + +// A sorted array of neutral language identifiers that are wildcard aliases to +// other languages for which translations are available. +const LangToOffset kLanguageToOffsetWildcards[] = { + // Use the U.S. region for anything English. + { L"en", IDS_L10N_OFFSET_EN_US }, +#if defined(GOOGLE_CHROME_BUILD) + // Use the Latin American region for anything Spanish. + { L"es", IDS_L10N_OFFSET_ES_419 }, + // Use the Brazil region for anything Portugese. + { L"pt", IDS_L10N_OFFSET_PT_BR }, + // Use the P.R.C. region for anything Chinese. + { L"zh", IDS_L10N_OFFSET_ZH_CN } +#endif // defined(GOOGLE_CHROME_BUILD) +}; + +#if !defined(NDEBUG) +// Returns true if the items in the given range are sorted. If +// |byNameAndOffset| is true, the items must be sorted by both name and offset. +bool IsArraySorted(const LangToOffset* first, const LangToOffset* last, + bool byNameAndOffset) { + if (last - first > 1) { + for (--last; first != last; ++first) { + if (!(std::wstring(first->language) < (first + 1)->language) || + byNameAndOffset && !(first->offset < (first + 1)->offset)) { + return false; + } + } + } + return true; +} + +// Validates that the static read-only mappings are properly sorted. +void ValidateMappings() { + // Ensure that kLanguageOffsetPairs is sorted. + DCHECK(IsArraySorted(&kLanguageOffsetPairs[0], + &kLanguageOffsetPairs[arraysize(kLanguageOffsetPairs)], + true)) << "kOffsetToLanguageId is not sorted"; + + // Ensure that kLanguageToOffsetExceptions is sorted. + DCHECK(IsArraySorted( + &kLanguageToOffsetExceptions[0], + &kLanguageToOffsetExceptions[arraysize(kLanguageToOffsetExceptions)], + false)) << "kLanguageToOffsetExceptions is not sorted"; + + // Ensure that kLanguageToOffsetWildcards is sorted. + DCHECK(IsArraySorted( + &kLanguageToOffsetWildcards[0], + &kLanguageToOffsetWildcards[arraysize(kLanguageToOffsetWildcards)], + false)) << "kLanguageToOffsetWildcards is not sorted"; +} +#endif // !defined(NDEBUG) + +// A less-than overload to do slightly more efficient searches in the +// sorted arrays. +bool operator<(const LangToOffset& left, const std::wstring& right) { + return left.language < right; +} + +// A less-than overload to do slightly more efficient searches in the +// sorted arrays. +bool operator<(const std::wstring& left, const LangToOffset& right) { + return left < right.language; +} + +// A not-so-efficient less-than overload for the same uses as above. +bool operator<(const LangToOffset& left, const LangToOffset& right) { + return std::wstring(left.language) < right.language; +} + +// A compare function for searching in a sorted array by offset. +bool IsOffsetLessThan(const LangToOffset& left, const LangToOffset& right) { + return left.offset < right.offset; +} + +// Binary search in one of the sorted arrays to find the offset corresponding to +// a given language |name|. +bool TryFindOffset(const LangToOffset* first, const LangToOffset* last, + const std::wstring& name, int* offset) { + const LangToOffset* search_result = std::lower_bound(first, last, name); + if (last != search_result && search_result->language == name) { + *offset = search_result->offset; + return true; + } + return false; +} + +// A predicate function for LanguageSelector::SelectIf that searches for the +// offset of a translated language. The search first tries to find an exact +// match. Failing that, an exact match with an alias is attempted. +bool GetLanguageOffset(const std::wstring& language, int* offset) { + // Note: always perform the exact match first so that an alias is never + // selected in place of a future translation. + return + TryFindOffset( + &kLanguageOffsetPairs[0], + &kLanguageOffsetPairs[arraysize(kLanguageOffsetPairs)], + language, offset) || + TryFindOffset( + &kLanguageToOffsetExceptions[0], + &kLanguageToOffsetExceptions[arraysize(kLanguageToOffsetExceptions)], + language, offset); +} + +// A predicate function for LanguageSelector::SelectIf that searches for a +// wildcard match with |language|'s primary language subtag. +bool MatchLanguageOffset(const std::wstring& language, int* offset) { + std::wstring primary_language = language.substr(0, language.find(L'-')); + + // Now check for wildcards. + return + TryFindOffset( + &kLanguageToOffsetWildcards[0], + &kLanguageToOffsetWildcards[arraysize(kLanguageToOffsetWildcards)], + primary_language, offset); +} + +// Adds to |candidates| the eligible languages on the system. Any language +// setting specified by Omaha takes precedence over the operating system's +// configured languages. +void GetCandidatesFromSystem(std::vector<std::wstring>* candidates) { + DCHECK(candidates); + std::wstring language; + + // Omaha gets first pick. + GoogleUpdateSettings::GetLanguage(&language); + if (!language.empty()) { + candidates->push_back(language); + } + + // Now try the Windows UI languages. Use the thread preferred since that will + // kindly return us a list of all kinds of fallbacks. + base::win::i18n::GetThreadPreferredUILanguageList(candidates); +} + +} // namespace + +namespace installer_util { + +LanguageSelector::LanguageSelector() + : offset_(arraysize(kLanguageOffsetPairs)) { +#if !defined(NDEBUG) + ValidateMappings(); +#endif // !defined(NDEBUG) + std::vector<std::wstring> candidates; + + GetCandidatesFromSystem(&candidates); + DoSelect(candidates); +} + +LanguageSelector::LanguageSelector(const std::vector<std::wstring>& candidates) + : offset_(arraysize(kLanguageOffsetPairs)) { +#if !defined(NDEBUG) + ValidateMappings(); +#endif // !defined(NDEBUG) + DoSelect(candidates); +} + +LanguageSelector::~LanguageSelector() { +} + +// static +std::wstring LanguageSelector::GetLanguageName(int offset) { + DCHECK_GE(offset, 0); + DCHECK_LT(static_cast<size_t>(offset), arraysize(kLanguageOffsetPairs)); + + LangToOffset value = { NULL, offset }; + const LangToOffset* search_result = + std::lower_bound(&kLanguageOffsetPairs[0], + &kLanguageOffsetPairs[arraysize(kLanguageOffsetPairs)], + value, IsOffsetLessThan); + if (&kLanguageOffsetPairs[arraysize(kLanguageOffsetPairs)] != search_result && + search_result->offset == offset) { + return search_result->language; + } + NOTREACHED() << "Unknown language offset."; + return std::wstring(&kFallbackLanguage[0], arraysize(kFallbackLanguage) - 1); +} + +// Runs through the set of candidates, sending their downcased representation +// through |select_predicate|. Returns true if the predicate selects a +// candidate, in which case |matched_name| is assigned the value of the +// candidate and |matched_offset| is assigned the language offset of the +// selected translation. +// static +bool LanguageSelector::SelectIf(const std::vector<std::wstring>& candidates, + SelectPred_Fn select_predicate, + std::wstring* matched_name, + int* matched_offset) { + std::wstring candidate; + for (std::vector<std::wstring>::const_iterator scan = candidates.begin(), + end = candidates.end(); scan != end; ++scan) { + candidate.assign(*scan); + StringToLowerASCII(&candidate); + if (select_predicate(candidate, matched_offset)) { + matched_name->assign(*scan); + return true; + } + } + + return false; +} + +// Select the best-fit translation from the ordered list |candidates|. +// At the conclusion, this instance's |matched_candidate_| and |offset_| members +// are set to the name of the selected candidate and the offset of the matched +// translation. If no translation is selected, the fallback's name and offset +// are selected. +void LanguageSelector::DoSelect(const std::vector<std::wstring>& candidates) { + // Make a pass through the candidates looking for an exact or alias match. + // Failing that, make another pass looking for a wildcard match. + if (!SelectIf(candidates, &GetLanguageOffset, &matched_candidate_, + &offset_) && + !SelectIf(candidates, &MatchLanguageOffset, &matched_candidate_, + &offset_)) { + VLOG(1) << "No suitable language found for any candidates."; + + // Our fallback is "en-us" + matched_candidate_.assign(&kFallbackLanguage[0], + arraysize(kFallbackLanguage) - 1); + offset_ = kFallbackLanguageOffset; + } +} + +} // namespace installer_util diff --git a/chrome/installer/util/language_selector.h b/chrome/installer/util/language_selector.h new file mode 100644 index 0000000..0085461c --- /dev/null +++ b/chrome/installer/util/language_selector.h @@ -0,0 +1,60 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file declares a helper class for selecting a supported language from a +// set of candidates. + +#ifndef CHROME_INSTALLER_UTIL_LANGUAGE_SELECTOR_H_ +#define CHROME_INSTALLER_UTIL_LANGUAGE_SELECTOR_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/basictypes.h" + +namespace installer_util { + +// A helper class for selecting a supported language from a set of candidates. +// By default, the candidates are retrieved from the operating system. +class LanguageSelector { + public: + // Default constructor will select from the set of languages supported by the + // operating system. + LanguageSelector(); + + // Constructor for testing purposes. + explicit LanguageSelector(const std::vector<std::wstring>& candidates); + + ~LanguageSelector(); + + // The offset of the matched language (i.e., IDS_L10N_OFFSET_*). + int offset() const { return offset_; } + + // The full name of the candidate language for which a match was found. + const std::wstring& matched_candidate() const { return matched_candidate_; } + + // The name of the selected translation. + std::wstring selected_translation() const { return GetLanguageName(offset_); } + + // Returns the name of a translation given its offset. + static std::wstring GetLanguageName(int offset); + + private: + typedef bool (*SelectPred_Fn)(const std::wstring&, int*); + + static bool SelectIf(const std::vector<std::wstring>& candidates, + SelectPred_Fn select_predicate, + std::wstring* matched_name, int* matched_offset); + void DoSelect(const std::vector<std::wstring>& candidates); + + std::wstring matched_candidate_; + int offset_; + + DISALLOW_COPY_AND_ASSIGN(LanguageSelector); +}; + +} // namespace installer_util. + +#endif // CHROME_INSTALLER_UTIL_LANGUAGE_SELECTOR_H_ diff --git a/chrome/installer/util/language_selector_unittest.cc b/chrome/installer/util/language_selector_unittest.cc new file mode 100644 index 0000000..fc6b72d7 --- /dev/null +++ b/chrome/installer/util/language_selector_unittest.cc @@ -0,0 +1,128 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> +#include <vector> + +#include "chrome/installer/util/language_selector.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const wchar_t* const kExactMatchCandidates[] = { +#if defined(GOOGLE_CHROME_BUILD) + L"am", L"ar", L"bg", L"bn", L"ca", L"cs", L"da", L"de", L"el", L"en-gb", + L"en-us", L"es", L"es-419", L"et", L"fa", L"fi", L"fil", L"fr", L"gu", L"hi", + L"hr", L"hu", L"id", L"it", L"iw", L"ja", L"kn", L"ko", L"lt", L"lv", L"ml", + L"mr", L"nl", L"no", L"pl", L"pt-br", L"pt-pt", L"ro", L"ru", L"sk", L"sl", + L"sr", L"sv", L"sw", L"ta", L"te", L"th", L"tr", L"uk", L"vi", L"zh-cn", + L"zh-tw" +#else + L"en-us" +#endif +}; + +const wchar_t* const kAliasMatchCandidates[] = { +#if defined(GOOGLE_CHROME_BUILD) + L"he", L"nb", L"tl", L"zh-chs", L"zh-cht", L"zh-hans", L"zh-hant", L"zh-hk", + L"zh-mk", L"zh-mo" +#else + // There is only en-us. + L"en-us" +#endif +}; + +const wchar_t* const kWildcardMatchCandidates[] = { + L"en-AU", +#if defined(GOOGLE_CHROME_BUILD) + L"es-CO", L"pt-AB", L"zh-SG" +#endif +}; + +} // namespace + +// Test that a language is selected from the system. +TEST(LanguageSelectorTest, DefaultSelection) { + installer_util::LanguageSelector instance; + EXPECT_FALSE(instance.matched_candidate().empty()); +} + +// Test some hypothetical candidate sets. +TEST(LanguageSelectorTest, AssortedSelections) { + { + std::wstring candidates[] = { + L"fr-BE", L"fr", L"en" + }; + installer_util::LanguageSelector instance( + std::vector<std::wstring>(&candidates[0], + &candidates[arraysize(candidates)])); +#if defined(GOOGLE_CHROME_BUILD) + // Expect the exact match to win. + EXPECT_EQ(L"fr", instance.matched_candidate()); +#else + // Expect the exact match to win. + EXPECT_EQ(L"en", instance.matched_candidate()); +#endif + } + { + std::wstring candidates[] = { + L"xx-YY", L"cc-Ssss-RR" + }; + installer_util::LanguageSelector instance( + std::vector<std::wstring>(&candidates[0], + &candidates[arraysize(candidates)])); + // Expect the fallback to win. + EXPECT_EQ(L"en-us", instance.matched_candidate()); + } + { + std::wstring candidates[] = { + L"zh-SG", L"en-GB" + }; + installer_util::LanguageSelector instance( + std::vector<std::wstring>(&candidates[0], + &candidates[arraysize(candidates)])); +#if defined(GOOGLE_CHROME_BUILD) + // Expect the alias match to win. + EXPECT_EQ(L"zh-SG", instance.matched_candidate()); +#else + // Expect the exact match to win. + EXPECT_EQ(L"en-GB", instance.matched_candidate()); +#endif + } +} + +// A fixture for testing sets of single-candidate selections. +class LanguageSelectorMatchCandidateTest + : public ::testing::TestWithParam<const wchar_t*> { +}; + +TEST_P(LanguageSelectorMatchCandidateTest, TestMatchCandidate) { + installer_util::LanguageSelector instance( + std::vector<std::wstring>(1, std::wstring(GetParam()))); + EXPECT_EQ(GetParam(), instance.matched_candidate()); +} + +// Test that all existing translations can be found by exact match. +INSTANTIATE_TEST_CASE_P( + TestExactMatches, + LanguageSelectorMatchCandidateTest, + ::testing::ValuesIn( + &kExactMatchCandidates[0], + &kExactMatchCandidates[arraysize(kExactMatchCandidates)])); + +// Test the alias matches. +INSTANTIATE_TEST_CASE_P( + TestAliasMatches, + LanguageSelectorMatchCandidateTest, + ::testing::ValuesIn( + &kAliasMatchCandidates[0], + &kAliasMatchCandidates[arraysize(kAliasMatchCandidates)])); + +// Test a few wildcard matches. +INSTANTIATE_TEST_CASE_P( + TestWildcardMatches, + LanguageSelectorMatchCandidateTest, + ::testing::ValuesIn( + &kWildcardMatchCandidates[0], + &kWildcardMatchCandidates[arraysize(kWildcardMatchCandidates)])); diff --git a/chrome/installer/util/prebuild/create_string_rc.py b/chrome/installer/util/prebuild/create_string_rc.py index c7eb9c1..4bb669a 100755 --- a/chrome/installer/util/prebuild/create_string_rc.py +++ b/chrome/installer/util/prebuild/create_string_rc.py @@ -175,6 +175,7 @@ def WriteHeaderFile(translated_strings, out_filename): """Writes a .h file with resource ids. This file can be included by the executable to refer to identifiers.""" lines = [] + do_languages_lines = ['#define DO_LANGUAGES'] # Write the values for how the languages ids are offset. seen_languages = set() @@ -183,7 +184,9 @@ def WriteHeaderFile(translated_strings, out_filename): lang = translation_struct.language if lang not in seen_languages: seen_languages.add(lang) - lines.append(u'#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id)) + lines.append('#define IDS_L10N_OFFSET_%s %s' % (lang, offset_id)) + do_languages_lines.append(' HANDLE_LANGUAGE(%s, IDS_L10N_OFFSET_%s)' + % (lang.replace('_', '-').lower(), lang)) offset_id += 1 else: break @@ -191,19 +194,22 @@ def WriteHeaderFile(translated_strings, out_filename): # Write the resource ids themselves. resource_id = kFirstResourceID for translation_struct in translated_strings: - lines.append(u'#define %s %s' % (translation_struct.resource_id_str, - resource_id)) + lines.append('#define %s %s' % (translation_struct.resource_id_str, + resource_id)) resource_id += 1 # Write out base ID values. for string_id in kStringIds: - lines.append(u'#define %s_BASE %s_%s' % (string_id, - string_id, - translated_strings[0].language)) + lines.append('#define %s_BASE %s_%s' % (string_id, + string_id, + translated_strings[0].language)) outfile = open(out_filename + '.h', 'wb') outfile.write('\n'.join(lines)) - outfile.write('\n') # .rc files must end in a new line + outfile.write('\n#ifndef RC_INVOKED\n') + outfile.write(' \\\n'.join(do_languages_lines)) + # .rc files must end in a new line + outfile.write('\n#endif // ndef RC_INVOKED\n') outfile.close() def main(argv): |