summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/l10n_util.cc20
-rw-r--r--app/l10n_util_win.cc41
-rw-r--r--app/l10n_util_win.h11
-rw-r--r--base/base.gyp1
-rw-r--r--base/base.gypi2
-rw-r--r--base/i18n/rtl.cc16
-rw-r--r--base/i18n/rtl.h7
-rw-r--r--base/win/i18n.cc169
-rw-r--r--base/win/i18n.h32
-rw-r--r--base/win/i18n_unittest.cc42
-rw-r--r--chrome/browser/browser_main_win.cc9
-rw-r--r--chrome/chrome_installer.gypi1
-rw-r--r--chrome/chrome_installer_util.gypi2
-rw-r--r--chrome/installer/util/l10n_string_util.cc226
-rw-r--r--chrome/installer/util/language_selector.cc301
-rw-r--r--chrome/installer/util/language_selector.h60
-rw-r--r--chrome/installer/util/language_selector_unittest.cc128
-rwxr-xr-xchrome/installer/util/prebuild/create_string_rc.py20
-rw-r--r--chrome_frame/simple_resource_loader.cc124
-rw-r--r--chrome_frame/simple_resource_loader.h10
-rw-r--r--chrome_frame/test/simple_resource_loader_test.cc23
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, &region)) {
+ // 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, &region);
- 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, &region);
- EXPECT_NE(static_cast<std::wstring::size_type>(0), language.size());
-}
-
TEST(SimpleResourceLoaderTest, InstanceTest) {
SimpleResourceLoader* loader = SimpleResourceLoader::instance();