extension_l10n_util.cc revision dc0f95d653279beabeb9817299e2902918ba123e
1// Copyright (c) 2009 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.
4
5#include "chrome/common/extensions/extension_l10n_util.h"
6
7#include <set>
8#include <string>
9#include <vector>
10
11#include "base/file_util.h"
12#include "base/linked_ptr.h"
13#include "base/logging.h"
14#include "base/string_util.h"
15#include "base/values.h"
16#include "chrome/common/extensions/extension.h"
17#include "chrome/common/extensions/extension_constants.h"
18#include "chrome/common/extensions/extension_file_util.h"
19#include "chrome/common/extensions/extension_message_bundle.h"
20#include "chrome/common/json_value_serializer.h"
21#include "chrome/common/url_constants.h"
22#include "ui/base/l10n/l10n_util.h"
23#include "unicode/uloc.h"
24
25namespace errors = extension_manifest_errors;
26namespace keys = extension_manifest_keys;
27
28static std::string* GetProcessLocale() {
29  static std::string locale;
30  return &locale;
31}
32
33namespace extension_l10n_util {
34
35void SetProcessLocale(const std::string& locale) {
36  *(GetProcessLocale()) = locale;
37}
38
39std::string GetDefaultLocaleFromManifest(const DictionaryValue& manifest,
40                                         std::string* error) {
41  std::string default_locale;
42  if (manifest.GetString(keys::kDefaultLocale, &default_locale))
43    return default_locale;
44
45  *error = errors::kInvalidDefaultLocale;
46  return "";
47
48}
49
50bool ShouldRelocalizeManifest(const ExtensionInfo& info) {
51  DictionaryValue* manifest = info.extension_manifest.get();
52  if (!manifest)
53    return false;
54
55  if (!manifest->HasKey(keys::kDefaultLocale))
56    return false;
57
58  std::string manifest_current_locale;
59  manifest->GetString(keys::kCurrentLocale, &manifest_current_locale);
60  return manifest_current_locale != CurrentLocaleOrDefault();
61}
62
63// Localizes manifest value for a given key.
64static bool LocalizeManifestValue(const std::string& key,
65                                  const ExtensionMessageBundle& messages,
66                                  DictionaryValue* manifest,
67                                  std::string* error) {
68  std::string result;
69  if (!manifest->GetString(key, &result))
70    return true;
71
72  if (!messages.ReplaceMessages(&result, error))
73    return false;
74
75  manifest->SetString(key, result);
76  return true;
77}
78
79bool LocalizeManifest(const ExtensionMessageBundle& messages,
80                      DictionaryValue* manifest,
81                      std::string* error) {
82  // Initialize name.
83  std::string result;
84  if (!manifest->GetString(keys::kName, &result)) {
85    *error = errors::kInvalidName;
86    return false;
87  }
88  if (!LocalizeManifestValue(keys::kName, messages, manifest, error)) {
89    return false;
90  }
91
92  // Initialize description.
93  if (!LocalizeManifestValue(keys::kDescription, messages, manifest, error))
94    return false;
95
96  // Initialize browser_action.default_title
97  std::string key(keys::kBrowserAction);
98  key.append(".");
99  key.append(keys::kPageActionDefaultTitle);
100  if (!LocalizeManifestValue(key, messages, manifest, error))
101    return false;
102
103  // Initialize page_action.default_title
104  key.assign(keys::kPageAction);
105  key.append(".");
106  key.append(keys::kPageActionDefaultTitle);
107  if (!LocalizeManifestValue(key, messages, manifest, error))
108    return false;
109
110  // Initialize omnibox.keyword.
111  if (!LocalizeManifestValue(keys::kOmniboxKeyword, messages, manifest, error))
112    return false;
113
114  // Add current locale key to the manifest, so we can overwrite prefs
115  // with new manifest when chrome locale changes.
116  manifest->SetString(keys::kCurrentLocale, CurrentLocaleOrDefault());
117  return true;
118}
119
120bool LocalizeExtension(const FilePath& extension_path,
121                       DictionaryValue* manifest,
122                       std::string* error) {
123  DCHECK(manifest);
124
125  std::string default_locale = GetDefaultLocaleFromManifest(*manifest, error);
126
127  scoped_ptr<ExtensionMessageBundle> message_bundle(
128      extension_file_util::LoadExtensionMessageBundle(
129          extension_path, default_locale, error));
130
131  if (!message_bundle.get() && !error->empty())
132    return false;
133
134  if (message_bundle.get() &&
135      !LocalizeManifest(*message_bundle, manifest, error))
136    return false;
137
138  return true;
139}
140
141bool AddLocale(const std::set<std::string>& chrome_locales,
142               const FilePath& locale_folder,
143               const std::string& locale_name,
144               std::set<std::string>* valid_locales,
145               std::string* error) {
146  // Accept name that starts with a . but don't add it to the list of supported
147  // locales.
148  if (locale_name.find(".") == 0)
149    return true;
150  if (chrome_locales.find(locale_name) == chrome_locales.end()) {
151    // Warn if there is an extension locale that's not in the Chrome list,
152    // but don't fail.
153    LOG(WARNING) << base::StringPrintf("Supplied locale %s is not supported.",
154                                       locale_name.c_str());
155    return true;
156  }
157  // Check if messages file is actually present (but don't check content).
158  if (file_util::PathExists(
159      locale_folder.Append(Extension::kMessagesFilename))) {
160    valid_locales->insert(locale_name);
161  } else {
162    *error = base::StringPrintf("Catalog file is missing for locale %s.",
163                                locale_name.c_str());
164    return false;
165  }
166
167  return true;
168}
169
170std::string CurrentLocaleOrDefault() {
171  std::string current_locale = l10n_util::NormalizeLocale(*GetProcessLocale());
172  if (current_locale.empty())
173    current_locale = "en";
174
175  return current_locale;
176}
177
178void GetAllLocales(std::set<std::string>* all_locales) {
179  const std::vector<std::string>& available_locales =
180      l10n_util::GetAvailableLocales();
181  // Add all parents of the current locale to the available locales set.
182  // I.e. for sr_Cyrl_RS we add sr_Cyrl_RS, sr_Cyrl and sr.
183  for (size_t i = 0; i < available_locales.size(); ++i) {
184    std::vector<std::string> result;
185    l10n_util::GetParentLocales(available_locales[i], &result);
186    all_locales->insert(result.begin(), result.end());
187  }
188}
189
190bool GetValidLocales(const FilePath& locale_path,
191                     std::set<std::string>* valid_locales,
192                     std::string* error) {
193  static std::set<std::string> chrome_locales;
194  GetAllLocales(&chrome_locales);
195
196  // Enumerate all supplied locales in the extension.
197  file_util::FileEnumerator locales(locale_path,
198                                    false,
199                                    file_util::FileEnumerator::DIRECTORIES);
200  FilePath locale_folder;
201  while (!(locale_folder = locales.Next()).empty()) {
202    std::string locale_name = locale_folder.BaseName().MaybeAsASCII();
203    if (locale_name.empty()) {
204      NOTREACHED();
205      continue;  // Not ASCII.
206    }
207    if (!AddLocale(chrome_locales,
208                   locale_folder,
209                   locale_name,
210                   valid_locales,
211                   error)) {
212      return false;
213    }
214  }
215
216  if (valid_locales->empty()) {
217    *error = extension_manifest_errors::kLocalesNoValidLocaleNamesListed;
218    return false;
219  }
220
221  return true;
222}
223
224// Loads contents of the messages file for given locale. If file is not found,
225// or there was parsing error we return NULL and set |error|.
226// Caller owns the returned object.
227static DictionaryValue* LoadMessageFile(const FilePath& locale_path,
228                                        const std::string& locale,
229                                        std::string* error) {
230  std::string extension_locale = locale;
231  FilePath file = locale_path.AppendASCII(extension_locale)
232      .Append(Extension::kMessagesFilename);
233  JSONFileValueSerializer messages_serializer(file);
234  Value *dictionary = messages_serializer.Deserialize(NULL, error);
235  if (!dictionary && error->empty()) {
236    // JSONFileValueSerializer just returns NULL if file cannot be found. It
237    // doesn't set the error, so we have to do it.
238    *error = base::StringPrintf("Catalog file is missing for locale %s.",
239                                extension_locale.c_str());
240  }
241
242  return static_cast<DictionaryValue*>(dictionary);
243}
244
245ExtensionMessageBundle* LoadMessageCatalogs(
246    const FilePath& locale_path,
247    const std::string& default_locale,
248    const std::string& application_locale,
249    const std::set<std::string>& valid_locales,
250    std::string* error) {
251  // Order locales to load as current_locale, first_parent, ..., default_locale.
252  std::vector<std::string> all_fallback_locales;
253  if (!application_locale.empty() && application_locale != default_locale)
254    l10n_util::GetParentLocales(application_locale, &all_fallback_locales);
255  all_fallback_locales.push_back(default_locale);
256
257  std::vector<linked_ptr<DictionaryValue> > catalogs;
258  for (size_t i = 0; i < all_fallback_locales.size(); ++i) {
259    // Skip all parent locales that are not supplied.
260    if (valid_locales.find(all_fallback_locales[i]) == valid_locales.end())
261      continue;
262    linked_ptr<DictionaryValue> catalog(
263      LoadMessageFile(locale_path, all_fallback_locales[i], error));
264    if (!catalog.get()) {
265      // If locale is valid, but messages.json is corrupted or missing, return
266      // an error.
267      return NULL;
268    } else {
269      catalogs.push_back(catalog);
270    }
271  }
272
273  return ExtensionMessageBundle::Create(catalogs, error);
274}
275
276bool ShouldSkipValidation(const FilePath& locales_path,
277                          const FilePath& locale_path,
278                          const std::set<std::string>& all_locales) {
279  // Since we use this string as a key in a DictionaryValue, be paranoid about
280  // skipping any strings with '.'. This happens sometimes, for example with
281  // '.svn' directories.
282  FilePath relative_path;
283  if (!locales_path.AppendRelativePath(locale_path, &relative_path)) {
284    NOTREACHED();
285    return true;
286  }
287  std::string subdir = relative_path.MaybeAsASCII();
288  if (subdir.empty())
289    return true;  // Non-ASCII.
290
291  if (std::find(subdir.begin(), subdir.end(), '.') != subdir.end())
292    return true;
293
294  if (all_locales.find(subdir) == all_locales.end())
295    return true;
296
297  return false;
298}
299
300}  // namespace extension_l10n_util
301