aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2011-07-29 12:09:43 +0200
committerSimon Brandhof <simon.brandhof@gmail.com>2011-07-29 12:09:43 +0200
commit402f8958a403d94422f38d47cc39aba0f13e8aae (patch)
treee4bac52b9a2927d7e994d9961bacf6d880611db7
parent15f5d562a3da55f3702500c1c855f3f9d7a52e2d (diff)
downloadsonarqube-402f8958a403d94422f38d47cc39aba0f13e8aae.tar.gz
sonarqube-402f8958a403d94422f38d47cc39aba0f13e8aae.zip
SONAR-75 Improve i18n API
- The extension point LanguagePack is not required anymore - No error logs when rule description is not available in all locales - Increase code coverage and decrease complexity
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java4
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/I18nManager.java316
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/InstalledPlugin.java46
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/EnglishLanguagePack.java39
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/FrenchLanguagePack.java39
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/I18nManagerTest.java171
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/QuebecLanguagePack.java39
-rwxr-xr-xplugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin.jarbin1561 -> 0 bytes
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/META-INF/MANIFEST.MF3
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test.properties6
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test/anotherfakerule.html2
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test/fakerule.html2
-rwxr-xr-xplugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin.jarbin1130 -> 0 bytes
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/META-INF/MANIFEST.MF3
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr.properties4
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr/fakerule.html2
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/QuebecPlugin.jarbin1051 -> 0 bytes
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/QuebecPlugin/META-INF/MANIFEST.MF3
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties2
-rw-r--r--plugins/sonar-i18n-en-plugin/pom.xml2
-rw-r--r--plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPack.java37
-rw-r--r--plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPackPlugin.java6
-rw-r--r--sonar-core/src/main/java/org/sonar/core/i18n/I18nManager.java141
-rw-r--r--sonar-core/src/main/java/org/sonar/core/i18n/RuleI18nManager.java111
-rw-r--r--sonar-core/src/test/java/org/sonar/core/i18n/I18nManagerTest.java138
-rw-r--r--sonar-core/src/test/java/org/sonar/core/i18n/RuleI18nManagerTest.java90
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/i18n/englishPack/org/sonar/i18n/checkstyle.properties1
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/i18n/englishPack/org/sonar/i18n/checkstyle/ArchitectureRule.html1
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/i18n/englishPack/org/sonar/i18n/core.properties4
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/i18n/frenchPack/org/sonar/i18n/checkstyle_fr.properties1
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/i18n/frenchPack/org/sonar/i18n/checkstyle_fr/ArchitectureRule.html1
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/i18n/frenchPack/org/sonar/i18n/core_fr.properties2
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/i18n/sqalePlugin/org/sonar/i18n/sqale.properties1
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/i18n/sqalePlugin/org/sonar/i18n/sqale_fr.properties1
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/i18n/LanguagePack.java54
-rw-r--r--sonar-server/src/main/java/org/sonar/server/platform/Platform.java4
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java23
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ui/JRubyI18n.java21
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb7
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/api/utils.rb2
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/rule.rb63
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/rules_parameter.rb149
-rw-r--r--sonar-server/src/test/java/org/sonar/server/ui/JRubyI18nTest.java3
43 files changed, 612 insertions, 932 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
index bf045ccbcc7..97b8e06428c 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
@@ -34,7 +34,6 @@ import org.sonar.plugins.core.charts.XradarChart;
import org.sonar.plugins.core.colorizers.JavaColorizerFormat;
import org.sonar.plugins.core.duplicationsviewer.DuplicationsViewerDefinition;
import org.sonar.plugins.core.hotspots.Hotspots;
-import org.sonar.plugins.core.i18n.I18nManager;
import org.sonar.plugins.core.metrics.UserManagedMetrics;
import org.sonar.plugins.core.security.ApplyProjectRolesDecorator;
import org.sonar.plugins.core.sensors.*;
@@ -241,9 +240,6 @@ public class CorePlugin extends SonarPlugin {
extensions.add(NewCoverageFileAnalyzer.class);
extensions.add(NewCoverageAggregator.class);
- // i18n
- extensions.add(I18nManager.class);
-// extensions.add(org.sonar.core.i18n.I18nManager.class);
return extensions;
}
}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/I18nManager.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/I18nManager.java
deleted file mode 100644
index b3cd4db85ac..00000000000
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/I18nManager.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.core.i18n;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import org.apache.commons.collections.EnumerationUtils;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.ServerExtension;
-import org.sonar.api.i18n.I18n;
-import org.sonar.api.i18n.LanguagePack;
-import org.sonar.api.platform.PluginRepository;
-import org.sonar.api.utils.SonarException;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.text.MessageFormat;
-import java.util.*;
-
-public final class I18nManager implements I18n, ServerExtension {
-
- private static final Logger LOG = LoggerFactory.getLogger(I18nManager.class);
- public static final String packagePathToSearchIn = "org/sonar/i18n";
-
- private PluginRepository pluginRepository;
- private LanguagePack[] languagePacks;
- private Map<String, String> keyToBundles = Maps.newHashMap();
- private BundleClassLoader bundleClassLoader = new BundleClassLoader();
- private List<Locale> registeredLocales = Lists.newArrayList();
-
- public I18nManager(PluginRepository pluginRepository, LanguagePack[] languagePacks) {
- this.pluginRepository = pluginRepository;
- this.languagePacks = languagePacks;
- }
-
- public I18nManager(PluginRepository pluginRepository) {
- this(pluginRepository, new LanguagePack[0]);
- }
-
- public void start() {
- doStart(InstalledPlugin.create(pluginRepository));
- }
-
- void doStart(List<InstalledPlugin> installedPlugins) {
- LOG.info("Loading i18n bundles");
- Set<URI> alreadyLoadedResources = Sets.newHashSet();
- LanguagePack englishPack = findEnglishPack();
-
- for (InstalledPlugin plugin : installedPlugins) {
- // look first in the classloader of the English Language Pack
- searchAndStoreBundleNames(plugin.key, englishPack.getClass().getClassLoader(), alreadyLoadedResources);
- // then look in the classloader of the plugin
- searchAndStoreBundleNames(plugin.key, plugin.classloader, alreadyLoadedResources);
- }
-
- for (LanguagePack pack : languagePacks) {
- if (!pack.equals(englishPack)) {
- addLanguagePack(pack);
- }
- }
- }
-
- protected LanguagePack findEnglishPack() {
- LanguagePack englishPack = null;
- for (LanguagePack pack : languagePacks) {
- if (pack.getLocales().contains(Locale.ENGLISH)) {
- englishPack = pack;
- break;
- }
- }
- if (englishPack == null) {
- throw new SonarException("The I18n English Pack was not found.");
- }
- return englishPack;
- }
-
- protected void addLanguagePack(LanguagePack languagePack) {
- LOG.debug("Search for bundles in language pack : {}", languagePack);
- for (String pluginKey : languagePack.getPluginKeys()) {
- String bundleBaseName = buildBundleBaseName(pluginKey);
- for (Locale locale : languagePack.getLocales()) {
- String bundlePropertiesFile = new StringBuilder(bundleBaseName).append('_').append(locale.toString()).append(".properties").toString();
- ClassLoader classloader = languagePack.getClass().getClassLoader();
- LOG.info("Adding locale {} for bundleName : {} from classloader : {}", new Object[]{locale, bundleBaseName, classloader});
- bundleClassLoader.addResource(bundlePropertiesFile, classloader);
- registeredLocales.add(locale);
- }
- }
- }
-
- protected String buildBundleBaseName(String pluginKey) {
- return packagePathToSearchIn + "/" + pluginKey;
- }
-
- @SuppressWarnings("unchecked")
- protected void searchAndStoreBundleNames(String pluginKey, ClassLoader classloader, Set<URI> alreadyLoadedResources) {
- String bundleBaseName = buildBundleBaseName(pluginKey);
- String bundleDefaultPropertiesFile = bundleBaseName + ".properties";
- try {
- LOG.debug("Search for ResourceBundle base file '" + bundleDefaultPropertiesFile + "' in the classloader : " + classloader);
- List<URL> resources = EnumerationUtils.toList(classloader.getResources(bundleDefaultPropertiesFile));
- if (resources.size() > 0) {
- if (resources.size() > 1) {
- LOG.debug("File '{}' found several times in the classloader : {}. Only the first one will be taken in account.",
- bundleDefaultPropertiesFile, classloader);
- }
-
- URL propertiesUrl = resources.get(0);
- if (!alreadyLoadedResources.contains(propertiesUrl.toURI())) {
- LOG.debug("Found the ResourceBundle base file : {} from classloader : {}", propertiesUrl, classloader);
- LOG.debug("Add bundleName : {} from classloader : {}", bundleBaseName, classloader);
- bundleClassLoader.addResource(bundleDefaultPropertiesFile, classloader);
- alreadyLoadedResources.add(propertiesUrl.toURI());
-
- Properties bundleContent = new Properties();
- InputStream input = null;
- try {
- input = propertiesUrl.openStream();
- bundleContent.load(input);
- Enumeration<String> keysToAdd = (Enumeration<String>) bundleContent.propertyNames();
- while (keysToAdd.hasMoreElements()) {
- String key = keysToAdd.nextElement();
- if (keyToBundles.containsKey(key)) {
- LOG.debug("DUPLICATE KEY : Key '{}' defined in bundle '{}' is already defined in bundle '{}'. It is ignored.",
- new Object[]{key, bundleBaseName, keyToBundles.get(key)});
- } else {
- keyToBundles.put(key, bundleBaseName);
- }
- }
- } finally {
- IOUtils.closeQuietly(input);
- }
- }
- }
- } catch (Exception e) {
- LOG.error("Fail to load '" + bundleDefaultPropertiesFile + "' in classloader : " + classloader, e);
- throw new SonarException("Fail to load '" + bundleDefaultPropertiesFile + "' in classloader : " + classloader, e);
- }
- }
-
- public String message(final Locale locale, final String key, final String defaultText, final Object... objects) {
- String translatedMessage = defaultText;
- try {
- if (isKeyForRuleDescription(key)) {
- // Rule descriptions are in HTML files, not in regular bundles
- translatedMessage = findRuleDescription(locale, key, defaultText);
- } else {
- translatedMessage = findStandardMessage(locale, key, defaultText, objects);
- }
- } catch (MissingResourceException e) {
- if (translatedMessage == null) {
- // when no translation has been found, the key is returned
- return key;
- }
- } catch (Exception e) {
- LOG.error("Exception when retrieving I18n string: ", e);
- if (translatedMessage == null) {
- // when no translation has been found, the key is returned
- return key;
- }
- }
- return translatedMessage;
- }
-
- protected boolean isKeyForRuleDescription(String key) {
- return StringUtils.startsWith(key, "rule.") && StringUtils.endsWith(key, ".description");
- }
-
- protected String findRuleDescription(final Locale locale, final String ruleDescriptionKey, final String defaultText) throws IOException {
- String translation = defaultText;
- String ruleNameKey = ruleDescriptionKey.replace(".description", ".name");
-
- String bundleBaseName = keyToBundles.get(ruleNameKey);
- if (bundleBaseName == null) {
- handleMissingBundle(ruleNameKey, defaultText, bundleBaseName);
- } else {
- InputStream stream = extractRuleDescription(locale, ruleDescriptionKey, bundleBaseName);
- if (stream == null && !Locale.ENGLISH.equals(locale)) {
- // let's try in the ENGLISH bundles
- stream = extractRuleDescription(Locale.ENGLISH, ruleDescriptionKey, bundleBaseName);
- }
- if (stream == null) {
- // Definitely, no HTML file found for the description of this rule...
- throw new MissingResourceException("MISSING RULE DESCRIPTION : HTML file not found in any bundle. Default value is returned.",
- bundleBaseName, ruleDescriptionKey);
- }
- translation = IOUtils.toString(stream, "UTF-8");
- }
-
- return translation;
- }
-
- private InputStream extractRuleDescription(final Locale locale, final String ruleDescriptionKey, String bundleBaseName) {
- Locale localeToUse = defineLocaleToUse(locale);
- String htmlFilePath = computeHtmlFilePath(bundleBaseName, ruleDescriptionKey, localeToUse);
- ClassLoader classLoader = bundleClassLoader.getClassLoaderForBundle(bundleBaseName, localeToUse);
- InputStream stream = classLoader.getResourceAsStream(htmlFilePath);
- return stream;
- }
-
- protected Locale defineLocaleToUse(final Locale locale) {
- Locale localeToUse = locale;
- if (!registeredLocales.contains(locale)) {
- localeToUse = Locale.ENGLISH;
- }
- return localeToUse;
- }
-
- String extractRuleKeyFromDescriptionProperty(String ruleDescriptionKey) {
- // format is "rule.<plugin>.<key>.description"
- String s = StringUtils.substringAfter(ruleDescriptionKey, "rule.");
- return StringUtils.substringBetween(s, ".", ".description");
- }
-
- protected String computeHtmlFilePath(String bundleBaseName, String ruleDescriptionKey, Locale locale) {
- String ruleName = extractRuleKeyFromDescriptionProperty(ruleDescriptionKey);
- if (Locale.ENGLISH.equals(locale)) {
- return bundleBaseName + "/" + ruleName + ".html";
- } else {
- return bundleBaseName + "_" + locale.toString() + "/" + ruleName + ".html";
- }
- }
-
- protected String findStandardMessage(final Locale locale, final String key, final String defaultText, final Object... objects) {
- String translation = defaultText;
-
- String bundleBaseName = keyToBundles.get(key);
- if (bundleBaseName == null) {
- handleMissingBundle(key, defaultText, bundleBaseName);
- } else {
- try {
- ResourceBundle bundle = ResourceBundle.getBundle(bundleBaseName, locale, bundleClassLoader);
-
- String value = bundle.getString(key);
- if ("".equals(value)) {
- if (translation == null) {
- throw new MissingResourceException("VOID KEY : Key '" + key + "' (from bundle '" + bundleBaseName + "') returns a void value.",
- bundleBaseName, key);
- }
- } else {
- translation = value;
- }
- } catch (MissingResourceException e) {
- if (translation == null) {
- throw e;
- }
- }
- }
-
- if (objects.length > 0) {
- return MessageFormat.format(translation, objects);
- }
- return translation;
- }
-
- protected void handleMissingBundle(final String key, final String defaultText, String bundleBaseName) {
- if (defaultText == null) {
- throw new MissingResourceException("UNKNOWN KEY : Key '" + key
- + "' not found in any bundle, and no default value provided. The key is returned.", bundleBaseName, key);
- }
- }
-
- private static class BundleClassLoader extends URLClassLoader {
- private Map<String, ClassLoader> resources = Maps.newHashMap();
-
- public BundleClassLoader() {
- super(new URL[]{}, null);
- }
-
- public void addResource(String resourceName, ClassLoader classloader) {
- resources.put(resourceName, classloader);
- }
-
- public ClassLoader getClassLoaderForBundle(String bundleBaseName, Locale locale) {
- StringBuilder resourceName = new StringBuilder(bundleBaseName);
- if (locale != null && !locale.equals(Locale.ENGLISH)) {
- resourceName.append("_");
- resourceName.append(locale);
- }
- resourceName.append(".properties");
- return resources.get(resourceName.toString());
- }
-
- @Override
- public URL findResource(String name) {
- if (resources.containsKey(name)) {
- return resources.get(name).getResource(name);
- }
- return null;
- }
- }
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/InstalledPlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/InstalledPlugin.java
deleted file mode 100644
index abe4b3cf0de..00000000000
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/InstalledPlugin.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.core.i18n;
-
-import com.google.common.collect.Lists;
-import org.sonar.api.Plugin;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-
-import java.util.List;
-
-class InstalledPlugin {
- String key;
- ClassLoader classloader;
-
- InstalledPlugin(String key, ClassLoader classloader) {
- this.key = key;
- this.classloader = classloader;
- }
-
- static List<InstalledPlugin> create(PluginRepository pluginRepository) {
- List<InstalledPlugin> result = Lists.newArrayList();
- for (PluginMetadata metadata : pluginRepository.getMetadata()) {
- Plugin entryPoint = pluginRepository.getPlugin(metadata.getKey());
- result.add(new InstalledPlugin(metadata.getKey(), entryPoint.getClass().getClassLoader()));
- }
- return result;
- }
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/EnglishLanguagePack.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/EnglishLanguagePack.java
deleted file mode 100644
index 0660fd60527..00000000000
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/EnglishLanguagePack.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.core.i18n;
-
-import org.sonar.api.i18n.LanguagePack;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-
-public class EnglishLanguagePack extends LanguagePack {
-
- @Override
- public List<String> getPluginKeys() {
- return Arrays.asList("test");
- }
-
- @Override
- public List<Locale> getLocales() {
- return Arrays.asList(Locale.ENGLISH);
- }
-} \ No newline at end of file
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/FrenchLanguagePack.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/FrenchLanguagePack.java
deleted file mode 100644
index e781f5a891f..00000000000
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/FrenchLanguagePack.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.core.i18n;
-
-import org.sonar.api.i18n.LanguagePack;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-
-public class FrenchLanguagePack extends LanguagePack {
-
- @Override
- public List<String> getPluginKeys() {
- return Arrays.asList("test");
- }
-
- @Override
- public List<Locale> getLocales() {
- return Arrays.asList(Locale.FRENCH);
- }
-} \ No newline at end of file
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/I18nManagerTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/I18nManagerTest.java
deleted file mode 100644
index 66c8ad17a31..00000000000
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/I18nManagerTest.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.core.i18n;
-
-import com.google.common.collect.Lists;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.i18n.LanguagePack;
-import org.sonar.api.platform.PluginRepository;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.mock;
-
-public class I18nManagerTest {
-
- public static String ENGLISH_PACK_CLASS_NAME = "org.sonar.plugins.core.i18n.EnglishLanguagePack";
- public static String FRENCH_PACK_CLASS_NAME = "org.sonar.plugins.core.i18n.FrenchLanguagePack";
- public static String QUEBEC_PACK_CLASS_NAME = "org.sonar.plugins.core.i18n.QuebecLanguagePack";
-
- private static URL classSource = I18nManagerTest.class.getProtectionDomain().getCodeSource().getLocation();
- private I18nManager manager;
-
- @Before
- public void createManager() throws Exception {
- List<InstalledPlugin> plugins = Lists.newArrayList(new InstalledPlugin("test", getClass().getClassLoader()), new InstalledPlugin(
- "fake1", getClass().getClassLoader()), new InstalledPlugin("fake2", getClass().getClassLoader()));
-
- TestClassLoader englishPackClassLoader = new TestClassLoader(getClass().getClassLoader().getResource("I18n/EnglishPlugin.jar"));
- LanguagePack englishPack = (LanguagePack) englishPackClassLoader.loadClass(ENGLISH_PACK_CLASS_NAME).newInstance();
-
- TestClassLoader frenchPackClassLoader = new TestClassLoader(getClass().getClassLoader().getResource("I18n/FrenchPlugin.jar"));
- LanguagePack frenchPack = (LanguagePack) frenchPackClassLoader.loadClass(FRENCH_PACK_CLASS_NAME).newInstance();
-
- TestClassLoader quebecPackClassLoader = new TestClassLoader(getClass().getClassLoader().getResource("I18n/QuebecPlugin.jar"));
- LanguagePack quebecPack = (LanguagePack) quebecPackClassLoader.loadClass(QUEBEC_PACK_CLASS_NAME).newInstance();
-
- manager = new I18nManager(mock(PluginRepository.class), new LanguagePack[]{frenchPack, quebecPack, englishPack});
- manager.doStart(plugins);
- }
-
- @Test
- public void shouldTranslateWithoutRegionalVariant() {
- List<String> sentence = Arrays.asList("it", "is", "cold");
- String result = "";
- for (String token : sentence) {
- result += manager.message(Locale.FRENCH, token, token) + " ";
- }
- assertEquals("Il fait froid ", result);
- }
-
- @Test
- public void shouldTranslateWithRegionalVariant() {
- // it & is are taken from the french language pack
- // and cold is taken from the quebec language pack
- List<String> sentence = Arrays.asList("it", "is", "cold");
- String result = "";
- for (String token : sentence) {
- result += manager.message(Locale.CANADA_FRENCH, token, token) + " ";
- }
- assertEquals("Il fait frette ", result);
- }
-
- @Test
- public void shouldTranslateReturnsDefaultBundleValue() {
- String result = manager.message(Locale.FRENCH, "only.english", "Default");
- assertEquals("Ketchup", result);
- }
-
- @Test
- public void shouldTranslateUnknownValue() {
- String result = manager.message(Locale.FRENCH, "unknown", "Default value for Unknown");
- assertEquals("Default value for Unknown", result);
- }
-
- @Test
- public void shouldReturnKeyIfTranslationMissingAndNotDefaultProvided() throws Exception {
- String result = manager.message(Locale.ENGLISH, "unknown.key", null);
- assertEquals("unknown.key", result);
- }
-
- @Test
- public void testIsKeyForRuleDescription() throws Exception {
- assertTrue(manager.isKeyForRuleDescription("rule.squid.ArchitecturalConstraint.description"));
- assertFalse(manager.isKeyForRuleDescription("rule.squid.ArchitecturalConstraint.name"));
- assertFalse(manager.isKeyForRuleDescription("another.key"));
- }
-
- @Test
- public void testDefineLocaleToUse() throws Exception {
- assertThat(manager.defineLocaleToUse(Locale.CANADA_FRENCH), is(Locale.CANADA_FRENCH));
- // Locale not supported => get the English one
- assertThat(manager.defineLocaleToUse(Locale.JAPAN), is(Locale.ENGLISH));
- }
-
- @Test
- public void shouldExtractRuleKey() throws Exception {
- assertThat(manager.extractRuleKeyFromDescriptionProperty("rule.squid.ArchitecturalConstraint.description"), is("ArchitecturalConstraint"));
- assertThat(manager.extractRuleKeyFromDescriptionProperty("rule.checkstyle.com.puppycrawl.checkstyle.IllegalRegexp.description"), is("com.puppycrawl.checkstyle.IllegalRegexp"));
- }
-
- @Test
- public void testComputeHtmlFilePath() throws Exception {
- assertThat(manager.computeHtmlFilePath("org/sonar/i18n/test", "rule.test.fakerule.description", Locale.FRENCH),
- is("org/sonar/i18n/test_fr/fakerule.html"));
- assertThat(manager.computeHtmlFilePath("org/sonar/i18n/test", "rule.test.fakerule.description", Locale.ENGLISH),
- is("org/sonar/i18n/test/fakerule.html"));
- }
-
- @Test
- public void shouldReturnRuleDescriptionFromHTMLFile() throws Exception {
- String result = manager.message(Locale.FRENCH, "rule.test.fakerule.description", "foo");
- assertThat(result, is("<h1>Règle bidon</h1>\nC'est la description de la règle bidon."));
- // Locale not supported => get the English translation
- result = manager.message(Locale.JAPAN, "rule.test.fakerule.description", "foo");
- assertThat(result, is("<h1>Fake Rule</h1>\nThis is the description of the fake rule."));
- }
-
- @Test
- public void shouldReturnEnglishRuleDescriptionFromMissingHTMLFileInFrench() throws Exception {
- String result = manager.message(Locale.FRENCH, "rule.test.anotherfakerule.description", "foo");
- assertThat(result, is("<h1>Another Fake Rule</h1>\nThis is the description of the fake rule."));
-
- }
-
- public static class TestClassLoader extends URLClassLoader {
-
- public TestClassLoader(URL url) {
- super(new URL[]{url, classSource}, Thread.currentThread().getContextClassLoader());
- }
-
- protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
- Class c = findLoadedClass(name);
- if (c == null) {
- if (name.equals(ENGLISH_PACK_CLASS_NAME) || name.equals(QUEBEC_PACK_CLASS_NAME) || name.equals(FRENCH_PACK_CLASS_NAME)) {
- c = findClass(name);
- } else {
- return super.loadClass(name, resolve);
- }
- }
- if (resolve) {
- resolveClass(c);
- }
- return c;
- }
- }
-
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/QuebecLanguagePack.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/QuebecLanguagePack.java
deleted file mode 100644
index c8ed96a4edb..00000000000
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/QuebecLanguagePack.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.core.i18n;
-
-import org.sonar.api.i18n.LanguagePack;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-
-public class QuebecLanguagePack extends LanguagePack {
-
- @Override
- public List<String> getPluginKeys() {
- return Arrays.asList("test");
- }
-
- @Override
- public List<Locale> getLocales() {
- return Arrays.asList(Locale.CANADA_FRENCH);
- }
-} \ No newline at end of file
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin.jar b/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin.jar
deleted file mode 100755
index f593384a2ab..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin.jar
+++ /dev/null
Binary files differ
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/META-INF/MANIFEST.MF b/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/META-INF/MANIFEST.MF
deleted file mode 100644
index a45029d3531..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,3 +0,0 @@
-Manifest-Version: 1.0
-Created-By: 1.6.0_17 (Apple Inc.)
-
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test.properties b/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test.properties
deleted file mode 100644
index 207a12a76a8..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-it=It
-is=is
-cold=cold
-only.english=Ketchup
-rule.test.fakerule.name=Fake Rule
-rule.test.anotherfakerule.name=Another Fake Rule \ No newline at end of file
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test/anotherfakerule.html b/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test/anotherfakerule.html
deleted file mode 100644
index 60539d00c95..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test/anotherfakerule.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<h1>Another Fake Rule</h1>
-This is the description of the fake rule. \ No newline at end of file
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test/fakerule.html b/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test/fakerule.html
deleted file mode 100644
index 7e7d84dea8b..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test/fakerule.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<h1>Fake Rule</h1>
-This is the description of the fake rule. \ No newline at end of file
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin.jar b/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin.jar
deleted file mode 100755
index 383c767d26d..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin.jar
+++ /dev/null
Binary files differ
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/META-INF/MANIFEST.MF b/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/META-INF/MANIFEST.MF
deleted file mode 100644
index a45029d3531..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,3 +0,0 @@
-Manifest-Version: 1.0
-Created-By: 1.6.0_17 (Apple Inc.)
-
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr.properties b/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr.properties
deleted file mode 100644
index a1d8766e962..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-it=Il
-is=fait
-cold=froid
-rule.test.fakerule.name=R\u00e8gle bidon
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr/fakerule.html b/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr/fakerule.html
deleted file mode 100644
index 94c0d0869f8..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr/fakerule.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<h1>Règle bidon</h1>
-C'est la description de la règle bidon. \ No newline at end of file
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/QuebecPlugin.jar b/plugins/sonar-core-plugin/src/test/resources/I18n/QuebecPlugin.jar
deleted file mode 100644
index 101df34ba6d..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/QuebecPlugin.jar
+++ /dev/null
Binary files differ
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/QuebecPlugin/META-INF/MANIFEST.MF b/plugins/sonar-core-plugin/src/test/resources/I18n/QuebecPlugin/META-INF/MANIFEST.MF
deleted file mode 100644
index a45029d3531..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/QuebecPlugin/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,3 +0,0 @@
-Manifest-Version: 1.0
-Created-By: 1.6.0_17 (Apple Inc.)
-
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties b/plugins/sonar-core-plugin/src/test/resources/I18n/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties
deleted file mode 100644
index 38b5b84ccdf..00000000000
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-cold=frette
-
diff --git a/plugins/sonar-i18n-en-plugin/pom.xml b/plugins/sonar-i18n-en-plugin/pom.xml
index 9e523b507f0..c25857f1b1d 100644
--- a/plugins/sonar-i18n-en-plugin/pom.xml
+++ b/plugins/sonar-i18n-en-plugin/pom.xml
@@ -51,7 +51,7 @@
<pluginName>I18n English Pack</pluginName>
<pluginClass>org.sonar.plugins.i18n.en.EnglishPackPlugin</pluginClass>
<pluginDescription>
- <![CDATA[Language pack for English.]]></pluginDescription>
+ <![CDATA[Language pack for English]]></pluginDescription>
</configuration>
</plugin>
</plugins>
diff --git a/plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPack.java b/plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPack.java
deleted file mode 100644
index 89dfc435365..00000000000
--- a/plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPack.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.plugins.i18n.en;
-
-import org.sonar.api.i18n.LanguagePack;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-
-public class EnglishPack extends LanguagePack {
-
- public List<String> getPluginKeys() {
- return Arrays.asList("core", "design", "squidjava", "checkstyle", "findbugs", "pmd");
- }
-
- public List<Locale> getLocales() {
- return Arrays.asList(Locale.ENGLISH);
- }
-}
diff --git a/plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPackPlugin.java b/plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPackPlugin.java
index 6bbe6f605e9..1cd2c84fb8f 100644
--- a/plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPackPlugin.java
+++ b/plugins/sonar-i18n-en-plugin/src/main/java/org/sonar/plugins/i18n/en/EnglishPackPlugin.java
@@ -21,12 +21,12 @@ package org.sonar.plugins.i18n.en;
import org.sonar.api.SonarPlugin;
-import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
-public class EnglishPackPlugin extends SonarPlugin {
+public final class EnglishPackPlugin extends SonarPlugin {
public List getExtensions() {
- return Arrays.asList(EnglishPack.class);
+ return Collections.emptyList();
}
}
diff --git a/sonar-core/src/main/java/org/sonar/core/i18n/I18nManager.java b/sonar-core/src/main/java/org/sonar/core/i18n/I18nManager.java
index a99ba3397bf..45f31b36853 100644
--- a/sonar-core/src/main/java/org/sonar/core/i18n/I18nManager.java
+++ b/sonar-core/src/main/java/org/sonar/core/i18n/I18nManager.java
@@ -20,50 +20,145 @@
package org.sonar.core.i18n;
import com.google.common.collect.Maps;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.sonar.api.ServerExtension;
import org.sonar.api.i18n.I18n;
import org.sonar.api.platform.PluginMetadata;
import org.sonar.api.platform.PluginRepository;
+import org.sonar.api.utils.SonarException;
+import java.io.IOException;
+import java.io.InputStream;
import java.text.MessageFormat;
-import java.util.Locale;
-import java.util.Map;
-import java.util.ResourceBundle;
+import java.util.*;
public class I18nManager implements I18n, ServerExtension {
+ private static Logger LOG = LoggerFactory.getLogger(I18nManager.class);
+
+ public static final String ENGLISH_PACK_PLUGIN_KEY = "i18nen";
+ public static final String BUNDLE_PACKAGE = "org.sonar.i18n.";
private PluginRepository pluginRepository;
- private Map<String, ClassLoader> bundleToClassloader;
+ private Map<String, ClassLoader> bundleToClassloaders;
+ private Map<String, String> propertyToBundles;
public I18nManager(PluginRepository pluginRepository) {
this.pluginRepository = pluginRepository;
}
- I18nManager(Map<String, ClassLoader> bundleToClassloader) {
- this.bundleToClassloader = bundleToClassloader;
+ I18nManager(Map<String, ClassLoader> bundleToClassloaders) {
+ this.bundleToClassloaders = bundleToClassloaders;
}
public void start() {
- ClassLoader coreClassLoader = pluginRepository.getPlugin("i18nen").getClass().getClassLoader();
+ initClassloaders();
+ initProperties();
+ }
+
+ private void initClassloaders() {
+ if (bundleToClassloaders == null) {
+ ClassLoader coreClassLoader = pluginRepository.getPlugin(ENGLISH_PACK_PLUGIN_KEY).getClass().getClassLoader();
+ bundleToClassloaders = Maps.newHashMap();
+ for (PluginMetadata metadata : pluginRepository.getMetadata()) {
+ if (!metadata.isCore() && !ENGLISH_PACK_PLUGIN_KEY.equals(metadata.getBasePlugin())) {
+ // plugin but not a language pack
+ // => plugins embedd only their own bundles with all locales
+ ClassLoader classLoader = pluginRepository.getPlugin(metadata.getKey()).getClass().getClassLoader();
+ bundleToClassloaders.put(BUNDLE_PACKAGE + metadata.getKey(), classLoader);
- bundleToClassloader = Maps.newHashMap();
- for (PluginMetadata metadata : pluginRepository.getMetadata()) {
- if (!metadata.isCore() && !"i18nen".equals(metadata.getBasePlugin())) {
- ClassLoader classLoader = pluginRepository.getPlugin(metadata.getKey()).getClass().getClassLoader();
- bundleToClassloader.put(metadata.getKey(), classLoader);
+ } else if (metadata.isCore()) {
+ // bundles of core plugins are defined into language packs. All language packs are supposed
+ // to share the same classloader (english pack classloader)
+ bundleToClassloaders.put(BUNDLE_PACKAGE + metadata.getKey(), coreClassLoader);
+ }
+ }
+ }
+ bundleToClassloaders = Collections.unmodifiableMap(bundleToClassloaders);
+ }
- } else if (metadata.isCore()) {
- bundleToClassloader.put(metadata.getKey(), coreClassLoader);
+ private void initProperties() {
+ propertyToBundles = Maps.newHashMap();
+ for (Map.Entry<String, ClassLoader> entry : bundleToClassloaders.entrySet()) {
+ try {
+ String bundleKey = entry.getKey();
+ ResourceBundle bundle = ResourceBundle.getBundle(bundleKey, Locale.ENGLISH, entry.getValue());
+ Enumeration<String> keys = bundle.getKeys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ propertyToBundles.put(key, bundleKey);
+ }
+ } catch (MissingResourceException e) {
+ // ignore
}
}
+ propertyToBundles = Collections.unmodifiableMap(propertyToBundles);
+ LOG.debug(String.format("Loaded %d properties from English bundles", propertyToBundles.size()));
}
public String message(Locale locale, String key, String defaultValue, Object... parameters) {
- String bundle = keyToBundle(key);
- ResourceBundle resourceBundle = ResourceBundle.getBundle(bundle, locale, bundleToClassloader.get(bundle));
- String value = resourceBundle.getString(key);
- if (value==null) {
+ String bundleKey = propertyToBundles.get(key);
+ ResourceBundle resourceBundle = getBundle(bundleKey, locale);
+ return message(resourceBundle, key, defaultValue, parameters);
+ }
+
+ /**
+ * Results are not kept in cache.
+ */
+ String messageFromFile(Locale locale, String filename, String relatedProperty) {
+ ClassLoader classloader = getClassLoaderForProperty(relatedProperty);
+ String result = null;
+ if (classloader != null) {
+ String bundleBase = propertyToBundles.get(relatedProperty);
+ String filePath = bundleBase.replace('.', '/');
+ if (locale != Locale.ENGLISH) {
+ filePath += "_" + locale.toString();
+ }
+ filePath += "/" + filename;
+ InputStream input = classloader.getResourceAsStream(filePath);
+ if (input != null) {
+ try {
+ result = IOUtils.toString(input, "UTF-8");
+ } catch (IOException e) {
+ throw new SonarException("Fail to load file: " + filePath, e);
+ } finally {
+ IOUtils.closeQuietly(input);
+ }
+ }
+ }
+ return result;
+ }
+
+ ResourceBundle getBundle(String bundleKey, Locale locale) {
+ try {
+ ClassLoader classloader = bundleToClassloaders.get(bundleKey);
+ if (classloader != null) {
+ return ResourceBundle.getBundle(bundleKey, locale, classloader);
+ }
+ } catch (MissingResourceException e) {
+ // ignore
+ }
+ return null;
+ }
+
+
+ ClassLoader getClassLoaderForProperty(String propertyKey) {
+ String bundleKey = propertyToBundles.get(propertyKey);
+ return (bundleKey != null ? bundleToClassloaders.get(bundleKey) : null);
+ }
+
+ String message(ResourceBundle resourceBundle, String key, String defaultValue, Object... parameters) {
+ String value = null;
+ if (resourceBundle != null) {
+ try {
+ value = resourceBundle.getString(key);
+ } catch (MissingResourceException e) {
+ // ignore
+ }
+ }
+ if (value == null) {
value = defaultValue;
}
if (value != null && parameters.length > 0) {
@@ -72,11 +167,11 @@ public class I18nManager implements I18n, ServerExtension {
return value;
}
- String keyToBundle(String key) {
- String pluginKey = StringUtils.substringBefore(key, ".");
- if (bundleToClassloader.containsKey(pluginKey)) {
- return pluginKey;
+ String extractBundleFromKey(String key) {
+ String bundleKey = BUNDLE_PACKAGE + StringUtils.substringBefore(key, ".");
+ if (bundleToClassloaders.containsKey(bundleKey)) {
+ return bundleKey;
}
- return "core";
+ return BUNDLE_PACKAGE + "core";
}
}
diff --git a/sonar-core/src/main/java/org/sonar/core/i18n/RuleI18nManager.java b/sonar-core/src/main/java/org/sonar/core/i18n/RuleI18nManager.java
new file mode 100644
index 00000000000..764871806cc
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/i18n/RuleI18nManager.java
@@ -0,0 +1,111 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.core.i18n;
+
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.ServerComponent;
+
+import java.util.Locale;
+
+public class RuleI18nManager implements ServerComponent {
+
+ private I18nManager i18nManager;
+
+ public RuleI18nManager(I18nManager i18nManager) {
+ this.i18nManager = i18nManager;
+ }
+
+ public String getName(String repositoryKey, String ruleKey, Locale locale) {
+ return message(repositoryKey, ruleKey, locale, ".name", ruleKey);
+ }
+
+ public String getDescription(String repositoryKey, String ruleKey, Locale locale) {
+ String relatedProperty = "rule." + repositoryKey + "." + ruleKey + ".name";
+
+ // TODO add cache
+ String description = i18nManager.messageFromFile(locale, ruleKey + ".html", relatedProperty);
+ if (description == null && !Locale.ENGLISH.equals(locale)) {
+ description = i18nManager.messageFromFile(Locale.ENGLISH, ruleKey + ".html", relatedProperty);
+ }
+ return StringUtils.defaultString(description, "");
+ }
+
+ public String getParamDescription(String repositoryKey, String ruleKey, String paramKey, Locale locale) {
+ return message(repositoryKey, ruleKey, locale, ".param." + paramKey, "");
+ }
+
+ private String message(String repositoryKey, String ruleKey, Locale locale, String suffix, String defaultValue) {
+ String propertyKey = new StringBuilder().append("rule.").append(repositoryKey).append(".").append(ruleKey).append(suffix).toString();
+ return i18nManager.message(locale, propertyKey, defaultValue);
+ }
+
+// static class RuleKey {
+// private String repositoryKey;
+// private String key;
+//
+// RuleKey(String repositoryKey, String key) {
+// this.repositoryKey = repositoryKey;
+// this.key = key;
+// }
+//
+// public String getRepositoryKey() {
+// return repositoryKey;
+// }
+//
+// public String getKey() {
+// return key;
+// }
+//
+// @Override
+// public boolean equals(Object o) {
+// if (this == o) return true;
+// if (o == null || getClass() != o.getClass()) return false;
+//
+// RuleKey ruleKey = (RuleKey) o;
+//
+// if (!key.equals(ruleKey.key)) return false;
+// if (!repositoryKey.equals(ruleKey.repositoryKey)) return false;
+//
+// return true;
+// }
+//
+// @Override
+// public int hashCode() {
+// int result = repositoryKey.hashCode();
+// result = 31 * result + key.hashCode();
+// return result;
+// }
+//
+// @Override
+// public String toString() {
+// return new StringBuilder().append(repositoryKey).append(":").append(key).toString();
+// }
+// }
+//
+// static class RuleMessages {
+// private String name;
+// private String description;
+//
+// RuleMessages(String name, String description) {
+// this.name = name;
+// this.description = description;
+// }
+// }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/i18n/I18nManagerTest.java b/sonar-core/src/test/java/org/sonar/core/i18n/I18nManagerTest.java
index af57b768738..b2ef460c993 100644
--- a/sonar-core/src/test/java/org/sonar/core/i18n/I18nManagerTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/i18n/I18nManagerTest.java
@@ -20,25 +20,145 @@
package org.sonar.core.i18n;
import com.google.common.collect.Maps;
+import org.hamcrest.CoreMatchers;
import org.hamcrest.core.Is;
+import org.junit.Before;
import org.junit.Test;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Locale;
import java.util.Map;
+import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
+import static org.sonar.core.i18n.I18nManager.BUNDLE_PACKAGE;
public class I18nManagerTest {
+ private I18nManager manager;
+ private ClassLoader coreClassLoader;
+ private ClassLoader sqaleClassLoader;
+
+ @Before
+ public void init() {
+ coreClassLoader = newCoreClassLoader();
+ sqaleClassLoader = newSqaleClassLoader();
+ Map<String, ClassLoader> bundleToClassLoaders = Maps.newHashMap();
+ bundleToClassLoaders.put(BUNDLE_PACKAGE + "core", coreClassLoader);
+ bundleToClassLoaders.put(BUNDLE_PACKAGE + "checkstyle", coreClassLoader);
+ bundleToClassLoaders.put(BUNDLE_PACKAGE + "sqale", sqaleClassLoader);
+ manager = new I18nManager(bundleToClassLoaders);
+ manager.start();
+ }
+
@Test
- public void shouldExtractBundleKey() {
- Map<String,ClassLoader> bundleToClassLoaders = Maps.newHashMap();
- bundleToClassLoaders.put("core", getClass().getClassLoader());
- bundleToClassLoaders.put("checkstyle", getClass().getClassLoader());
- bundleToClassLoaders.put("sqale", getClass().getClassLoader());
+ public void shouldExtractPluginFromKey() {
+ Map<String, ClassLoader> bundleToClassLoaders = Maps.newHashMap();
+ bundleToClassLoaders.put(BUNDLE_PACKAGE + "core", getClass().getClassLoader());
+ bundleToClassLoaders.put(BUNDLE_PACKAGE + "checkstyle", getClass().getClassLoader());
+ bundleToClassLoaders.put(BUNDLE_PACKAGE + "sqale", getClass().getClassLoader());
I18nManager i18n = new I18nManager(bundleToClassLoaders);
+ i18n.start();
+
+ assertThat(i18n.extractBundleFromKey("by"), Is.is(BUNDLE_PACKAGE + "core"));
+ assertThat(i18n.extractBundleFromKey("violations_drilldown.page"), Is.is(BUNDLE_PACKAGE + "core"));
+ assertThat(i18n.extractBundleFromKey("checkstyle.rule1.name"), Is.is(BUNDLE_PACKAGE + "checkstyle"));
+ assertThat(i18n.extractBundleFromKey("sqale.console.page"), Is.is(BUNDLE_PACKAGE + "sqale"));
+ }
+
+ @Test
+ public void shouldFindKeysInEnglishLanguagePack() {
+ assertThat(manager.message(Locale.ENGLISH, "checkstyle.rule1.name", null), Is.is("Rule one"));
+ assertThat(manager.message(Locale.ENGLISH, "by", null), Is.is("By"));
+ assertThat(manager.message(Locale.ENGLISH, "sqale.page", null), Is.is("Sqale page title"));
+
+ assertThat(manager.message(Locale.FRENCH, "checkstyle.rule1.name", null), Is.is("Rule un"));
+ assertThat(manager.message(Locale.FRENCH, "by", null), Is.is("Par"));
+ assertThat(manager.message(Locale.FRENCH, "sqale.page", null), Is.is("Titre de la page Sqale"));
+ }
+
+ @Test
+ public void shouldUseDefaultLocale() {
+ assertThat(manager.message(Locale.CHINA, "checkstyle.rule1.name", null), Is.is("Rule one"));
+ assertThat(manager.message(Locale.CHINA, "by", null), Is.is("By"));
+ assertThat(manager.message(Locale.CHINA, "sqale.page", null), Is.is("Sqale page title"));
+ }
+
+ @Test
+ public void shouldUseLanguagePack() {
+ assertThat(manager.message(Locale.FRENCH, "checkstyle.rule1.name", null), Is.is("Rule un"));
+ assertThat(manager.message(Locale.FRENCH, "by", null), Is.is("Par"));
+ assertThat(manager.message(Locale.FRENCH, "sqale.page", null), Is.is("Titre de la page Sqale"));
+ }
+
+ @Test
+ public void shouldReturnDefaultValueIfMissingKey() {
+ assertThat(manager.message(Locale.ENGLISH, "foo.unknown", "default"), Is.is("default"));
+ assertThat(manager.message(Locale.FRENCH, "foo.unknown", "default"), Is.is("default"));
+ }
+
+ @Test
+ public void shouldAcceptEmptyLabels() {
+ assertThat(manager.message(Locale.ENGLISH, "empty", "default"), Is.is(""));
+ assertThat(manager.message(Locale.FRENCH, "empty", "default"), Is.is(""));
+ }
+
+ @Test
+ public void shouldFormatMessageWithParameters() {
+ assertThat(manager.message(Locale.ENGLISH, "with.parameters", null, "one", "two"), Is.is("First is one and second is two"));
+ }
+
+ @Test
+ public void shouldUseDefaultLocaleIfMissingValueInLocalizedBundle() {
+ assertThat(manager.message(Locale.FRENCH, "only.in.english", null), Is.is("Missing in French bundle"));
+ assertThat(manager.message(Locale.CHINA, "only.in.english", null), Is.is("Missing in French bundle"));
+ }
+
+ @Test
+ public void shouldGetClassLoaderByProperty() {
+ assertThat(manager.getClassLoaderForProperty("foo.unknown"), nullValue());
+ assertThat(manager.getClassLoaderForProperty("by"), Is.is(coreClassLoader));
+ assertThat(manager.getClassLoaderForProperty("sqale.page"), Is.is(sqaleClassLoader));
+ }
+
+ @Test
+ public void shouldFindEnglishFile() {
+ String html = manager.messageFromFile(Locale.ENGLISH, "ArchitectureRule.html", "checkstyle.rule1.name" /* any property in the same bundle */);
+ assertThat(html, Is.is("This is the architecture rule"));
+ }
+
+ @Test
+ public void shouldNotFindFile() {
+ String html = manager.messageFromFile(Locale.ENGLISH, "UnknownRule.html", "checkstyle.rule1.name" /* any property in the same bundle */);
+ assertThat(html, nullValue());
+ }
+
+ @Test
+ public void shouldFindFrenchFile() {
+ String html = manager.messageFromFile(Locale.FRENCH, "ArchitectureRule.html", "checkstyle.rule1.name" /* any property in the same bundle */);
+ assertThat(html, Is.is("Règle d'architecture"));
+ }
+
+ @Test
+ public void shouldNotFindMissingLocale() {
+ String html = manager.messageFromFile(Locale.CHINA, "ArchitectureRule.html", "checkstyle.rule1.name" /* any property in the same bundle */);
+ assertThat(html, nullValue());
+ }
+
+
+ private URLClassLoader newSqaleClassLoader() {
+ return newClassLoader("/org/sonar/core/i18n/sqalePlugin/");
+ }
+
+ private URLClassLoader newCoreClassLoader() {
+ return newClassLoader("/org/sonar/core/i18n/englishPack/", "/org/sonar/core/i18n/frenchPack/");
+ }
- assertThat(i18n.keyToBundle("by"), Is.is("core"));
- assertThat(i18n.keyToBundle("violations_drilldown.page"), Is.is("core"));
- assertThat(i18n.keyToBundle("checkstyle.rule1.name"), Is.is("checkstyle"));
- assertThat(i18n.keyToBundle("sqale.console.page"), Is.is("sqale"));
+ private URLClassLoader newClassLoader(String... resourcePaths) {
+ URL[] urls = new URL[resourcePaths.length];
+ for (int index = 0; index < resourcePaths.length; index++) {
+ urls[index] = getClass().getResource(resourcePaths[index]);
+ }
+ return new URLClassLoader(urls);
}
}
diff --git a/sonar-core/src/test/java/org/sonar/core/i18n/RuleI18nManagerTest.java b/sonar-core/src/test/java/org/sonar/core/i18n/RuleI18nManagerTest.java
new file mode 100644
index 00000000000..17608330075
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/i18n/RuleI18nManagerTest.java
@@ -0,0 +1,90 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.core.i18n;
+
+import org.hamcrest.core.Is;
+import org.junit.Test;
+
+import java.util.Locale;
+
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.isNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+public class RuleI18nManagerTest {
+ @Test
+ public void shouldGetName() {
+ I18nManager i18n = mock(I18nManager.class);
+ RuleI18nManager ruleI18n = new RuleI18nManager(i18n);
+
+ ruleI18n.getName("checkstyle", "com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck", Locale.ENGLISH);
+
+ String propertyKey = "rule.checkstyle.com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck.name";
+ verify(i18n).message(Locale.ENGLISH, propertyKey, "com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck" /* default value is the key */);
+ verifyNoMoreInteractions(i18n);
+ }
+
+ @Test
+ public void shouldGetParamDescription() {
+ I18nManager i18n = mock(I18nManager.class);
+ RuleI18nManager ruleI18n = new RuleI18nManager(i18n);
+
+ ruleI18n.getParamDescription("checkstyle", "com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck", "pattern", Locale.ENGLISH);
+
+ String propertyKey = "rule.checkstyle.com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck.param.pattern";
+ verify(i18n).message(Locale.ENGLISH, propertyKey, "" /* default value must be blank */);
+ verifyNoMoreInteractions(i18n);
+ }
+
+ @Test
+ public void shouldGetDescriptionFromFile() {
+ I18nManager i18n = mock(I18nManager.class);
+ RuleI18nManager ruleI18n = new RuleI18nManager(i18n);
+
+ ruleI18n.getDescription("checkstyle", "com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck", Locale.ENGLISH);
+
+ String propertyKeyForName = "rule.checkstyle.com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck.name";
+ verify(i18n).messageFromFile(Locale.ENGLISH, "com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck.html", propertyKeyForName);
+ verifyNoMoreInteractions(i18n);
+ }
+
+ @Test
+ public void shoudlReturnBlankIfMissingDescription() {
+ I18nManager i18n = mock(I18nManager.class);
+ RuleI18nManager ruleI18n = new RuleI18nManager(i18n);
+
+ assertThat(ruleI18n.getDescription("checkstyle", "com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck", Locale.ENGLISH), Is.is(""));
+ }
+
+ @Test
+ public void shouldUseEnglishIfMissingLocale() {
+ I18nManager i18n = mock(I18nManager.class);
+ RuleI18nManager ruleI18n = new RuleI18nManager(i18n);
+
+ ruleI18n.getDescription("checkstyle", "com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck", Locale.FRENCH);
+
+ String propertyKeyForName = "rule.checkstyle.com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck.name";
+ verify(i18n).messageFromFile(Locale.FRENCH, "com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck.html", propertyKeyForName);
+ verify(i18n).messageFromFile(Locale.ENGLISH, "com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck.html", propertyKeyForName);
+ verifyNoMoreInteractions(i18n);
+ }
+}
diff --git a/sonar-core/src/test/resources/org/sonar/core/i18n/englishPack/org/sonar/i18n/checkstyle.properties b/sonar-core/src/test/resources/org/sonar/core/i18n/englishPack/org/sonar/i18n/checkstyle.properties
new file mode 100644
index 00000000000..10fa9295c44
--- /dev/null
+++ b/sonar-core/src/test/resources/org/sonar/core/i18n/englishPack/org/sonar/i18n/checkstyle.properties
@@ -0,0 +1 @@
+checkstyle.rule1.name=Rule one
diff --git a/sonar-core/src/test/resources/org/sonar/core/i18n/englishPack/org/sonar/i18n/checkstyle/ArchitectureRule.html b/sonar-core/src/test/resources/org/sonar/core/i18n/englishPack/org/sonar/i18n/checkstyle/ArchitectureRule.html
new file mode 100644
index 00000000000..a7cad9049d7
--- /dev/null
+++ b/sonar-core/src/test/resources/org/sonar/core/i18n/englishPack/org/sonar/i18n/checkstyle/ArchitectureRule.html
@@ -0,0 +1 @@
+This is the architecture rule \ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/i18n/englishPack/org/sonar/i18n/core.properties b/sonar-core/src/test/resources/org/sonar/core/i18n/englishPack/org/sonar/i18n/core.properties
new file mode 100644
index 00000000000..de205d651cb
--- /dev/null
+++ b/sonar-core/src/test/resources/org/sonar/core/i18n/englishPack/org/sonar/i18n/core.properties
@@ -0,0 +1,4 @@
+by=By
+empty=
+with.parameters=First is {0} and second is {1}
+only.in.english=Missing in French bundle \ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/i18n/frenchPack/org/sonar/i18n/checkstyle_fr.properties b/sonar-core/src/test/resources/org/sonar/core/i18n/frenchPack/org/sonar/i18n/checkstyle_fr.properties
new file mode 100644
index 00000000000..b2fc8f9651f
--- /dev/null
+++ b/sonar-core/src/test/resources/org/sonar/core/i18n/frenchPack/org/sonar/i18n/checkstyle_fr.properties
@@ -0,0 +1 @@
+checkstyle.rule1.name=Rule un \ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/i18n/frenchPack/org/sonar/i18n/checkstyle_fr/ArchitectureRule.html b/sonar-core/src/test/resources/org/sonar/core/i18n/frenchPack/org/sonar/i18n/checkstyle_fr/ArchitectureRule.html
new file mode 100644
index 00000000000..9b12ae071ce
--- /dev/null
+++ b/sonar-core/src/test/resources/org/sonar/core/i18n/frenchPack/org/sonar/i18n/checkstyle_fr/ArchitectureRule.html
@@ -0,0 +1 @@
+Règle d'architecture \ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/i18n/frenchPack/org/sonar/i18n/core_fr.properties b/sonar-core/src/test/resources/org/sonar/core/i18n/frenchPack/org/sonar/i18n/core_fr.properties
new file mode 100644
index 00000000000..e9ced4039ae
--- /dev/null
+++ b/sonar-core/src/test/resources/org/sonar/core/i18n/frenchPack/org/sonar/i18n/core_fr.properties
@@ -0,0 +1,2 @@
+by=Par
+empty= \ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/i18n/sqalePlugin/org/sonar/i18n/sqale.properties b/sonar-core/src/test/resources/org/sonar/core/i18n/sqalePlugin/org/sonar/i18n/sqale.properties
new file mode 100644
index 00000000000..a8ea9c0553e
--- /dev/null
+++ b/sonar-core/src/test/resources/org/sonar/core/i18n/sqalePlugin/org/sonar/i18n/sqale.properties
@@ -0,0 +1 @@
+sqale.page=Sqale page title \ No newline at end of file
diff --git a/sonar-core/src/test/resources/org/sonar/core/i18n/sqalePlugin/org/sonar/i18n/sqale_fr.properties b/sonar-core/src/test/resources/org/sonar/core/i18n/sqalePlugin/org/sonar/i18n/sqale_fr.properties
new file mode 100644
index 00000000000..471d015a11a
--- /dev/null
+++ b/sonar-core/src/test/resources/org/sonar/core/i18n/sqalePlugin/org/sonar/i18n/sqale_fr.properties
@@ -0,0 +1 @@
+sqale.page=Titre de la page Sqale \ No newline at end of file
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/i18n/LanguagePack.java b/sonar-plugin-api/src/main/java/org/sonar/api/i18n/LanguagePack.java
deleted file mode 100644
index 5b167fd6cad..00000000000
--- a/sonar-plugin-api/src/main/java/org/sonar/api/i18n/LanguagePack.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.api.i18n;
-
-import org.sonar.api.BatchExtension;
-import org.sonar.api.ServerExtension;
-
-import java.util.Collection;
-import java.util.Locale;
-
-/**
- *
- * EXPERIMENTAL - this feature will be fully implemented in version 2.10
- *
- *
- * @since 2.9
- */
-public abstract class LanguagePack implements ServerExtension, BatchExtension {
-
- @Override
- public String toString() {
- return new StringBuilder("Language Pack (")
- .append(getPluginKeys().toString())
- .append(getLocales().toString())
- .append(')').toString();
- }
-
- /**
- * @return the pluginKeys
- */
- public abstract Collection<String> getPluginKeys();
-
- /**
- * @return the locales
- */
- public abstract Collection<Locale> getLocales();
-}
diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
index 7b5ef8fac7f..6835c7f69dd 100644
--- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
+++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
@@ -41,6 +41,8 @@ import org.sonar.core.components.DefaultMetricFinder;
import org.sonar.core.components.DefaultModelFinder;
import org.sonar.core.components.DefaultRuleFinder;
import org.sonar.core.components.DefaultUserFinder;
+import org.sonar.core.i18n.I18nManager;
+import org.sonar.core.i18n.RuleI18nManager;
import org.sonar.core.notifications.DefaultNotificationManager;
import org.sonar.jpa.dao.DaoFacade;
import org.sonar.jpa.dao.MeasuresDao;
@@ -187,6 +189,8 @@ public final class Platform {
servicesContainer.as(Characteristics.CACHE).addComponent(RulesConsole.class);
servicesContainer.as(Characteristics.CACHE).addComponent(JRubyI18n.class);
servicesContainer.as(Characteristics.CACHE).addComponent(DefaultUserFinder.class);
+ servicesContainer.as(Characteristics.CACHE).addComponent(I18nManager.class);
+ servicesContainer.as(Characteristics.CACHE).addComponent(RuleI18nManager.class);
// Notifications
servicesContainer.as(Characteristics.CACHE).addComponent(NotificationService.class);
diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
index 473bc679f65..e0ea1b1004f 100644
--- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
+++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
@@ -302,13 +302,34 @@ public final class JRubyFacade {
return component;
}
- public String getI18nMessage(String rubyLocale, String key, String defaultValue, Object... parameters) {
+ public String getMessage(String rubyLocale, String key, String defaultValue, Object... parameters) {
if (i18n == null) {
i18n = getContainer().getComponent(JRubyI18n.class);
}
return i18n.message(rubyLocale, key, defaultValue, parameters);
}
+ public String getRuleName(String rubyLocale, String repositoryKey, String key) {
+ if (i18n == null) {
+ i18n = getContainer().getComponent(JRubyI18n.class);
+ }
+ return i18n.getRuleName(rubyLocale, repositoryKey, key);
+ }
+
+ public String getRuleDescription(String rubyLocale, String repositoryKey, String key) {
+ if (i18n == null) {
+ i18n = getContainer().getComponent(JRubyI18n.class);
+ }
+ return i18n.getRuleDescription(rubyLocale, repositoryKey, key);
+ }
+
+ public String getRuleParamDescription(String rubyLocale, String repositoryKey, String key, String paramKey) {
+ if (i18n == null) {
+ i18n = getContainer().getComponent(JRubyI18n.class);
+ }
+ return i18n.getRuleParamDescription(rubyLocale, repositoryKey, key, paramKey);
+ }
+
public ReviewsNotificationManager getReviewsNotificationManager() {
return getContainer().getComponent(ReviewsNotificationManager.class);
}
diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyI18n.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyI18n.java
index 72a1d0e39a0..e7d9804e3f3 100644
--- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyI18n.java
+++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyI18n.java
@@ -23,6 +23,7 @@ import com.google.common.collect.Maps;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.ServerComponent;
import org.sonar.api.i18n.I18n;
+import org.sonar.core.i18n.RuleI18nManager;
import java.util.Locale;
import java.util.Map;
@@ -33,10 +34,12 @@ import java.util.Map;
public final class JRubyI18n implements ServerComponent {
private I18n i18n;
- private Map<String,Locale> localesByRubyKey = Maps.newHashMap();
+ private Map<String, Locale> localesByRubyKey = Maps.newHashMap();
+ private RuleI18nManager ruleI18nManager;
- public JRubyI18n(I18n i18n) {
+ public JRubyI18n(I18n i18n, RuleI18nManager ruleI18nManager) {
this.i18n = i18n;
+ this.ruleI18nManager = ruleI18nManager;
}
Locale getLocale(String rubyKey) {
@@ -55,7 +58,7 @@ public final class JRubyI18n implements ServerComponent {
static Locale toLocale(String rubyKey) {
Locale locale;
String[] fields = StringUtils.split(rubyKey, "-");
- if (fields.length==1) {
+ if (fields.length == 1) {
locale = new Locale(fields[0]);
} else {
locale = new Locale(fields[0], fields[1]);
@@ -66,4 +69,16 @@ public final class JRubyI18n implements ServerComponent {
public String message(String rubyLocale, String key, String defaultValue, Object... parameters) {
return i18n.message(getLocale(rubyLocale), key, defaultValue, parameters);
}
+
+ public String getRuleName(String rubyLocale, String repositoryKey, String key) {
+ return ruleI18nManager.getName(repositoryKey, key, toLocale(rubyLocale));
+ }
+
+ public String getRuleDescription(String rubyLocale, String repositoryKey, String key) {
+ return ruleI18nManager.getDescription(repositoryKey, key, toLocale(rubyLocale));
+ }
+
+ public String getRuleParamDescription(String rubyLocale, String repositoryKey, String ruleKey, String paramKey) {
+ return ruleI18nManager.getParamDescription(repositoryKey, ruleKey, paramKey, toLocale(rubyLocale));
+ }
}
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb
index 0c06549b0fd..9e8ff5e686b 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb
@@ -97,12 +97,7 @@ class ApplicationController < ActionController::Base
# i18n
def message(key, options={})
- default = options[:default]
- params = options[:params]
- if params.nil?
- params=[]
- end
- Java::OrgSonarServerUi::JRubyFacade.getInstance().getI18nMessage(I18n.locale, key, default, params.to_java)
+ Api::Utils.message(key, options)
end
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/api/utils.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/api/utils.rb
index 7ff974147af..8f5d33436a6 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/models/api/utils.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/api/utils.rb
@@ -67,6 +67,6 @@ class Api::Utils
if params.nil?
params=[]
end
- Java::OrgSonarServerUi::JRubyFacade.getInstance().getI18nMessage(I18n.locale, key, default, params.to_java)
+ Java::OrgSonarServerUi::JRubyFacade.getInstance().getMessage(I18n.locale, key, default, params.to_java)
end
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/rule.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/rule.rb
index 0fbb2e78823..6724dcbeac8 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/models/rule.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/rule.rb
@@ -26,6 +26,10 @@ class Rule < ActiveRecord::Base
has_many :active_rules
belongs_to :parent, :class_name => 'Rule', :foreign_key => 'parent_id'
+ def repository_key
+ plugin_name
+ end
+
def parameters
rules_parameters
end
@@ -57,41 +61,31 @@ class Rule < ActiveRecord::Base
def <=>(rule)
name<=>rule.name
end
-
- def name(translate=true)
- default_string = read_attribute(:name)
- return default_string unless translate
-
- rule_plugin_name = read_attribute(:plugin_name)
- rule_plugin_rule_key = read_attribute(:plugin_rule_key)
-
- return nil if (rule_plugin_name.nil? or rule_plugin_rule_key.nil?)
-
- i18n_key = 'rule.' + rule_plugin_name + '.' + rule_plugin_rule_key + '.name'
- result = Api::Utils.message(i18n_key, :default => default_string)
- result
+
+ def name
+ @l10n_name ||=
+ begin
+ result = Java::OrgSonarServerUi::JRubyFacade.getInstance().getRuleName(I18n.locale, repository_key, plugin_rule_key)
+ result = read_attribute(:name) unless result
+ result
+ end
end
-
+
def name=(value)
- write_attribute(:name, value)
+ write_attribute(:name, value)
end
-
- def description(translate=true)
- default_string = read_attribute(:description)
- return default_string unless translate
-
- rule_plugin_name = read_attribute(:plugin_name)
- rule_plugin_rule_key = read_attribute(:plugin_rule_key)
-
- return nil if (rule_plugin_name.nil? or rule_plugin_rule_key.nil?)
-
- i18n_key = 'rule.' + rule_plugin_name + '.' + rule_plugin_rule_key + '.description'
- result = Api::Utils.message(i18n_key, :default => default_string)
- result
+
+ def description
+ @l10n_description ||=
+ begin
+ result = Java::OrgSonarServerUi::JRubyFacade.getInstance().getRuleDescription(I18n.locale, repository_key, plugin_rule_key)
+ result = read_attribute(:description) unless result
+ result
+ end
end
def description=(value)
- write_attribute(:description, value)
+ write_attribute(:description, value)
end
def config_key
@@ -142,7 +136,7 @@ class Rule < ActiveRecord::Base
else
json['priority'] = priority_text
end
- json['params'] = parameters.collect{|parameter| parameter.to_hash_json(active_rule)} unless parameters.empty?
+ json['params'] = parameters.collect { |parameter| parameter.to_hash_json(active_rule) } unless parameters.empty?
json
end
@@ -152,7 +146,7 @@ class Rule < ActiveRecord::Base
xml.key(key)
xml.config_key(config_key)
xml.plugin(plugin_name)
- xml.description {xml.cdata!(description)}
+ xml.description { xml.cdata!(description) }
active_rule = nil
if profile
active_rule = profile.active_by_rule_id(id)
@@ -197,10 +191,10 @@ class Rule < ActiveRecord::Base
if remove_blank(options[:plugins])
plugins = options[:plugins]
unless options[:language].blank?
- plugins = plugins & java_facade.getRuleRepositoriesByLanguage(options[:language]).collect{ |repo| repo.getKey() }
+ plugins = plugins & java_facade.getRuleRepositoriesByLanguage(options[:language]).collect { |repo| repo.getKey() }
end
elsif !options[:language].blank?
- plugins = java_facade.getRuleRepositoriesByLanguage(options[:language]).collect{ |repo| repo.getKey() }
+ plugins = java_facade.getRuleRepositoriesByLanguage(options[:language]).collect { |repo| repo.getKey() }
end
if plugins
@@ -220,8 +214,7 @@ class Rule < ActiveRecord::Base
end
includes=(options[:include_parameters] ? :rules_parameters : nil)
- rules = Rule.find(:all, :order => 'rules.name', :include => includes,
- :conditions => [conditions.join(" AND "), values])
+ rules = Rule.find(:all, :include => includes, :conditions => [conditions.join(" AND "), values]).sort_by { |rule| rule.name }
filter(rules, options)
end
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/rules_parameter.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/rules_parameter.rb
index efe684b8c58..360b4f988af 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/models/rules_parameter.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/rules_parameter.rb
@@ -1,22 +1,22 @@
- #
- # Sonar, entreprise quality control tool.
- # Copyright (C) 2008-2011 SonarSource
- # mailto:contact AT sonarsource DOT com
- #
- # Sonar is free software; you can redistribute it and/or
- # modify it under the terms of the GNU Lesser General Public
- # License as published by the Free Software Foundation; either
- # version 3 of the License, or (at your option) any later version.
- #
- # Sonar is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- # Lesser General Public License for more details.
- #
- # You should have received a copy of the GNU Lesser General Public
- # License along with {library}; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- #
+#
+# Sonar, entreprise quality control tool.
+# Copyright (C) 2008-2011 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# Sonar is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 3 of the License, or (at your option) any later version.
+#
+# Sonar is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with {library}; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+#
class RulesParameter < ActiveRecord::Base
validates_presence_of :name, :param_type
@@ -29,35 +29,30 @@ class RulesParameter < ActiveRecord::Base
PARAM_TYPE_INTEGER_LIST = "i{}";
PARAM_TYPE_BOOLEAN = "b";
PARAM_TYPE_REGEXP = "r";
-
+
belongs_to :rule
def is_set_type
- return param_type.at(1) == "[" && param_type.ends_with?( "]" )
+ return param_type.at(1) == "[" && param_type.ends_with?("]")
end
-
+
def get_allowed_tokens
- return param_type[2,param_type.length-3].split( "," )
+ return param_type[2, param_type.length-3].split(",")
end
- def description(translate=true)
- default_string = read_attribute(:description)
- return default_string unless translate
-
- rule_plugin_name = rule.plugin_name
- rule_plugin_rule_key = rule.plugin_rule_key
-
- return nil if (rule_plugin_name.nil? or rule_plugin_rule_key.nil?)
-
- i18n_key = 'rule.' + rule_plugin_name + '.' + rule_plugin_rule_key + '.param.' + read_attribute(:name)
- result = Api::Utils.message(i18n_key, :default => default_string)
- result
+ def description
+ @l10n_description ||=
+ begin
+ result = Java::OrgSonarServerUi::JRubyFacade.getInstance().getRuleParamDescription(I18n.locale, rule.repository_key, rule.plugin_rule_key, name())
+ result = read_attribute(:description) unless result
+ result
+ end
end
-
+
def description=(value)
- write_attribute(:description, value)
+ write_attribute(:description, value)
end
-
+
def readable_param_type
return "String" if param_type == PARAM_TYPE_STRING
return "Set of string (, as delimiter)" if param_type == PARAM_TYPE_STRING_LIST
@@ -67,55 +62,55 @@ class RulesParameter < ActiveRecord::Base
return "Regular expression" if param_type == PARAM_TYPE_REGEXP
return "Set of values (, as delimiter)" if is_set_type
end
-
+
def input_box_size
return 15 if param_type == PARAM_TYPE_STRING or param_type == PARAM_TYPE_STRING_LIST or param_type == PARAM_TYPE_REGEXP
return 8 if param_type == PARAM_TYPE_INTEGER or param_type == PARAM_TYPE_INTEGER_LIST
return 4 if param_type == PARAM_TYPE_BOOLEAN
if is_set_type
- size = ( param_type.length / 2 ).to_i
+ size = (param_type.length / 2).to_i
size = 64 if size > 64
return size
end
end
def validate_value(attribute, errors, value)
- return if attribute.nil? or attribute.length == 0
- if is_set_type
- provided_tokens = attribute.split( "," )
- allowed_tokens = get_allowed_tokens
- provided_tokens.each do |provided_token|
- if !allowed_tokens.include?(provided_token)
- errors.add( "#{value}", "Invalid value '" + provided_token + "'. Must be one of : " + allowed_tokens.join(", ") )
- end
- end
- elsif param_type == RulesParameter::PARAM_TYPE_INTEGER
- begin
- Kernel.Integer(attribute)
- rescue
- errors.add( "#{value}", "Invalid value '" + attribute + "'. Must be an integer." )
- end
- elsif param_type == RulesParameter::PARAM_TYPE_INTEGER_LIST
- provided_numbers = attribute.split( "," )
- provided_numbers.each do |provided_number|
- begin
- Kernel.Integer(provided_number)
- rescue
- errors.add("#{value}", "Invalid value '" + provided_number + "'. Must be an integer." )
- return
- end
- end
- elsif param_type == RulesParameter::PARAM_TYPE_BOOLEAN
- if attribute != "true" && attribute != "false"
- errors.add( "#{value}", "Invalid value '" + attribute + "'. Must be one of : true,false" )
- end
- elsif param_type == RulesParameter::PARAM_TYPE_REGEXP
- begin
- Regexp.new(attribute)
- rescue
- errors.add( "#{value}", "Invalid regular expression '" + attribute + "'.")
- end
- end
+ return if attribute.nil? or attribute.length == 0
+ if is_set_type
+ provided_tokens = attribute.split(",")
+ allowed_tokens = get_allowed_tokens
+ provided_tokens.each do |provided_token|
+ if !allowed_tokens.include?(provided_token)
+ errors.add("#{value}", "Invalid value '" + provided_token + "'. Must be one of : " + allowed_tokens.join(", "))
+ end
+ end
+ elsif param_type == RulesParameter::PARAM_TYPE_INTEGER
+ begin
+ Kernel.Integer(attribute)
+ rescue
+ errors.add("#{value}", "Invalid value '" + attribute + "'. Must be an integer.")
+ end
+ elsif param_type == RulesParameter::PARAM_TYPE_INTEGER_LIST
+ provided_numbers = attribute.split(",")
+ provided_numbers.each do |provided_number|
+ begin
+ Kernel.Integer(provided_number)
+ rescue
+ errors.add("#{value}", "Invalid value '" + provided_number + "'. Must be an integer.")
+ return
+ end
+ end
+ elsif param_type == RulesParameter::PARAM_TYPE_BOOLEAN
+ if attribute != "true" && attribute != "false"
+ errors.add("#{value}", "Invalid value '" + attribute + "'. Must be one of : true,false")
+ end
+ elsif param_type == RulesParameter::PARAM_TYPE_REGEXP
+ begin
+ Regexp.new(attribute)
+ rescue
+ errors.add("#{value}", "Invalid regular expression '" + attribute + "'.")
+ end
+ end
end
def to_hash_json(active_rule)
@@ -130,7 +125,7 @@ class RulesParameter < ActiveRecord::Base
def to_xml(active_rule, xml)
xml.param do
xml.name(name)
- xml.description {xml.cdata!(description)}
+ xml.description { xml.cdata!(description) }
if active_rule
active_parameter = active_rule.active_param_by_param_id(id)
xml.value(active_parameter.value) if active_parameter
diff --git a/sonar-server/src/test/java/org/sonar/server/ui/JRubyI18nTest.java b/sonar-server/src/test/java/org/sonar/server/ui/JRubyI18nTest.java
index fe150e2b754..c03934a3940 100644
--- a/sonar-server/src/test/java/org/sonar/server/ui/JRubyI18nTest.java
+++ b/sonar-server/src/test/java/org/sonar/server/ui/JRubyI18nTest.java
@@ -22,6 +22,7 @@ package org.sonar.server.ui;
import org.hamcrest.core.Is;
import org.junit.Test;
import org.sonar.api.i18n.I18n;
+import org.sonar.core.i18n.RuleI18nManager;
import java.util.Locale;
@@ -39,7 +40,7 @@ public class JRubyI18nTest {
@Test
public void shouldCacheLocales() {
- JRubyI18n i18n = new JRubyI18n(mock(I18n.class));
+ JRubyI18n i18n = new JRubyI18n(mock(I18n.class), mock(RuleI18nManager.class));
assertThat(i18n.getLocalesByRubyKey().size(), Is.is(0));
i18n.getLocale("fr");