1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "extensions/common/extension_l10n_util.h"
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_util.h"
14 #include "base/json/json_file_value_serializer.h"
15 #include "base/logging.h"
16 #include "base/memory/linked_ptr.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/values.h"
20 #include "extensions/common/constants.h"
21 #include "extensions/common/error_utils.h"
22 #include "extensions/common/file_util.h"
23 #include "extensions/common/manifest_constants.h"
24 #include "extensions/common/message_bundle.h"
25 #include "third_party/icu/source/common/unicode/uloc.h"
26 #include "ui/base/l10n/l10n_util.h"
28 namespace errors
= extensions::manifest_errors
;
29 namespace keys
= extensions::manifest_keys
;
33 // Loads contents of the messages file for given locale. If file is not found,
34 // or there was parsing error we return NULL and set |error|.
35 // Caller owns the returned object.
36 base::DictionaryValue
* LoadMessageFile(const base::FilePath
& locale_path
,
37 const std::string
& locale
,
40 locale_path
.AppendASCII(locale
).Append(extensions::kMessagesFilename
);
41 JSONFileValueDeserializer
messages_deserializer(file
);
42 base::Value
* dictionary
= messages_deserializer
.Deserialize(NULL
, error
);
45 // JSONFileValueSerializer just returns NULL if file cannot be found. It
46 // doesn't set the error, so we have to do it.
47 *error
= base::StringPrintf("Catalog file is missing for locale %s.",
50 *error
= extensions::ErrorUtils::FormatErrorMessage(
51 errors::kLocalesInvalidLocale
,
52 base::UTF16ToUTF8(file
.LossyDisplayName()),
57 return static_cast<base::DictionaryValue
*>(dictionary
);
60 // Localizes manifest value of string type for a given key.
61 bool LocalizeManifestValue(const std::string
& key
,
62 const extensions::MessageBundle
& messages
,
63 base::DictionaryValue
* manifest
,
66 if (!manifest
->GetString(key
, &result
))
69 if (!messages
.ReplaceMessages(&result
, error
))
72 manifest
->SetString(key
, result
);
76 // Localizes manifest value of list type for a given key.
77 bool LocalizeManifestListValue(const std::string
& key
,
78 const extensions::MessageBundle
& messages
,
79 base::DictionaryValue
* manifest
,
81 base::ListValue
* list
= NULL
;
82 if (!manifest
->GetList(key
, &list
))
86 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
88 if (list
->GetString(i
, &result
)) {
89 if (messages
.ReplaceMessages(&result
, error
))
90 list
->Set(i
, new base::StringValue(result
));
98 std::string
& GetProcessLocale() {
99 CR_DEFINE_STATIC_LOCAL(std::string
, locale
, ());
105 namespace extension_l10n_util
{
107 void SetProcessLocale(const std::string
& locale
) {
108 GetProcessLocale() = locale
;
111 std::string
GetDefaultLocaleFromManifest(const base::DictionaryValue
& manifest
,
112 std::string
* error
) {
113 std::string default_locale
;
114 if (manifest
.GetString(keys::kDefaultLocale
, &default_locale
))
115 return default_locale
;
117 *error
= errors::kInvalidDefaultLocale
;
118 return std::string();
121 bool ShouldRelocalizeManifest(const base::DictionaryValue
* manifest
) {
125 if (!manifest
->HasKey(keys::kDefaultLocale
))
128 std::string manifest_current_locale
;
129 manifest
->GetString(keys::kCurrentLocale
, &manifest_current_locale
);
130 return manifest_current_locale
!= CurrentLocaleOrDefault();
133 bool LocalizeManifest(const extensions::MessageBundle
& messages
,
134 base::DictionaryValue
* manifest
,
135 std::string
* error
) {
138 if (!manifest
->GetString(keys::kName
, &result
)) {
139 *error
= errors::kInvalidName
;
142 if (!LocalizeManifestValue(keys::kName
, messages
, manifest
, error
)) {
146 // Initialize short name.
147 if (!LocalizeManifestValue(keys::kShortName
, messages
, manifest
, error
))
150 // Initialize description.
151 if (!LocalizeManifestValue(keys::kDescription
, messages
, manifest
, error
))
154 // Initialize browser_action.default_title
155 std::string
key(keys::kBrowserAction
);
157 key
.append(keys::kPageActionDefaultTitle
);
158 if (!LocalizeManifestValue(key
, messages
, manifest
, error
))
161 // Initialize page_action.default_title
162 key
.assign(keys::kPageAction
);
164 key
.append(keys::kPageActionDefaultTitle
);
165 if (!LocalizeManifestValue(key
, messages
, manifest
, error
))
168 // Initialize omnibox.keyword.
169 if (!LocalizeManifestValue(keys::kOmniboxKeyword
, messages
, manifest
, error
))
172 base::ListValue
* file_handlers
= NULL
;
173 if (manifest
->GetList(keys::kFileBrowserHandlers
, &file_handlers
)) {
174 key
.assign(keys::kFileBrowserHandlers
);
175 for (size_t i
= 0; i
< file_handlers
->GetSize(); i
++) {
176 base::DictionaryValue
* handler
= NULL
;
177 if (!file_handlers
->GetDictionary(i
, &handler
)) {
178 *error
= errors::kInvalidFileBrowserHandler
;
181 if (!LocalizeManifestValue(
182 keys::kPageActionDefaultTitle
, messages
, handler
, error
))
187 // Initialize all input_components
188 base::ListValue
* input_components
= NULL
;
189 if (manifest
->GetList(keys::kInputComponents
, &input_components
)) {
190 for (size_t i
= 0; i
< input_components
->GetSize(); ++i
) {
191 base::DictionaryValue
* module
= NULL
;
192 if (!input_components
->GetDictionary(i
, &module
)) {
193 *error
= errors::kInvalidInputComponents
;
196 if (!LocalizeManifestValue(keys::kName
, messages
, module
, error
))
198 if (!LocalizeManifestValue(keys::kDescription
, messages
, module
, error
))
203 // Initialize app.launch.local_path.
204 if (!LocalizeManifestValue(keys::kLaunchLocalPath
, messages
, manifest
, error
))
207 // Initialize app.launch.web_url.
208 if (!LocalizeManifestValue(keys::kLaunchWebURL
, messages
, manifest
, error
))
211 // Initialize description of commmands.
212 base::DictionaryValue
* commands_handler
= NULL
;
213 if (manifest
->GetDictionary(keys::kCommands
, &commands_handler
)) {
214 for (base::DictionaryValue::Iterator
iter(*commands_handler
);
218 base::StringPrintf("commands.%s.description", iter
.key().c_str()));
219 if (!LocalizeManifestValue(key
, messages
, manifest
, error
))
224 // Initialize search_provider fields.
225 base::DictionaryValue
* search_provider
= NULL
;
226 if (manifest
->GetDictionary(keys::kOverrideSearchProvider
,
228 for (base::DictionaryValue::Iterator
iter(*search_provider
);
231 key
.assign(base::StringPrintf(
232 "%s.%s", keys::kOverrideSearchProvider
, iter
.key().c_str()));
234 (key
== keys::kSettingsOverrideAlternateUrls
)
235 ? LocalizeManifestListValue(key
, messages
, manifest
, error
)
236 : LocalizeManifestValue(key
, messages
, manifest
, error
);
242 // Initialize chrome_settings_overrides.homepage.
243 if (!LocalizeManifestValue(
244 keys::kOverrideHomepage
, messages
, manifest
, error
))
247 // Initialize chrome_settings_overrides.startup_pages.
248 if (!LocalizeManifestListValue(
249 keys::kOverrideStartupPage
, messages
, manifest
, error
))
252 // Add current locale key to the manifest, so we can overwrite prefs
253 // with new manifest when chrome locale changes.
254 manifest
->SetString(keys::kCurrentLocale
, CurrentLocaleOrDefault());
258 bool LocalizeExtension(const base::FilePath
& extension_path
,
259 base::DictionaryValue
* manifest
,
260 std::string
* error
) {
263 std::string default_locale
= GetDefaultLocaleFromManifest(*manifest
, error
);
265 scoped_ptr
<extensions::MessageBundle
> message_bundle(
266 extensions::file_util::LoadMessageBundle(
267 extension_path
, default_locale
, error
));
269 if (!message_bundle
.get() && !error
->empty())
272 if (message_bundle
.get() &&
273 !LocalizeManifest(*message_bundle
, manifest
, error
))
279 bool AddLocale(const std::set
<std::string
>& chrome_locales
,
280 const base::FilePath
& locale_folder
,
281 const std::string
& locale_name
,
282 std::set
<std::string
>* valid_locales
,
283 std::string
* error
) {
284 // Accept name that starts with a . but don't add it to the list of supported
286 if (locale_name
.find(".") == 0)
288 if (chrome_locales
.find(locale_name
) == chrome_locales
.end()) {
289 // Warn if there is an extension locale that's not in the Chrome list,
291 DLOG(WARNING
) << base::StringPrintf("Supplied locale %s is not supported.",
292 locale_name
.c_str());
295 // Check if messages file is actually present (but don't check content).
296 if (base::PathExists(locale_folder
.Append(extensions::kMessagesFilename
))) {
297 valid_locales
->insert(locale_name
);
299 *error
= base::StringPrintf("Catalog file is missing for locale %s.",
300 locale_name
.c_str());
307 std::string
CurrentLocaleOrDefault() {
308 std::string current_locale
= l10n_util::NormalizeLocale(GetProcessLocale());
309 if (current_locale
.empty())
310 current_locale
= "en";
312 return current_locale
;
315 void GetAllLocales(std::set
<std::string
>* all_locales
) {
316 const std::vector
<std::string
>& available_locales
=
317 l10n_util::GetAvailableLocales();
318 // Add all parents of the current locale to the available locales set.
319 // I.e. for sr_Cyrl_RS we add sr_Cyrl_RS, sr_Cyrl and sr.
320 for (size_t i
= 0; i
< available_locales
.size(); ++i
) {
321 std::vector
<std::string
> result
;
322 l10n_util::GetParentLocales(available_locales
[i
], &result
);
323 all_locales
->insert(result
.begin(), result
.end());
327 void GetAllFallbackLocales(const std::string
& application_locale
,
328 const std::string
& default_locale
,
329 std::vector
<std::string
>* all_fallback_locales
) {
330 DCHECK(all_fallback_locales
);
331 if (!application_locale
.empty() && application_locale
!= default_locale
)
332 l10n_util::GetParentLocales(application_locale
, all_fallback_locales
);
333 all_fallback_locales
->push_back(default_locale
);
336 bool GetValidLocales(const base::FilePath
& locale_path
,
337 std::set
<std::string
>* valid_locales
,
338 std::string
* error
) {
339 std::set
<std::string
> chrome_locales
;
340 GetAllLocales(&chrome_locales
);
342 // Enumerate all supplied locales in the extension.
343 base::FileEnumerator
locales(
344 locale_path
, false, base::FileEnumerator::DIRECTORIES
);
345 base::FilePath locale_folder
;
346 while (!(locale_folder
= locales
.Next()).empty()) {
347 std::string locale_name
= locale_folder
.BaseName().MaybeAsASCII();
348 if (locale_name
.empty()) {
350 continue; // Not ASCII.
353 chrome_locales
, locale_folder
, locale_name
, valid_locales
, error
)) {
354 valid_locales
->clear();
359 if (valid_locales
->empty()) {
360 *error
= errors::kLocalesNoValidLocaleNamesListed
;
367 extensions::MessageBundle
* LoadMessageCatalogs(
368 const base::FilePath
& locale_path
,
369 const std::string
& default_locale
,
370 const std::string
& application_locale
,
371 std::string
* error
) {
372 std::vector
<std::string
> all_fallback_locales
;
373 GetAllFallbackLocales(
374 application_locale
, default_locale
, &all_fallback_locales
);
376 std::vector
<linked_ptr
<base::DictionaryValue
> > catalogs
;
377 for (size_t i
= 0; i
< all_fallback_locales
.size(); ++i
) {
378 // Skip all parent locales that are not supplied.
379 base::FilePath this_locale_path
=
380 locale_path
.AppendASCII(all_fallback_locales
[i
]);
381 if (!base::PathExists(this_locale_path
))
383 linked_ptr
<base::DictionaryValue
> catalog(
384 LoadMessageFile(locale_path
, all_fallback_locales
[i
], error
));
385 if (!catalog
.get()) {
386 // If locale is valid, but messages.json is corrupted or missing, return
390 catalogs
.push_back(catalog
);
394 return extensions::MessageBundle::Create(catalogs
, error
);
397 bool ValidateExtensionLocales(const base::FilePath
& extension_path
,
398 const base::DictionaryValue
* manifest
,
399 std::string
* error
) {
400 std::string default_locale
= GetDefaultLocaleFromManifest(*manifest
, error
);
402 if (default_locale
.empty())
405 base::FilePath locale_path
= extension_path
.Append(extensions::kLocaleFolder
);
407 std::set
<std::string
> valid_locales
;
408 if (!GetValidLocales(locale_path
, &valid_locales
, error
))
411 for (std::set
<std::string
>::const_iterator locale
= valid_locales
.begin();
412 locale
!= valid_locales
.end();
414 std::string locale_error
;
415 scoped_ptr
<base::DictionaryValue
> catalog(
416 LoadMessageFile(locale_path
, *locale
, &locale_error
));
418 if (!locale_error
.empty()) {
421 error
->append(locale_error
);
425 return error
->empty();
428 bool ShouldSkipValidation(const base::FilePath
& locales_path
,
429 const base::FilePath
& locale_path
,
430 const std::set
<std::string
>& all_locales
) {
431 // Since we use this string as a key in a DictionaryValue, be paranoid about
432 // skipping any strings with '.'. This happens sometimes, for example with
433 // '.svn' directories.
434 base::FilePath relative_path
;
435 if (!locales_path
.AppendRelativePath(locale_path
, &relative_path
)) {
439 std::string subdir
= relative_path
.MaybeAsASCII();
441 return true; // Non-ASCII.
443 if (std::find(subdir
.begin(), subdir
.end(), '.') != subdir
.end())
446 if (all_locales
.find(subdir
) == all_locales
.end())
452 ScopedLocaleForTest::ScopedLocaleForTest()
453 : locale_(extension_l10n_util::CurrentLocaleOrDefault()) {}
455 ScopedLocaleForTest::ScopedLocaleForTest(const std::string
& locale
)
456 : locale_(extension_l10n_util::CurrentLocaleOrDefault()) {
457 extension_l10n_util::SetProcessLocale(locale
);
460 ScopedLocaleForTest::~ScopedLocaleForTest() {
461 extension_l10n_util::SetProcessLocale(locale_
);
464 } // namespace extension_l10n_util