diff options
-rw-r--r-- | app/l10n_util.cc | 20 | ||||
-rw-r--r-- | app/l10n_util_win.cc | 41 | ||||
-rw-r--r-- | app/l10n_util_win.h | 11 | ||||
-rw-r--r-- | base/base.gyp | 1 | ||||
-rw-r--r-- | base/base.gypi | 2 | ||||
-rw-r--r-- | base/i18n/rtl.cc | 16 | ||||
-rw-r--r-- | base/i18n/rtl.h | 7 | ||||
-rw-r--r-- | base/win/i18n.cc | 169 | ||||
-rw-r--r-- | base/win/i18n.h | 32 | ||||
-rw-r--r-- | base/win/i18n_unittest.cc | 42 | ||||
-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 | ||||
-rw-r--r-- | chrome_frame/simple_resource_loader.cc | 124 | ||||
-rw-r--r-- | chrome_frame/simple_resource_loader.h | 10 | ||||
-rw-r--r-- | chrome_frame/test/simple_resource_loader_test.cc | 23 |
21 files changed, 879 insertions, 366 deletions
diff --git a/app/l10n_util.cc b/app/l10n_util.cc index 375f3b6..d86f082 100644 --- a/app/l10n_util.cc +++ b/app/l10n_util.cc @@ -8,7 +8,9 @@ #include <glib/gutils.h> #endif +#include <algorithm> #include <cstdlib> +#include <iterator> #include "app/app_paths.h" #include "app/l10n_util_collator.h" @@ -30,6 +32,8 @@ #if defined(OS_MACOSX) #include "app/l10n_util_mac.h" +#elif defined(OS_WIN) +#include "app/l10n_util_win.h" #endif namespace { @@ -325,6 +329,10 @@ void AdjustParagraphDirectionality(string16* paragraph) { #endif } +std::string GetCanonicalLocale(const std::string& locale) { + return base::i18n::GetCanonicalLocale(locale.c_str()); +} + } // namespace namespace l10n_util { @@ -369,8 +377,16 @@ std::string GetApplicationLocale(const std::string& pref_locale) { if (!pref_locale.empty()) candidates.push_back(pref_locale); - // Next, try the system locale. - candidates.push_back(base::i18n::GetConfiguredLocale()); + // Next, try the overridden locale. + const std::vector<std::string>& languages = l10n_util::GetLocaleOverrides(); + if (!languages.empty()) { + candidates.reserve(candidates.size() + languages.size()); + std::transform(languages.begin(), languages.end(), + std::back_inserter(candidates), &GetCanonicalLocale); + } else { + // If no override was set, defer to ICU + candidates.push_back(base::i18n::GetConfiguredLocale()); + } #elif defined(OS_CHROMEOS) diff --git a/app/l10n_util_win.cc b/app/l10n_util_win.cc index 2f1f627..eacdb35 100644 --- a/app/l10n_util_win.cc +++ b/app/l10n_util_win.cc @@ -2,14 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "app/l10n_util.h" +#include "app/l10n_util_win.h" -#include <algorithm> #include <windowsx.h> +#include <algorithm> +#include <iterator> -#include "app/l10n_util_win.h" +#include "app/l10n_util.h" #include "base/i18n/rtl.h" +#include "base/lazy_instance.h" #include "base/string_number_conversions.h" +#include "base/win/i18n.h" #include "base/win/windows_version.h" #include "grit/app_locale_settings.h" @@ -52,6 +55,21 @@ bool IsFontPresent(const wchar_t* font_name) { return wcscmp(font_name, actual_font_name) == 0; } +class OverrideLocaleHolder { + public: + OverrideLocaleHolder() {} + const std::vector<std::string>& value() const { return value_; } + void swap_value(std::vector<std::string>* override_value) { + value_.swap(*override_value); + } + private: + std::vector<std::string> value_; + DISALLOW_COPY_AND_ASSIGN(OverrideLocaleHolder); +}; + +base::LazyInstance<OverrideLocaleHolder> + override_locale_holder(base::LINKER_INITIALIZED); + } // namespace namespace l10n_util { @@ -147,4 +165,21 @@ void AdjustUIFontForWindow(HWND hwnd) { } } +void OverrideLocaleWithUILanguageList() { + std::vector<std::wstring> ui_languages; + if (base::win::i18n::GetThreadPreferredUILanguageList(&ui_languages)) { + std::vector<std::string> ascii_languages; + ascii_languages.reserve(ui_languages.size()); + std::transform(ui_languages.begin(), ui_languages.end(), + std::back_inserter(ascii_languages), &WideToASCII); + override_locale_holder.Get().swap_value(&ascii_languages); + } else { + NOTREACHED() << "Failed to determine the UI language for locale override."; + } +} + +const std::vector<std::string>& GetLocaleOverrides() { + return override_locale_holder.Get().value(); +} + } // namespace l10n_util diff --git a/app/l10n_util_win.h b/app/l10n_util_win.h index 99a9df3..7a1dda5 100644 --- a/app/l10n_util_win.h +++ b/app/l10n_util_win.h @@ -7,6 +7,8 @@ #pragma once #include <windows.h> +#include <string> +#include <vector> namespace l10n_util { @@ -47,6 +49,15 @@ void AdjustUIFont(LOGFONT* logfont); // stored in the per-locale resource. void AdjustUIFontForWindow(HWND hwnd); +// Allow processes to override the configured locale with the user's Windows UI +// languages. This function should generally be called once early in +// Application startup. +void OverrideLocaleWithUILanguageList(); + +// Retrieve the locale override, or an empty vector if the locale has not been +// or failed to be overridden. +const std::vector<std::string>& GetLocaleOverrides(); + } // namespace l10n_util #endif // APP_L10N_UTIL_WIN_H_ diff --git a/base/base.gyp b/base/base.gyp index 6a03b67..0c3918f 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -164,6 +164,7 @@ 'win/event_trace_consumer_unittest.cc', 'win/event_trace_controller_unittest.cc', 'win/event_trace_provider_unittest.cc', + 'win/i18n_unittest.cc', 'win/pe_image_unittest.cc', 'win/registry_unittest.cc', 'win/scoped_bstr_unittest.cc', diff --git a/base/base.gypi b/base/base.gypi index a395299..a90a451 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -294,6 +294,8 @@ 'watchdog.h', 'weak_ptr.cc', 'weak_ptr.h', + 'win/i18n.cc', + 'win/i18n.h', 'win/pe_image.cc', 'win/event_trace_consumer.h', 'win/event_trace_controller.cc', diff --git a/base/i18n/rtl.cc b/base/i18n/rtl.cc index 9ff12d8..0881fb7 100644 --- a/base/i18n/rtl.cc +++ b/base/i18n/rtl.cc @@ -44,22 +44,6 @@ namespace i18n { // Represents the locale-specific ICU text direction. static TextDirection g_icu_text_direction = UNKNOWN_DIRECTION; -#if defined(OS_WIN) -void GetLanguageAndRegionFromOS(std::string* lang, std::string* region) { - // Later we may have to change this to be OS-dependent so that - // it's not affected by ICU's default locale. It's all right - // to do this way because SetICUDefaultLocale is internal - // to this file and we know that it's not yet called when this function - // is called. - const icu::Locale& locale = icu::Locale::getDefault(); - const char* language = locale.getLanguage(); - const char* country = locale.getCountry(); - DCHECK(language); - *lang = language; - *region = country; -} -#endif - // Convert the ICU default locale to a string. std::string GetConfiguredLocale() { return GetLocaleString(icu::Locale::getDefault()); diff --git a/base/i18n/rtl.h b/base/i18n/rtl.h index 52b1a2b..ed0882f 100644 --- a/base/i18n/rtl.h +++ b/base/i18n/rtl.h @@ -6,6 +6,8 @@ #define BASE_I18N_RTL_H_ #pragma once +#include <string> + #include "base/compiler_specific.h" #include "base/string16.h" #include "build/build_config.h" @@ -29,11 +31,6 @@ enum TextDirection { LEFT_TO_RIGHT, }; -#if defined(OS_WIN) -// Get language and region from the OS. Used by Chrome Frame. -void GetLanguageAndRegionFromOS(std::string* lang, std::string* region); -#endif - // Get the locale that the currently running process has been configured to use. // The return value is of the form language[-country] (e.g., en-US) where the // language is the 2 or 3 letter code from ISO-639. diff --git a/base/win/i18n.cc b/base/win/i18n.cc new file mode 100644 index 0000000..59480f2 --- /dev/null +++ b/base/win/i18n.cc @@ -0,0 +1,169 @@ +// 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 "base/win/i18n.h" + +#include <windows.h> + +#include "base/logging.h" + +namespace { + +// Keep this enum in sync with kLanguageFunctionNames. +enum LanguageFunction { + SYSTEM_LANGUAGES, + USER_LANGUAGES, + PROCESS_LANGUAGES, + THREAD_LANGUAGES, + NUM_FUNCTIONS +}; + +const char kSystemLanguagesFunctionName[] = "GetSystemPreferredUILanguages"; +const char kUserLanguagesFunctionName[] = "GetUserPreferredUILanguages"; +const char kProcessLanguagesFunctionName[] = "GetProcessPreferredUILanguages"; +const char kThreadLanguagesFunctionName[] = "GetThreadPreferredUILanguages"; + +// Keep this array in sync with enum LanguageFunction. +const char *const kLanguageFunctionNames[] = { + &kSystemLanguagesFunctionName[0], + &kUserLanguagesFunctionName[0], + &kProcessLanguagesFunctionName[0], + &kThreadLanguagesFunctionName[0] +}; + +COMPILE_ASSERT(NUM_FUNCTIONS == arraysize(kLanguageFunctionNames), + language_function_enum_and_names_out_of_sync); + +// Calls one of the MUI Get*PreferredUILanguages functions, placing the result +// in |languages|. |function| identifies the function to call and |flags| is +// the function-specific flags (callers must not specify MUI_LANGUAGE_ID or +// MUI_LANGUAGE_NAME). Returns true if at least one language is placed in +// |languages|. +bool GetMUIPreferredUILanguageList(LanguageFunction function, ULONG flags, + std::vector<wchar_t>* languages) { + DCHECK(0 <= function && NUM_FUNCTIONS > function); + DCHECK_EQ(0U, (flags & (MUI_LANGUAGE_ID | MUI_LANGUAGE_NAME))); + DCHECK(languages); + + HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); + if (NULL != kernel32) { + typedef BOOL (WINAPI* GetPreferredUILanguages_Fn)( + DWORD, PULONG, PZZWSTR, PULONG); + GetPreferredUILanguages_Fn get_preferred_ui_languages = + reinterpret_cast<GetPreferredUILanguages_Fn>( + GetProcAddress(kernel32, kLanguageFunctionNames[function])); + if (NULL != get_preferred_ui_languages) { + const ULONG call_flags = flags | MUI_LANGUAGE_NAME; + ULONG language_count = 0; + ULONG buffer_length = 0; + if (get_preferred_ui_languages(call_flags, &language_count, NULL, + &buffer_length) && + 0 != buffer_length) { + languages->resize(buffer_length); + if (get_preferred_ui_languages(call_flags, &language_count, + &(*languages)[0], &buffer_length) && + 0 != language_count) { + DCHECK(languages->size() == buffer_length); + return true; + } else { + DPCHECK(0 == language_count) + << "Failed getting preferred UI languages."; + } + } else { + DPCHECK(0 == buffer_length) + << "Failed getting size of preferred UI languages."; + } + } else { + VLOG(2) << "MUI not available."; + } + } else { + NOTREACHED() << "kernel32.dll not found."; + } + + return false; +} + +bool GetUserDefaultUILanguage(std::wstring* language, std::wstring* region) { + DCHECK(language); + + LANGID lang_id = ::GetUserDefaultUILanguage(); + if (LOCALE_CUSTOM_UI_DEFAULT != lang_id) { + const LCID locale_id = MAKELCID(lang_id, SORT_DEFAULT); + // max size for LOCALE_SISO639LANGNAME and LOCALE_SISO3166CTRYNAME is 9 + wchar_t result_buffer[9]; + int result_length = + GetLocaleInfo(locale_id, LOCALE_SISO639LANGNAME, &result_buffer[0], + arraysize(result_buffer)); + DPCHECK(0 != result_length) << "Failed getting language id"; + if (1 < result_length) { + language->assign(&result_buffer[0], result_length - 1); + region->clear(); + if (SUBLANG_NEUTRAL != SUBLANGID(lang_id)) { + result_length = + GetLocaleInfo(locale_id, LOCALE_SISO3166CTRYNAME, &result_buffer[0], + arraysize(result_buffer)); + DPCHECK(0 != result_length) << "Failed getting region id"; + if (1 < result_length) + region->assign(&result_buffer[0], result_length - 1); + } + return true; + } + } else { + // This is entirely unexpected on pre-Vista, which is the only time we + // should try GetUserDefaultUILanguage anyway. + NOTREACHED() << "Cannot determine language for a supplemental locale."; + } + return false; +} + +bool GetPreferredUILanguageList(LanguageFunction function, ULONG flags, + std::vector<std::wstring>* languages) { + std::vector<wchar_t> buffer; + std::wstring language; + std::wstring region; + + if (GetMUIPreferredUILanguageList(function, flags, &buffer)) { + std::vector<wchar_t>::const_iterator scan = buffer.begin(); + language.assign(&*scan); + while (!language.empty()) { + languages->push_back(language); + scan += language.size() + 1; + language.assign(&*scan); + } + } else if (GetUserDefaultUILanguage(&language, ®ion)) { + // Mimic the MUI behavior of putting the neutral version of the lang after + // the regional one (e.g., "fr-CA, fr"). + if (!region.empty()) + languages->push_back(std::wstring(language) + .append(1, L'-') + .append(region)); + languages->push_back(language); + } else { + return false; + } + + return true; +} + +} // namespace + +namespace base { +namespace win { +namespace i18n { + +bool GetUserPreferredUILanguageList(std::vector<std::wstring>* languages) { + DCHECK(languages); + return GetPreferredUILanguageList(USER_LANGUAGES, 0, languages); +} + +bool GetThreadPreferredUILanguageList(std::vector<std::wstring>* languages) { + DCHECK(languages); + return GetPreferredUILanguageList( + THREAD_LANGUAGES, MUI_MERGE_SYSTEM_FALLBACK | MUI_MERGE_USER_FALLBACK, + languages); +} + +} // namespace i18n +} // namespace win +} // namespace base diff --git a/base/win/i18n.h b/base/win/i18n.h new file mode 100644 index 0000000..ba0f74d --- /dev/null +++ b/base/win/i18n.h @@ -0,0 +1,32 @@ +// 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. + +#ifndef BASE_WIN_I18N_H_ +#define BASE_WIN_I18N_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/basictypes.h" + +namespace base { +namespace win { +namespace i18n { + +// Adds to |languages| the list of user preferred UI languages from MUI, if +// available, falling-back on the user default UI language otherwise. Returns +// true if at least one language is added. +bool GetUserPreferredUILanguageList(std::vector<std::wstring>* languages); + +// Adds to |languages| the list of thread, process, user, and system preferred +// UI languages from MUI, if available, falling-back on the user default UI +// language otherwise. Returns true if at least one language is added. +bool GetThreadPreferredUILanguageList(std::vector<std::wstring>* languages); + +} // namespace i18n +} // namespace win +} // namespace base + +#endif // BASE_WIN_I18N_H_ diff --git a/base/win/i18n_unittest.cc b/base/win/i18n_unittest.cc new file mode 100644 index 0000000..781fc39 --- /dev/null +++ b/base/win/i18n_unittest.cc @@ -0,0 +1,42 @@ +// 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 contains unit tests for Windows internationalization funcs. + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/win/i18n.h" +#include "base/win/windows_version.h" + +namespace base { +namespace win { +namespace i18n { + +// Tests that at least one user preferred UI language can be obtained. +TEST(I18NTest, GetUserPreferredUILanguageList) { + std::vector<std::wstring> languages; + EXPECT_TRUE(GetUserPreferredUILanguageList(&languages)); + EXPECT_NE(static_cast<std::vector<std::wstring>::size_type>(0), + languages.size()); + for (std::vector<std::wstring>::const_iterator scan = languages.begin(), + end = languages.end(); scan != end; ++scan) { + EXPECT_FALSE((*scan).empty()); + } +} + +// Tests that at least one thread preferred UI language can be obtained. +TEST(I18NTest, GetThreadPreferredUILanguageList) { + std::vector<std::wstring> languages; + EXPECT_TRUE(GetThreadPreferredUILanguageList(&languages)); + EXPECT_NE(static_cast<std::vector<std::wstring>::size_type>(0), + languages.size()); + for (std::vector<std::wstring>::const_iterator scan = languages.begin(), + end = languages.end(); scan != end; ++scan) { + EXPECT_FALSE((*scan).empty()); + } +} + +} // namespace i18n +} // namespace win +} // namespace base 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): diff --git a/chrome_frame/simple_resource_loader.cc b/chrome_frame/simple_resource_loader.cc index 844afa7..89a51d7 100644 --- a/chrome_frame/simple_resource_loader.cc +++ b/chrome_frame/simple_resource_loader.cc @@ -4,19 +4,19 @@ #include "chrome_frame/simple_resource_loader.h" -#include <algorithm> - #include <atlbase.h> +#include <algorithm> + #include "base/base_paths.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/path_service.h" -#include "base/i18n/file_util_icu.h" #include "base/i18n/rtl.h" #include "base/singleton.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" +#include "base/win/i18n.h" #include "base/win/windows_version.h" #include "chrome_frame/policy_settings.h" @@ -61,6 +61,25 @@ bool PushBackIfAbsent( return false; } +// Returns true if the collection is modified. +bool PushBackWithFallbackIfAbsent( + const std::wstring& language, + std::vector<std::wstring>* collection) { + bool modified = false; + + if (!language.empty()) { + // Try adding the language itself. + modified = PushBackIfAbsent(language, collection); + + // Now try adding its fallback, if it has one. + std::wstring::size_type dash_pos = language.find(L'-'); + if (0 < dash_pos && language.size() - 1 > dash_pos) + modified |= PushBackIfAbsent(language.substr(0, dash_pos), collection); + } + + return modified; +} + } // namespace SimpleResourceLoader::SimpleResourceLoader() @@ -69,19 +88,8 @@ SimpleResourceLoader::SimpleResourceLoader() std::vector<std::wstring> language_tags; // First, try the locale dictated by policy and its fallback. - std::wstring application_locale = - Singleton<PolicySettings>()->ApplicationLocale(); - if (!application_locale.empty()) { - language_tags.push_back(application_locale); - std::wstring::size_type dash = application_locale.find(L'-'); - if (std::wstring::npos != dash) { - if (0 != dash) { - language_tags.push_back(application_locale.substr(0, dash)); - } else { - NOTREACHED() << "Group Policy application locale begins with a dash."; - } - } - } + PushBackWithFallbackIfAbsent(Singleton<PolicySettings>()->ApplicationLocale(), + &language_tags); // Next, try the thread, process, user, system languages. GetPreferredLanguages(&language_tags); @@ -109,84 +117,20 @@ SimpleResourceLoader::~SimpleResourceLoader() { // static void SimpleResourceLoader::GetPreferredLanguages( std::vector<std::wstring>* language_tags) { + DCHECK(language_tags); // The full set of preferred languages and their fallbacks are given priority. - GetThreadPreferredUILanguages(language_tags); - - // The above gives us nothing pre-Vista, so use ICU to get the system - // language and add it and its fallback to the end of the list if not present. - std::wstring language; - std::wstring region; - - GetICUSystemLanguage(&language, ®ion); - if (!region.empty()) { - std::wstring combined; - combined.reserve(language.size() + 1 + region.size()); - combined.assign(language).append(L"-").append(region); - PushBackIfAbsent(combined, language_tags); - } - PushBackIfAbsent(language, language_tags); -} - -// static -bool SimpleResourceLoader::GetThreadPreferredUILanguages( - std::vector<std::wstring>* language_tags) { - typedef BOOL (WINAPI* GetThreadPreferredUILanguages_Fn)( - DWORD, PULONG, PZZWSTR, PULONG); - HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); - DCHECK(kernel32) << "Failed finding kernel32.dll!"; - GetThreadPreferredUILanguages_Fn get_thread_preferred_ui_languages = - reinterpret_cast<GetThreadPreferredUILanguages_Fn>( - GetProcAddress(kernel32, "GetThreadPreferredUILanguages")); - bool have_mui = (NULL != get_thread_preferred_ui_languages); - if (have_mui) { - const DWORD kNameAndFallbackFlags = - MUI_LANGUAGE_NAME | MUI_MERGE_SYSTEM_FALLBACK | MUI_MERGE_USER_FALLBACK; - ULONG language_count = 0; - ULONG buffer_length = 0; - - if (get_thread_preferred_ui_languages( - kNameAndFallbackFlags, - &language_count, - NULL, - &buffer_length) && 0 != buffer_length) { - std::vector<wchar_t> language_names(buffer_length); - - if (get_thread_preferred_ui_languages( - kNameAndFallbackFlags, - &language_count, - &language_names[0], - &buffer_length)) { - std::vector<wchar_t>::const_iterator scan = language_names.begin(); - std::wstring language(&*scan); - while (!language.empty()) { - language_tags->push_back(language); - scan += language.size() + 1; - language.assign(&*scan); - } - } + std::vector<std::wstring> languages; + if (base::win::i18n::GetThreadPreferredUILanguageList(&languages)) { + for (std::vector<std::wstring>::const_iterator scan = languages.begin(), + end = languages.end(); scan != end; ++scan) { + PushBackIfAbsent(*scan, language_tags); } } - return have_mui; -} -// static -void SimpleResourceLoader::GetICUSystemLanguage(std::wstring* language, - std::wstring* region) { - DCHECK(language); - DCHECK(region); - - std::string icu_language, icu_region; - base::i18n::GetLanguageAndRegionFromOS(&icu_language, &icu_region); - if (!icu_language.empty()) { - *language = ASCIIToWide(icu_language); - } else { - language->clear(); - } - if (!icu_region.empty()) { - *region = ASCIIToWide(icu_region); - } else { - region->clear(); - } + // Use the base i18n routines (i.e., ICU) as a last, best hope for something + // meaningful for the user. + PushBackWithFallbackIfAbsent(ASCIIToWide(base::i18n::GetConfiguredLocale()), + language_tags); } // static diff --git a/chrome_frame/simple_resource_loader.h b/chrome_frame/simple_resource_loader.h index 9f10934..c749052 100644 --- a/chrome_frame/simple_resource_loader.h +++ b/chrome_frame/simple_resource_loader.h @@ -40,16 +40,6 @@ class SimpleResourceLoader { // |language_tags|. static void GetPreferredLanguages(std::vector<std::wstring>* language_tags); - // Retrieves the thread/process/user/system preferred languages on Vista+, - // adding them and their fallbacks to |language_tags|. Returns |false| if the - // platform does not support such (i.e., XP). - static bool GetThreadPreferredUILanguages( - std::vector<std::wstring>* language_tags); - - // Retrieves the system language and the region using ICU (used on XP). - static void GetICUSystemLanguage(std::wstring* language, - std::wstring* region); - // Populates |locales_path| with the path to the "Locales" directory. static void DetermineLocalesDirectory(FilePath* locales_path); diff --git a/chrome_frame/test/simple_resource_loader_test.cc b/chrome_frame/test/simple_resource_loader_test.cc index e120a6b..9b67a6e 100644 --- a/chrome_frame/test/simple_resource_loader_test.cc +++ b/chrome_frame/test/simple_resource_loader_test.cc @@ -56,29 +56,6 @@ TEST(SimpleResourceLoaderTest, LoadLocaleDll) { EXPECT_TRUE(file_path.BaseName() == FilePath(L"en-US.dll")); } -TEST(SimpleResourceLoaderTest, GetThreadPreferredUILanguages) { - std::vector<std::wstring> language_tags; - - if (base::win::GetVersion() >= base::win::VERSION_VISTA) { - EXPECT_TRUE( - SimpleResourceLoader::GetThreadPreferredUILanguages(&language_tags)); - // Did we find at least one language? - EXPECT_NE(static_cast<std::vector<std::wstring>::size_type>(0), - language_tags.size()); - } else { - EXPECT_FALSE( - SimpleResourceLoader::GetThreadPreferredUILanguages(&language_tags)); - } -} - -TEST(SimpleResourceLoaderTest, GetICUSystemLanguage) { - std::wstring language; - std::wstring region; - - SimpleResourceLoader::GetICUSystemLanguage(&language, ®ion); - EXPECT_NE(static_cast<std::wstring::size_type>(0), language.size()); -} - TEST(SimpleResourceLoaderTest, InstanceTest) { SimpleResourceLoader* loader = SimpleResourceLoader::instance(); |