1 // Copyright (c) 2012 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 "chrome/common/extensions/extension_l10n_util.h"
12 #include "base/file_util.h"
13 #include "base/files/file_enumerator.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 "chrome/common/extensions/extension_file_util.h"
21 #include "chrome/common/extensions/message_bundle.h"
22 #include "chrome/common/url_constants.h"
23 #include "extensions/common/constants.h"
24 #include "extensions/common/error_utils.h"
25 #include "extensions/common/manifest_constants.h"
26 #include "third_party/icu/source/common/unicode/uloc.h"
27 #include "ui/base/l10n/l10n_util.h"
29 namespace errors
= extensions::manifest_errors
;
30 namespace keys
= extensions::manifest_keys
;
34 // Loads contents of the messages file for given locale. If file is not found,
35 // or there was parsing error we return NULL and set |error|.
36 // Caller owns the returned object.
37 base::DictionaryValue
* LoadMessageFile(const base::FilePath
& locale_path
,
38 const std::string
& locale
,
40 base::FilePath file
= locale_path
.AppendASCII(locale
)
41 .Append(extensions::kMessagesFilename
);
42 JSONFileValueSerializer
messages_serializer(file
);
43 base::Value
* dictionary
= messages_serializer
.Deserialize(NULL
, error
);
46 // JSONFileValueSerializer just returns NULL if file cannot be found. It
47 // doesn't set the error, so we have to do it.
48 *error
= base::StringPrintf("Catalog file is missing for locale %s.",
51 *error
= extensions::ErrorUtils::FormatErrorMessage(
52 errors::kLocalesInvalidLocale
,
53 base::UTF16ToUTF8(file
.LossyDisplayName()),
58 return static_cast<base::DictionaryValue
*>(dictionary
);
61 // Localizes manifest value of string type for a given key.
62 bool LocalizeManifestValue(const std::string
& key
,
63 const extensions::MessageBundle
& messages
,
64 base::DictionaryValue
* manifest
,
67 if (!manifest
->GetString(key
, &result
))
70 if (!messages
.ReplaceMessages(&result
, error
))
73 manifest
->SetString(key
, result
);
77 // Localizes manifest value of list type for a given key.
78 bool LocalizeManifestListValue(const std::string
& key
,
79 const extensions::MessageBundle
& messages
,
80 base::DictionaryValue
* manifest
,
82 base::ListValue
* list
= NULL
;
83 if (!manifest
->GetList(key
, &list
))
87 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
89 if (list
->GetString(i
, &result
)) {
90 if (messages
.ReplaceMessages(&result
, error
))
91 list
->Set(i
, new base::StringValue(result
));
99 std::string
& GetProcessLocale() {
100 CR_DEFINE_STATIC_LOCAL(std::string
, locale
, ());
106 namespace extension_l10n_util
{
108 void SetProcessLocale(const std::string
& locale
) {
109 GetProcessLocale() = locale
;
112 std::string
GetDefaultLocaleFromManifest(const base::DictionaryValue
& manifest
,
113 std::string
* error
) {
114 std::string default_locale
;
115 if (manifest
.GetString(keys::kDefaultLocale
, &default_locale
))
116 return default_locale
;
118 *error
= errors::kInvalidDefaultLocale
;
119 return std::string();
122 bool ShouldRelocalizeManifest(const base::DictionaryValue
* manifest
) {
126 if (!manifest
->HasKey(keys::kDefaultLocale
))
129 std::string manifest_current_locale
;
130 manifest
->GetString(keys::kCurrentLocale
, &manifest_current_locale
);
131 return manifest_current_locale
!= CurrentLocaleOrDefault();
134 bool LocalizeManifest(const extensions::MessageBundle
& messages
,
135 base::DictionaryValue
* manifest
,
136 std::string
* error
) {
139 if (!manifest
->GetString(keys::kName
, &result
)) {
140 *error
= errors::kInvalidName
;
143 if (!LocalizeManifestValue(keys::kName
, messages
, manifest
, error
)) {
147 // Initialize short name.
148 if (!LocalizeManifestValue(keys::kShortName
, messages
, manifest
, error
))
151 // Initialize description.
152 if (!LocalizeManifestValue(keys::kDescription
, messages
, manifest
, error
))
155 // Initialize browser_action.default_title
156 std::string
key(keys::kBrowserAction
);
158 key
.append(keys::kPageActionDefaultTitle
);
159 if (!LocalizeManifestValue(key
, messages
, manifest
, error
))
162 // Initialize page_action.default_title
163 key
.assign(keys::kPageAction
);
165 key
.append(keys::kPageActionDefaultTitle
);
166 if (!LocalizeManifestValue(key
, messages
, manifest
, error
))
169 // Initialize omnibox.keyword.
170 if (!LocalizeManifestValue(keys::kOmniboxKeyword
, messages
, manifest
, error
))
173 base::ListValue
* file_handlers
= NULL
;
174 if (manifest
->GetList(keys::kFileBrowserHandlers
, &file_handlers
)) {
175 key
.assign(keys::kFileBrowserHandlers
);
176 for (size_t i
= 0; i
< file_handlers
->GetSize(); i
++) {
177 base::DictionaryValue
* handler
= NULL
;
178 if (!file_handlers
->GetDictionary(i
, &handler
)) {
179 *error
= errors::kInvalidFileBrowserHandler
;
182 if (!LocalizeManifestValue(keys::kPageActionDefaultTitle
, messages
,
188 base::ListValue
* media_galleries_handlers
= NULL
;
189 if (manifest
->GetList(keys::kMediaGalleriesHandlers
,
190 &media_galleries_handlers
)) {
191 key
.assign(keys::kMediaGalleriesHandlers
);
192 for (size_t i
= 0; i
< media_galleries_handlers
->GetSize(); i
++) {
193 base::DictionaryValue
* handler
= NULL
;
194 if (!media_galleries_handlers
->GetDictionary(i
, &handler
)) {
195 *error
= errors::kInvalidMediaGalleriesHandler
;
198 if (!LocalizeManifestValue(keys::kPageActionDefaultTitle
, messages
,
204 // Initialize all input_components
205 base::ListValue
* input_components
= NULL
;
206 if (manifest
->GetList(keys::kInputComponents
, &input_components
)) {
207 for (size_t i
= 0; i
< input_components
->GetSize(); ++i
) {
208 base::DictionaryValue
* module
= NULL
;
209 if (!input_components
->GetDictionary(i
, &module
)) {
210 *error
= errors::kInvalidInputComponents
;
213 if (!LocalizeManifestValue(keys::kName
, messages
, module
, error
))
215 if (!LocalizeManifestValue(keys::kDescription
, messages
, module
, error
))
220 // Initialize app.launch.local_path.
221 if (!LocalizeManifestValue(keys::kLaunchLocalPath
, messages
, manifest
, error
))
224 // Initialize app.launch.web_url.
225 if (!LocalizeManifestValue(keys::kLaunchWebURL
, messages
, manifest
, error
))
228 // Initialize description of commmands.
229 base::DictionaryValue
* commands_handler
= NULL
;
230 if (manifest
->GetDictionary(keys::kCommands
, &commands_handler
)) {
231 for (base::DictionaryValue::Iterator
iter(*commands_handler
);
234 key
.assign(base::StringPrintf("commands.%s.description",
235 iter
.key().c_str()));
236 if (!LocalizeManifestValue(key
, messages
, manifest
, error
))
241 // Initialize search_provider fields.
242 base::DictionaryValue
* search_provider
= NULL
;
243 if (manifest
->GetDictionary(keys::kSearchProvider
, &search_provider
)) {
244 for (base::DictionaryValue::Iterator
iter(*search_provider
);
247 key
.assign(base::StringPrintf("%s.%s", keys::kSearchProvider
,
248 iter
.key().c_str()));
249 bool success
= (key
== keys::kSettingsOverrideAlternateUrls
) ?
250 LocalizeManifestListValue(key
, messages
, manifest
, error
) :
251 LocalizeManifestValue(key
, messages
, manifest
, error
);
257 // Add current locale key to the manifest, so we can overwrite prefs
258 // with new manifest when chrome locale changes.
259 manifest
->SetString(keys::kCurrentLocale
, CurrentLocaleOrDefault());
263 bool LocalizeExtension(const base::FilePath
& extension_path
,
264 base::DictionaryValue
* manifest
,
265 std::string
* error
) {
268 std::string default_locale
= GetDefaultLocaleFromManifest(*manifest
, error
);
270 scoped_ptr
<extensions::MessageBundle
> message_bundle(
271 extension_file_util::LoadMessageBundle(
272 extension_path
, default_locale
, error
));
274 if (!message_bundle
.get() && !error
->empty())
277 if (message_bundle
.get() &&
278 !LocalizeManifest(*message_bundle
, manifest
, error
))
284 bool AddLocale(const std::set
<std::string
>& chrome_locales
,
285 const base::FilePath
& locale_folder
,
286 const std::string
& locale_name
,
287 std::set
<std::string
>* valid_locales
,
288 std::string
* error
) {
289 // Accept name that starts with a . but don't add it to the list of supported
291 if (locale_name
.find(".") == 0)
293 if (chrome_locales
.find(locale_name
) == chrome_locales
.end()) {
294 // Warn if there is an extension locale that's not in the Chrome list,
296 DLOG(WARNING
) << base::StringPrintf("Supplied locale %s is not supported.",
297 locale_name
.c_str());
300 // Check if messages file is actually present (but don't check content).
301 if (base::PathExists(
302 locale_folder
.Append(extensions::kMessagesFilename
))) {
303 valid_locales
->insert(locale_name
);
305 *error
= base::StringPrintf("Catalog file is missing for locale %s.",
306 locale_name
.c_str());
313 std::string
CurrentLocaleOrDefault() {
314 std::string current_locale
= l10n_util::NormalizeLocale(GetProcessLocale());
315 if (current_locale
.empty())
316 current_locale
= "en";
318 return current_locale
;
321 void GetAllLocales(std::set
<std::string
>* all_locales
) {
322 const std::vector
<std::string
>& available_locales
=
323 l10n_util::GetAvailableLocales();
324 // Add all parents of the current locale to the available locales set.
325 // I.e. for sr_Cyrl_RS we add sr_Cyrl_RS, sr_Cyrl and sr.
326 for (size_t i
= 0; i
< available_locales
.size(); ++i
) {
327 std::vector
<std::string
> result
;
328 l10n_util::GetParentLocales(available_locales
[i
], &result
);
329 all_locales
->insert(result
.begin(), result
.end());
333 void GetAllFallbackLocales(const std::string
& application_locale
,
334 const std::string
& default_locale
,
335 std::vector
<std::string
>* all_fallback_locales
) {
336 DCHECK(all_fallback_locales
);
337 if (!application_locale
.empty() && application_locale
!= default_locale
)
338 l10n_util::GetParentLocales(application_locale
, all_fallback_locales
);
339 all_fallback_locales
->push_back(default_locale
);
342 bool GetValidLocales(const base::FilePath
& locale_path
,
343 std::set
<std::string
>* valid_locales
,
344 std::string
* error
) {
345 std::set
<std::string
> chrome_locales
;
346 GetAllLocales(&chrome_locales
);
348 // Enumerate all supplied locales in the extension.
349 base::FileEnumerator
locales(locale_path
,
351 base::FileEnumerator::DIRECTORIES
);
352 base::FilePath locale_folder
;
353 while (!(locale_folder
= locales
.Next()).empty()) {
354 std::string locale_name
= locale_folder
.BaseName().MaybeAsASCII();
355 if (locale_name
.empty()) {
357 continue; // Not ASCII.
359 if (!AddLocale(chrome_locales
,
368 if (valid_locales
->empty()) {
369 *error
= errors::kLocalesNoValidLocaleNamesListed
;
376 extensions::MessageBundle
* LoadMessageCatalogs(
377 const base::FilePath
& locale_path
,
378 const std::string
& default_locale
,
379 const std::string
& application_locale
,
380 const std::set
<std::string
>& valid_locales
,
381 std::string
* error
) {
382 std::vector
<std::string
> all_fallback_locales
;
383 GetAllFallbackLocales(application_locale
, default_locale
,
384 &all_fallback_locales
);
386 std::vector
<linked_ptr
<base::DictionaryValue
> > catalogs
;
387 for (size_t i
= 0; i
< all_fallback_locales
.size(); ++i
) {
388 // Skip all parent locales that are not supplied.
389 if (valid_locales
.find(all_fallback_locales
[i
]) == valid_locales
.end())
391 linked_ptr
<base::DictionaryValue
> catalog(
392 LoadMessageFile(locale_path
, all_fallback_locales
[i
], error
));
393 if (!catalog
.get()) {
394 // If locale is valid, but messages.json is corrupted or missing, return
398 catalogs
.push_back(catalog
);
402 return extensions::MessageBundle::Create(catalogs
, error
);
405 bool ValidateExtensionLocales(const base::FilePath
& extension_path
,
406 const base::DictionaryValue
* manifest
,
407 std::string
* error
) {
408 std::string default_locale
= GetDefaultLocaleFromManifest(*manifest
, error
);
410 if (default_locale
.empty())
413 base::FilePath locale_path
=
414 extension_path
.Append(extensions::kLocaleFolder
);
416 std::set
<std::string
> valid_locales
;
417 if (!GetValidLocales(locale_path
, &valid_locales
, error
))
420 for (std::set
<std::string
>::const_iterator locale
= valid_locales
.begin();
421 locale
!= valid_locales
.end(); ++locale
) {
422 std::string locale_error
;
423 scoped_ptr
<base::DictionaryValue
> catalog(
424 LoadMessageFile(locale_path
, *locale
, &locale_error
));
426 if (!locale_error
.empty()) {
429 error
->append(locale_error
);
433 return error
->empty();
436 bool ShouldSkipValidation(const base::FilePath
& locales_path
,
437 const base::FilePath
& locale_path
,
438 const std::set
<std::string
>& all_locales
) {
439 // Since we use this string as a key in a DictionaryValue, be paranoid about
440 // skipping any strings with '.'. This happens sometimes, for example with
441 // '.svn' directories.
442 base::FilePath relative_path
;
443 if (!locales_path
.AppendRelativePath(locale_path
, &relative_path
)) {
447 std::string subdir
= relative_path
.MaybeAsASCII();
449 return true; // Non-ASCII.
451 if (std::find(subdir
.begin(), subdir
.end(), '.') != subdir
.end())
454 if (all_locales
.find(subdir
) == all_locales
.end())
460 ScopedLocaleForTest::ScopedLocaleForTest()
461 : locale_(extension_l10n_util::CurrentLocaleOrDefault()) {}
463 ScopedLocaleForTest::ScopedLocaleForTest(const std::string
& locale
)
464 : locale_(extension_l10n_util::CurrentLocaleOrDefault()) {
465 extension_l10n_util::SetProcessLocale(locale
);
468 ScopedLocaleForTest::~ScopedLocaleForTest() {
469 extension_l10n_util::SetProcessLocale(locale_
);
472 } // namespace extension_l10n_util