diff options
Diffstat (limited to 'plugins/sonar-core-plugin/src')
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 Binary files differindex 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 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 Binary files differindex 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 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 |