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