aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/sonar-core-plugin/src
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/sonar-core-plugin/src')
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/i18n/I18nManager.java162
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/i18n/I18nManagerTest.java40
-rwxr-xr-xplugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin.jarbin767 -> 1113 bytes
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test.properties1
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test/fakerule.html2
-rwxr-xr-x[-rw-r--r--]plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin.jarbin1058 -> 1130 bytes
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr.properties2
-rw-r--r--plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr/fakerule.html2
8 files changed, 169 insertions, 40 deletions
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
index b8a4c4e5472..66cd05dc970 100644
--- 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
@@ -19,6 +19,7 @@
*/
package org.sonar.plugins.core.i18n;
+import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
@@ -36,6 +37,7 @@ import java.util.Set;
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.BatchExtension;
@@ -46,6 +48,7 @@ import org.sonar.api.platform.PluginRepository;
import org.sonar.api.utils.Logs;
import org.sonar.api.utils.SonarException;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@@ -59,6 +62,7 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension
private Map<String, String> keys = Maps.newHashMap();
private Properties unknownKeys = new Properties();
private BundleClassLoader bundleClassLoader = new BundleClassLoader();
+ private List<Locale> registeredLocales = Lists.newArrayList();
public I18nManager(PluginRepository pluginRepository, LanguagePack[] languagePacks) {
this.pluginRepository = pluginRepository;
@@ -73,13 +77,16 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension
doStart(InstalledPlugin.create(pluginRepository));
}
- void doStart(List<InstalledPlugin> installedPlugins) {
+ protected void doStart(List<InstalledPlugin> installedPlugins) {
Logs.INFO.info("Loading i18n bundles");
Set<URI> alreadyLoadedResources = Sets.newHashSet();
LanguagePack englishPack = findEnglishPack();
for (InstalledPlugin plugin : installedPlugins) {
+ // look first in the classloader of the English I18n Plugin of the Sonar platform
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) {
@@ -89,7 +96,7 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension
}
}
- private LanguagePack findEnglishPack() {
+ protected LanguagePack findEnglishPack() {
LanguagePack englishPack = null;
for (LanguagePack pack : languagePacks) {
if (pack.getLocales().contains(Locale.ENGLISH)) {
@@ -103,7 +110,7 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension
return englishPack;
}
- private void addLanguagePack(LanguagePack languagePack) {
+ protected void addLanguagePack(LanguagePack languagePack) {
LOG.debug("Search for bundles in language pack : {}", languagePack);
for (String pluginKey : languagePack.getPluginKeys()) {
String bundleBaseName = buildBundleBaseName(pluginKey);
@@ -113,16 +120,17 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension
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);
}
}
}
- private String buildBundleBaseName(String pluginKey) {
+ protected String buildBundleBaseName(String pluginKey) {
return packagePathToSearchIn + "/" + pluginKey;
}
@SuppressWarnings("unchecked")
- private void searchAndStoreBundleNames(String pluginKey, ClassLoader classloader, Set<URI> alreadyLoadedResources) {
+ protected void searchAndStoreBundleNames(String pluginKey, ClassLoader classloader, Set<URI> alreadyLoadedResources) {
String bundleBaseName = buildBundleBaseName(pluginKey);
String bundleDefaultPropertiesFile = bundleBaseName + ".properties";
try {
@@ -168,61 +176,127 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension
}
public String message(final Locale locale, final String key, final String defaultText, final Object... objects) {
- String result = defaultText;
+ String translatedMessage = defaultText;
try {
- String bundleBaseName = keys.get(key);
- if (bundleBaseName == null) {
- if (result == null) {
- throw new MissingResourceException("UNKNOWN KEY : Key '" + key
- + "' not found in any bundle, and no default value provided. The key is returned.", bundleBaseName, key);
- }
- LOG.warn("UNKNOWN KEY : Key '{}' not found in any bundle. Default value '{}' is returned.", key, defaultText);
- unknownKeys.put(key, defaultText);
+ if (isKeyForRuleDescription(key)) {
+ // Rule descriptions are in HTML files, not in regular bundles
+ translatedMessage = findRuleDescription(locale, key, defaultText);
} else {
- try {
- ResourceBundle bundle = ResourceBundle.getBundle(bundleBaseName, locale, bundleClassLoader);
-
- String value = bundle.getString(key);
- if ("".equals(value)) {
- if (result == null) {
- throw new MissingResourceException("VOID KEY : Key '" + key + "' (from bundle '" + bundleBaseName
- + "') returns a void value.", bundleBaseName, key);
- }
- 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) {
- if (result == null) {
- throw e;
- }
- LOG.warn("BUNDLE NOT LOADED : Failed loading bundle {} from classloader {}. Default value '{}' is returned.", new Object[] {
- bundleBaseName, bundleClassLoader, defaultText });
- }
+ translatedMessage = findStandardMessage(locale, key, defaultText, objects);
}
} catch (MissingResourceException e) {
LOG.warn(e.getMessage());
- if (result == null) {
+ 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 (result == null) {
+ 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 = keys.get(ruleNameKey);
+ if (bundleBaseName == null) {
+ handleMissingBundle(ruleNameKey, defaultText, bundleBaseName);
+ } else {
+ Locale localeToUse = defineLocaleToUse(locale);
+ String htmlFilePath = computeHtmlFilePath(bundleBaseName, ruleDescriptionKey, localeToUse);
+ ClassLoader classLoader = bundleClassLoader.getClassLoaderForBundle(bundleBaseName, localeToUse);
+ InputStream stream = classLoader.getResourceAsStream(htmlFilePath);
+ if (stream == null) {
+ throw new MissingResourceException("MISSING RULE DESCRIPTION : file '" + htmlFilePath
+ + "' not found in any bundle. Default value is returned.", bundleBaseName, ruleDescriptionKey);
+ }
+ translation = IOUtils.toString(stream, "UTF-8");
+ }
+
+ return translation;
+ }
+
+ protected Locale defineLocaleToUse(final Locale locale) {
+ Locale localeToUse = locale;
+ if ( !registeredLocales.contains(locale)) {
+ localeToUse = Locale.ENGLISH;
+ }
+ return localeToUse;
+ }
+
+ protected String extractRuleName(String ruleDescriptionKey) {
+ int firstDotIndex = ruleDescriptionKey.indexOf(".");
+ int secondDotIndex = ruleDescriptionKey.indexOf(".", firstDotIndex + 1);
+ int thirdDotIndex = ruleDescriptionKey.indexOf(".", secondDotIndex + 1);
+ return ruleDescriptionKey.substring(secondDotIndex + 1, thirdDotIndex);
+ }
+
+ protected String computeHtmlFilePath(String bundleBaseName, String ruleDescriptionKey, Locale locale) {
+ String ruleName = extractRuleName(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 = keys.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);
+ }
+ LOG.warn("VOID KEY : Key '{}' (from bundle '{}') returns a void value. Default value '{}' is returned.", new Object[] { key,
+ bundleBaseName, defaultText });
+ } else {
+ translation = value;
+ }
+ } catch (MissingResourceException e) {
+ if (translation == null) {
+ throw e;
+ }
+ LOG.warn("BUNDLE NOT LOADED : Failed loading bundle {} from classloader {}. Default value '{}' is returned.", new Object[] {
+ bundleBaseName, bundleClassLoader, defaultText });
+ }
+ }
if (objects.length > 0) {
LOG.debug("Translation : {}, {}, {}, {}", new String[] { locale.toString(), key, defaultText, Arrays.deepToString(objects) });
- return MessageFormat.format(result, objects);
+ return MessageFormat.format(translation, objects);
} else {
- return result;
+ 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);
+ }
+ LOG.warn("UNKNOWN KEY : Key '{}' not found in any bundle. Default value '{}' is returned.", key, defaultText);
+ unknownKeys.put(key, defaultText);
+ }
+
/**
* @return the unknownKeys
*/
@@ -242,6 +316,16 @@ public final class I18nManager implements I18n, ServerExtension, BatchExtension
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)) {
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
index f8b8a299975..bf66390c9e6 100644
--- 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
@@ -19,7 +19,11 @@
*/
package org.sonar.plugins.core.i18n;
+import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import java.net.URL;
@@ -106,6 +110,42 @@ public class I18nManagerTest {
Assert.assertEquals(0, manager.getUnknownKeys().size());
}
+ @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 testExtractRuleName() throws Exception {
+ assertThat(manager.extractRuleName("rule.squid.ArchitecturalConstraint.description"), is("ArchitecturalConstraint"));
+ }
+
+ @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."));
+ }
+
public static class TestClassLoader extends URLClassLoader {
public TestClassLoader(URL url) {
diff --git a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin.jar b/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin.jar
index a0398457179..14d66ce530d 100755
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin.jar
+++ b/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin.jar
Binary files differ
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
index 88aabfe61a2..7aba645661f 100644
--- 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
@@ -2,3 +2,4 @@ it=It
is=is
cold=cold
only.english=Ketchup
+rule.test.fakerule.name=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
new file mode 100644
index 00000000000..7e7d84dea8b
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/I18n/EnglishPlugin/org/sonar/i18n/test/fakerule.html
@@ -0,0 +1,2 @@
+<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
index ab61bd88697..383c767d26d 100644..100755
--- a/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin.jar
+++ b/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin.jar
Binary files differ
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
index cc8efa7afc3..a1d8766e962 100644
--- 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
@@ -1,4 +1,4 @@
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
new file mode 100644
index 00000000000..94c0d0869f8
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/resources/I18n/FrenchPlugin/org/sonar/i18n/test_fr/fakerule.html
@@ -0,0 +1,2 @@
+<h1>Règle bidon</h1>
+C'est la description de la règle bidon. \ No newline at end of file