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