aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/sonar-core-plugin
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2011-06-21 18:06:11 +0200
committerSimon Brandhof <simon.brandhof@gmail.com>2011-06-21 18:13:15 +0200
commit88e6ec0762914d72dd42fb8be3b1ce0fb1e447c2 (patch)
tree022f19acf8e838dd59a6521e40931125331280b0 /plugins/sonar-core-plugin
parentfe7eb1d29d9775f3da0d8b98ada1a5e927555cab (diff)
downloadsonarqube-88e6ec0762914d72dd42fb8be3b1ce0fb1e447c2.tar.gz
sonarqube-88e6ec0762914d72dd42fb8be3b1ce0fb1e447c2.zip
SONAR-2497 Merge i18n plugin with core
Diffstat (limited to 'plugins/sonar-core-plugin')
-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.java203
-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/FrenchLanguagePack.java39
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/I18nManagerTest.java122
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/QuebecLanguagePack.java39
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/FrenchPlugin.jarbin0 -> 1058 bytes
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/FrenchPlugin/META-INF/MANIFEST.MF3
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/FrenchPlugin/org/sonar/i18n/test_fr.properties4
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/QuebecPlugin.jarbin0 -> 1051 bytes
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/QuebecPlugin/META-INF/MANIFEST.MF3
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties2
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/StandardPlugin.jarbin0 -> 1068 bytes
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/StandardPlugin/META-INF/MANIFEST.MF3
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/StandardPlugin/org/sonar/i18n/test.properties4
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/logback-test.xml17
16 files changed, 489 insertions, 0 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 f3b72b9e0c1..87d1e406442 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,6 +34,7 @@ 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.*;
@@ -227,6 +228,9 @@ public class CorePlugin extends SonarPlugin {
extensions.add(NewCoverageFileAnalyzer.class);
extensions.add(NewCoverageAggregator.class);
+ // i18n
+ extensions.add(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
new file mode 100644
index 00000000000..8438b706622
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/I18nManager.java
@@ -0,0 +1,203 @@
+/*
+ * 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.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.EnumerationUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchExtension;
+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.Logs;
+import org.sonar.api.utils.SonarException;
+
+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, BatchExtension {
+
+ 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> keys = Maps.newHashMap();
+ private Properties unknownKeys = new Properties();
+ private BundleClassLoader bundleClassLoader = new BundleClassLoader();
+
+ 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) {
+ Logs.INFO.info("Loading i18n bundles");
+ Set<URI> alreadyLoadedResources = Sets.newHashSet();
+ for (InstalledPlugin plugin : installedPlugins) {
+ searchAndStoreBundleNames(plugin.key, plugin.classloader, alreadyLoadedResources);
+ }
+ for (LanguagePack pack : languagePacks) {
+ addLanguagePack(pack);
+ }
+ }
+
+ private 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);
+ }
+ }
+ }
+
+ private String buildBundleBaseName(String pluginKey) {
+ return packagePathToSearchIn + "/" + pluginKey;
+ }
+
+ private 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.warn("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 (keys.containsKey(key)) {
+ LOG.warn("DUPLICATE KEY : Key '{}' defined in bundle '{}' is already defined in bundle '{}'. It is ignored.", new Object[]{
+ key, bundleBaseName, keys.get(key)});
+ } else {
+ keys.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 result = defaultText;
+ try {
+ String bundleBaseName = keys.get(key);
+ if (bundleBaseName == null) {
+ LOG.warn("UNKNOWN KEY : Key '{}' not found in any bundle. Default value '{}' is returned.", key, defaultText);
+ unknownKeys.put(key, defaultText);
+ } else {
+ try {
+ ResourceBundle bundle = ResourceBundle.getBundle(bundleBaseName, locale, bundleClassLoader);
+
+ String value = bundle.getString(key);
+ if ("".equals(value)) {
+ LOG.warn("VOID KEY : Key '{}' (from bundle '{}') returns a void value. Default value '{}' is returned.", new Object[]{key,
+ bundleBaseName, defaultText});
+ } else {
+ result = value;
+ }
+ } catch (MissingResourceException e) {
+ LOG.warn("BUNDLE NOT LOADED : Failed loading bundle {} from classloader {}. Default value '{}' is returned.", new Object[]{
+ bundleBaseName, bundleClassLoader, defaultText});
+ }
+ }
+ } catch (Exception e) {
+ LOG.error("Exception when retrieving I18n string.", e);
+ }
+
+ if (objects.length > 0) {
+ LOG.debug("Translation : {}, {}, {}, {}", new String[]{locale.toString(), key, defaultText, Arrays.deepToString(objects)});
+ return MessageFormat.format(result, objects);
+ } else {
+ return result;
+ }
+ }
+
+ /**
+ * @return the unknownKeys
+ */
+ public Properties getUnknownKeys() {
+ return unknownKeys;
+ }
+
+
+ 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);
+ }
+
+ @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
new file mode 100644
index 00000000000..abe4b3cf0de
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/InstalledPlugin.java
@@ -0,0 +1,46 @@
+/*
+ * 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/FrenchLanguagePack.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/FrenchLanguagePack.java
new file mode 100644
index 00000000000..e781f5a891f
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/FrenchLanguagePack.java
@@ -0,0 +1,39 @@
+/*
+ * 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
new file mode 100644
index 00000000000..380f65efe3d
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/I18nManagerTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.Assert;
+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.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class I18nManagerTest {
+
+ public static String TEST_PLUGIN_CLASS_NAME = "org.sonar.plugins.core.i18n.StandardPlugin";
+ 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", new TestClassLoader(getClass().getClassLoader().getResource("StandardPlugin.jar"))),
+ new InstalledPlugin("fake1", getClass().getClassLoader()),
+ new InstalledPlugin("fake2", getClass().getClassLoader())
+ );
+
+ TestClassLoader frenchPackClassLoader = new TestClassLoader(getClass().getClassLoader().getResource("FrenchPlugin.jar"));
+ LanguagePack frenchPack = (LanguagePack) frenchPackClassLoader.loadClass(FRENCH_PACK_CLASS_NAME).newInstance();
+
+ TestClassLoader quebecPackClassLoader = new TestClassLoader(getClass().getClassLoader().getResource("QuebecPlugin.jar"));
+ LanguagePack quebecPack = (LanguagePack) quebecPackClassLoader.loadClass(QUEBEC_PACK_CLASS_NAME).newInstance();
+
+ manager = new I18nManager(mock(PluginRepository.class), new LanguagePack[]{frenchPack, quebecPack});
+ 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);
+ Assert.assertEquals(1, manager.getUnknownKeys().size());
+ Assert.assertEquals("Default value for Unknown", manager.getUnknownKeys().getProperty("unknown"));
+ }
+
+ 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(TEST_PLUGIN_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
new file mode 100644
index 00000000000..c8ed96a4edb
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/QuebecLanguagePack.java
@@ -0,0 +1,39 @@
+/*
+ * 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/FrenchPlugin.jar b/plugins/sonar-core-plugin/src/test/resources/FrenchPlugin.jar
new file mode 100644
index 00000000000..ab61bd88697
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/FrenchPlugin.jar
Binary files differ
diff --git a/plugins/sonar-core-plugin/src/test/resources/FrenchPlugin/META-INF/MANIFEST.MF b/plugins/sonar-core-plugin/src/test/resources/FrenchPlugin/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..a45029d3531
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/FrenchPlugin/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Created-By: 1.6.0_17 (Apple Inc.)
+
diff --git a/plugins/sonar-core-plugin/src/test/resources/FrenchPlugin/org/sonar/i18n/test_fr.properties b/plugins/sonar-core-plugin/src/test/resources/FrenchPlugin/org/sonar/i18n/test_fr.properties
new file mode 100644
index 00000000000..cc8efa7afc3
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/FrenchPlugin/org/sonar/i18n/test_fr.properties
@@ -0,0 +1,4 @@
+it=Il
+is=fait
+cold=froid
+
diff --git a/plugins/sonar-core-plugin/src/test/resources/QuebecPlugin.jar b/plugins/sonar-core-plugin/src/test/resources/QuebecPlugin.jar
new file mode 100644
index 00000000000..101df34ba6d
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/QuebecPlugin.jar
Binary files differ
diff --git a/plugins/sonar-core-plugin/src/test/resources/QuebecPlugin/META-INF/MANIFEST.MF b/plugins/sonar-core-plugin/src/test/resources/QuebecPlugin/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..a45029d3531
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/QuebecPlugin/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Created-By: 1.6.0_17 (Apple Inc.)
+
diff --git a/plugins/sonar-core-plugin/src/test/resources/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties b/plugins/sonar-core-plugin/src/test/resources/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties
new file mode 100644
index 00000000000..38b5b84ccdf
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties
@@ -0,0 +1,2 @@
+cold=frette
+
diff --git a/plugins/sonar-core-plugin/src/test/resources/StandardPlugin.jar b/plugins/sonar-core-plugin/src/test/resources/StandardPlugin.jar
new file mode 100644
index 00000000000..73deac7e8b6
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/StandardPlugin.jar
Binary files differ
diff --git a/plugins/sonar-core-plugin/src/test/resources/StandardPlugin/META-INF/MANIFEST.MF b/plugins/sonar-core-plugin/src/test/resources/StandardPlugin/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..a45029d3531
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/StandardPlugin/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Created-By: 1.6.0_17 (Apple Inc.)
+
diff --git a/plugins/sonar-core-plugin/src/test/resources/StandardPlugin/org/sonar/i18n/test.properties b/plugins/sonar-core-plugin/src/test/resources/StandardPlugin/org/sonar/i18n/test.properties
new file mode 100644
index 00000000000..88aabfe61a2
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/StandardPlugin/org/sonar/i18n/test.properties
@@ -0,0 +1,4 @@
+it=It
+is=is
+cold=cold
+only.english=Ketchup
diff --git a/plugins/sonar-core-plugin/src/test/resources/logback-test.xml b/plugins/sonar-core-plugin/src/test/resources/logback-test.xml
new file mode 100644
index 00000000000..cdc8866fe3a
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/logback-test.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<configuration>
+ <appender name="STDOUT"
+ class="ch.qos.logback.core.ConsoleAppender">
+ <layout class="ch.qos.logback.classic.PatternLayout">
+ <pattern>
+ %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n
+ </pattern>
+ </layout>
+ </appender>
+
+ <root>
+ <level value="INFO"/>
+ <appender-ref ref="STDOUT"/>
+ </root>
+</configuration> \ No newline at end of file