diff options
author | Simon Brandhof <simon.brandhof@gmail.com> | 2011-06-13 10:40:52 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@gmail.com> | 2011-06-13 10:40:52 +0200 |
commit | aa92267c3ae906a14383c26ef2610ae358807115 (patch) | |
tree | 88e51ad2c7ad68b0faf55998dfa2e330741ee7e0 | |
parent | c257dc801bcb060cb9a5c2f8c516607721bc57e4 (diff) | |
download | sonarqube-aa92267c3ae906a14383c26ef2610ae358807115.tar.gz sonarqube-aa92267c3ae906a14383c26ef2610ae358807115.zip |
SONAR-75 Apply first version of contribution by www.serli.com
40 files changed, 1597 insertions, 21 deletions
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/i18n/core.properties b/plugins/sonar-core-plugin/src/main/resources/org/sonar/i18n/core.properties new file mode 100755 index 00000000000..7f77fa47862 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/i18n/core.properties @@ -0,0 +1,436 @@ +app.view.layouts.layout.login=Log in +app.view.layouts.layout.logout=Log out +app.view.layouts.layout.configuration=Configuration +app.view.layouts.layout.filters=Filters +app.view.layouts.layout.reviews=Reviews +app.view.layouts.layout.dependencies=Dependencies +app.view.layouts.layout.components=Components +app.view.layouts.layout.violations_drilldown=Violations drilldown +app.view.layouts.layout.time_machine=Time machine +app.view.layouts.layout.settings=Settings +app.view.layouts.layout.project_roles=Project roles +app.view.layouts.layout.quality_profiles=Quality profiles +app.view.layouts.layout.event_categories=Event categories +app.view.layouts.layout.manual_metrics=Manual metrics +app.view.layouts.layout.default_filters=Default filters +app.view.layouts.layout.default_dashboards=Default dashboards +app.view.layouts.layout.my_profile=My profile +app.view.layouts.layout.security=Security +app.view.layouts.layout.users=Users +app.view.layouts.layout.groups=Groups +app.view.layouts.layout.global_roles=Global roles +app.view.layouts.layout.system=System +app.view.layouts.layout.backup=Backup +app.view.layouts.layout.system_info=System Info +app.view.layouts.layout.update_center=Update Center + +app.view.components.treemap_settings.size=Size +app.view.components.treemap_settings.color=Color +app.view.filters.treemap.size=Size +app.view.filters.treemap.color=Color + +view.org.sonar.plugins.core.clouds.GwtClouds.title=Clouds +view.org.sonar.plugins.core.hotspots.GwtHotspots.title=Hotspots +view.coverage.title=Coverage +view.org.sonar.plugins.core.duplicationsviewer.DuplicationsViewer.title=Duplications +view.source.title=Source +view.violations.title=Violations + +view.size.lines_of_code=Lines of code +view.size.classes=Classes +view.size.files=Files +view.size.generated_suffix=\ generated +view.size.lines_suffix=\ lines +view.size.statements_suffix=\ statements +view.size.files_suffix=\ files +view.size.packages_suffix=\ packages +view.size.directories_suffix=\ directories +view.size.methods_suffix=\ methods +view.size.accessors_suffix=\ accessors +view.size.paragraphs_suffix=\ paragraphs + +general_columns.links=Links +general_columns.build_time=Build time +general_columns.language=Language +general_columns.version=Version +general_columns.date=Build date +general_columns.key=Key +general_columns.name=Name + +domain.Size=Size +domain.Tests=Tests +domain.Complexity=Complexity +domain.Documentation=Documentation +domain.Rules=Rules +domain.General=General +domain.Duplication=Duplication +domain.Design=Design +domain.SCM=SCM +domain.Management=Management + +metric.lines.name=Lines +metric.lines.description=Lines + +metric.generated_lines.name=Generated Lines +metric.generated_lines.description=Number of generated lines + +metric.ncloc.name=Lines of code +metric.ncloc.description=Non Commenting Lines of Code + +metric.generated_ncloc.name=Generated lines of code +metric.generated_ncloc.description=Generated non Commenting Lines of Code + +metric.classes.name=Classes +metric.classes.description=Classes + +metric.files.name=Files +metric.files.description=Number of files + +metric.directories.name=Directories +metric.directories.description=Directories + +metric.packages.name=Packages +metric.packages.description=Packages + +metric.functions.name=Methods +metric.functions.description=Methods + +metric.accessors.name=Accessors +metric.accessors.description=Accessors + +metric.paragraphs.name=Paragraphs +metric.paragraphs.description=Number of paragraphs + +metric.statements.name=Statements +metric.statements.description=Number of statements + +metric.public_api.name=Public API +metric.public_api.description=Public API + +#-------------------------------------------------------------------------------------------------------------------- +# +# DOCUMENTATION +# +#-------------------------------------------------------------------------------------------------------------------- + +metric.comment_lines.name=Comment lines +metric.comment_lines.description=Number of comment lines + +metric.comment_lines_density.name=Comments (%) +metric.comment_lines_density.description=Comments balanced by ncloc + comment lines + +metric.comment_blank_lines.name=Blank comments +metric.comment_blank_lines.description=Comments that do not contain comments + +metric.public_documented_api_density.name=Public documented API (%) +metric.public_documented_api_density.description=Public documented classes and methods balanced by ncloc + +metric.public_undocumented_api.name=Public undocumented API +metric.public_undocumented_api.description=Public undocumented classes, methods and variables + +metric.commented_out_code_lines.name=Commented LOCs +metric.commented_out_code_lines.description=Commented lines of code + +#-------------------------------------------------------------------------------------------------------------------- +# +# COMPLEXITY +# +#-------------------------------------------------------------------------------------------------------------------- + +metric.complexity.name=Complexity +metric.complexity.description=Cyclomatic complexity + +metric.class_complexity.name=Complexity /class +metric.class_complexity.description=Complexity average by class + +metric.function_complexity.name=Complexity /method +metric.function_complexity.description=Complexity average by method + +metric.file_complexity.name=Complexity /file +metric.file_complexity.description=Complexity average by file + +metric.paragraph_complexity.name=Complexity /paragraph +metric.paragraph_complexity.description=Complexity average by paragraph + +metric.class_complexity_distribution.name=Classes distribution /complexity +metric.class_complexity_distribution.description=Classes distribution /complexity + +metric.function_complexity_distribution.name=Functions distribution /complexity +metric.function_complexity_distribution.description=Functions distribution /complexity + +metric.file_complexity_distribution.name=Files distribution /complexity +metric.file_complexity_distribution.description=Files distribution /complexity + +metric.paragraph_complexity_distribution.name=Paragraph distribution /complexity +metric.paragraph_complexity_distribution.description=Paragraph distribution /complexity + +#-------------------------------------------------------------------------------------------------------------------- +# +# UNIT TESTS +# +#-------------------------------------------------------------------------------------------------------------------- + +metric.tests.name=Unit tests +metric.tests.description=Number of unit tests + +metric.test_execution_time.name=Unit tests duration +metric.test_execution_time.description=Execution duration of unit tests + +metric.test_errors.name=Unit test errors +metric.test_errors.description=Number of unit test errors + +metric.skipped_tests.name=Skipped unit tests +metric.skipped_tests.description=Number of skipped unit tests + +metric.test_failures.name=Unit test failures +metric.test_failures.description=Number of unit test failures + +metric.test_success_density.name=Unit test success (%) +metric.test_success_density.description=Density of successful unit tests + +metric.test_data.name=Unit tests details +metric.test_data.description=Unit tests details + +metric.coverage.name=Coverage +metric.coverage.description=Coverage by unit tests + +metric.new_coverage.name=New coverage +metric.new_coverage.description=Coverage of new/changed code + +metric.lines_to_cover.name=Lines to cover +metric.lines_to_cover.description=Lines to cover + +metric.new_lines_to_cover.name=New lines to cover +metric.new_lines_to_cover.description=New lines to cover + +metric.uncovered_lines.name=Uncovered lines +metric.uncovered_lines.description=Uncovered lines + +metric.new_uncovered_lines.name=New uncovered lines +metric.new_uncovered_lines.description=New uncovered lines + +metric.line_coverage.name=Line coverage +metric.line_coverage.description=Line coverage + +metric.new_line_coverage.name=New line coverage +metric.new_line_coverage.description=Line coverage of added/changed code + +metric.coverage_line_hits_data.name=Coverage hits by line +metric.coverage_line_hits_data.description=Coverage hits by line + +metric.conditions_to_cover.name=Conditions to cover +metric.conditions_to_cover.description=Conditions to cover + +metric.new_conditions_to_cover.name=New conditions to cover +metric.new_conditions_to_cover.description=New conditions to cover + +metric.uncovered_conditions.name=Uncovered conditions +metric.uncovered_conditions.description=Uncovered conditions + +metric.new_uncovered_conditions.name=New uncovered conditions +metric.new_uncovered_conditions.description=New uncovered conditions + +metric.branch_coverage.name=Branch coverage +metric.branch_coverage.description=Branch coverage + +metric.new_branch_coverage.name=New branch coverage +metric.new_branch_coverage.description=Branch coverage of new/changed code + +metric.branch_coverage_hits_data.name=Branch coverage hits +metric.branch_coverage_hits_data.description=Branch coverage hits + +metric.conditions_by_line.name=Conditions by line +metric.conditions_by_line.description=Conditions by line + +metric.covered_conditions_by_line.name=Covered conditions by line +metric.covered_conditions_by_line.description=Covered conditions by line + +#-------------------------------------------------------------------------------------------------------------------- +# +# DUPLICATIONS +# +#-------------------------------------------------------------------------------------------------------------------- + +metric.duplicated_lines.name=Duplicated lines +metric.duplicated_lines.description=Duplicated lines + +metric.duplicated_blocks.name=Duplicated blocks +metric.duplicated_blocks.description=Duplicated blocks + +metric.duplicated_files.name=Duplicated files +metric.duplicated_files.description=Duplicated files + +metric.duplicated_lines_density.name=Duplicated lines (%) +metric.duplicated_lines_density.description=Duplicated lines balanced by statements + +metric.duplications_data.name=Duplications details +metric.duplications_data.description=Duplications details + +#-------------------------------------------------------------------------------------------------------------------- +# +# CODING RULES +# +#-------------------------------------------------------------------------------------------------------------------- + +metric.usability.name=Usability +metric.usability.description=Usability + +metric.reliability.name=Reliability +metric.reliability.description=Reliability + +metric.efficiency.name=Efficiency +metric.efficiency.description=Efficiency + +metric.portability.name=Portability +metric.portability.description=Portability + +metric.maintainability.name=Maintainability +metric.maintainability.description=Maintainability + +metric.weighted_violations.name=Weighted violations +metric.weighted_violations.description=Weighted Violations + +metric.violations_density.name=Rules compliance +metric.violations_density.description=Rules compliance + +metric.violations.name=Violations +metric.violations.description=Violations + +metric.blocker_violations.name=Blocker violations +metric.blocker_violations.description=Blocker violations + +metric.critical_violations.name=Critical violations +metric.critical_violations.description=Critical violations + +metric.major_violations.name=Major violations +metric.major_violations.description=Major violations + +metric.minor_violations.name=Minor violations +metric.minor_violations.description=Minor violations + +metric.info_violations.name=Info violations +metric.info_violations.description=Info violations + +metric.new_violations.name=New Violations +metric.new_violations.description=New Violations + +metric.new_blocker_violations.name=New Blocker violations +metric.new_blocker_violations.description=New Blocker violations + +metric.new_critical_violations.name=New Critical violations +metric.new_critical_violations.description=New Critical violations + +metric.new_major_violations.name=New Major violations +metric.new_major_violations.description=New Major violations + +metric.new_minor_violations.name=New Minor violations +metric.new_minor_violations.description=New Minor violations + +metric.new_info_violations.name=New Info violations +metric.new_info_violations.description=New Info violations + +#-------------------------------------------------------------------------------------------------------------------- +# +# DESIGN +# +#-------------------------------------------------------------------------------------------------------------------- + +metric.abstractness.name=Abstractness +metric.abstractness.description=Abstractness + +metric.instability.name=Instability +metric.instability.description=Instability + +metric.distance.name=Distance +metric.distance.description=Distance + +metric.dit.name=Depth in Tree +metric.dit.description=Depth in Inheritance Tree + +metric.noc.name=Number of Children +metric.noc.description=Number of Children + +metric.rfc.name=RFC +metric.rfc.description=Response for Class + +metric.rfc_distribution.name=Class distribution /RFC +metric.rfc_distribution.description=Class distribution /RFC + +metric.lcom4.name=LCOM4 +metric.lcom4.description=Lack of Cohesion of Methods + +metric.lcom4_blocks.name=LCOM4 blocks +metric.lcom4_blocks.description=LCOM4 blocks + +metric.lcom4_distribution.name=Class distribution /LCOM4 +metric.lcom4_distribution.description=Class distribution /LCOM4 + +metric.suspect_lcom4_density.name=Suspect LCOM4 density +metric.suspect_lcom4_density.description=Density of classes having LCOM4>1 + +metric.ca.name=Afferent couplings +metric.ca.description=Afferent couplings + +metric.ce.name=Efferent couplings +metric.ce.description=Efferent couplings + +metric.dsm.name=Dependency Matrix +metric.dsm.description=Dependency Matrix + +metric.package_cycles.name=Package cycles +metric.package_cycles.description=Package cycles + +metric.package_tangle_index.name=Package tangle index +metric.package_tangle_index.description=Package tangle index + +metric.package_tangles.name=File dependencies to cut +metric.package_tangles.description=File dependencies to cut + +metric.package_feedback_edges.name=Package dependencies to cut +metric.package_feedback_edges.description=Package dependencies to cut + +metric.package_edges_weight.name=Package edges weight +metric.package_edges_weight.description=Package edges weight + +metric.file_cycles.name=File cycles +metric.file_cycles.description=File cycles + +metric.file_tangle_index.name=File tangle index +metric.file_tangle_index.description=File tangle index + +metric.file_tangles.name=File tangles +metric.file_tangles.description=Files tangles + +metric.file_feedback_edges.name=Suspect file dependencies +metric.file_feedback_edges.description=Suspect file dependencies + +metric.file_edges_weight.name=File edges weight +metric.file_edges_weight.description=File edges weight + +metric.commits.name=Commits +metric.commits.description=Commits + +metric.last_commit_date.name=Last commit +metric.last_commit_date.description=Last commit + +metric.revision.name=Revision +metric.revision.description=Revision + +metric.authors_by_line.name=Authors by line +metric.authors_by_line.description=Authors by line + +metric.revisions_by_line.name=Revisions by line +metric.revisions_by_line.description=Revisions by line + +metric.last_commit_datetimes_by_line.name=Last commit dates by line +metric.last_commit_datetimes_by_line.description=Last commit dates by line + +metric.alert_status.name=Alert +metric.alert_status.description=Alert + +metric.profile.name=Profile +metric.profile.description=Selected quality profile + +metric.profile_version.name=Profile version +metric.profile_version.description=Selected quality profile version + diff --git a/plugins/sonar-design-plugin/src/main/resources/org/sonar/i18n/design.properties b/plugins/sonar-design-plugin/src/main/resources/org/sonar/i18n/design.properties new file mode 100755 index 00000000000..c3af9b66e32 --- /dev/null +++ b/plugins/sonar-design-plugin/src/main/resources/org/sonar/i18n/design.properties @@ -0,0 +1,4 @@ +view.org.sonar.plugins.design.ui.page.DesignPage.title=Design +view.org.sonar.plugins.design.ui.libraries.LibrariesPage.title=Libraries +view.org.sonar.plugins.design.ui.dependencies.DependenciesTab.title=Dependencies +view.org.sonar.plugins.design.ui.lcom4.Lcom4Tab.title=LCOM4 diff --git a/plugins/sonar-i18n-plugin/pom.xml b/plugins/sonar-i18n-plugin/pom.xml new file mode 100644 index 00000000000..c9390e6571c --- /dev/null +++ b/plugins/sonar-i18n-plugin/pom.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>sonar</artifactId>
+ <groupId>org.codehaus.sonar</groupId>
+ <version>2.9-SNAPSHOT</version>
+ <relativePath>../..</relativePath>
+ </parent>
+ <groupId>org.codehaus.sonar.plugins</groupId>
+ <artifactId>sonar-i18n-plugin</artifactId>
+ <packaging>sonar-plugin</packaging>
+ <version>2.9-SNAPSHOT</version>
+ <name>Sonar :: Plugins :: I18n</name>
+ <description>Sonar I18n plugin</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-plugin-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- unit tests -->
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-testing-harness</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.0.2</version>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ <encoding>UTF-8</encoding>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-packaging-maven-plugin</artifactId>
+ <version>1.1</version>
+ <extensions>true</extensions>
+ <configuration>
+ <pluginKey>i18n</pluginKey>
+ <pluginClass>org.sonar.plugins.i18n.I18nPlugin</pluginClass>
+ <pluginName>I18n</pluginName>
+ <pluginDescription>
+ <![CDATA[Provides components required to internationalize Sonar application and plugins, as well as a default language bundle for the Sonar kernel modules and web application.]]></pluginDescription>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nManager.java b/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nManager.java new file mode 100644 index 00000000000..6846f88b4ae --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/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.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.info("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-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nPlugin.java b/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nPlugin.java new file mode 100644 index 00000000000..0e6495c021a --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nPlugin.java @@ -0,0 +1,36 @@ +/*
+ * 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;
+
+import org.sonar.api.SonarPlugin;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @since 2.9
+ */
+public final class I18nPlugin extends SonarPlugin {
+
+ public List getExtensions() {
+ return Arrays.asList(I18nManager.class, I18nWebService.class);
+ }
+
+}
diff --git a/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nWebService.java b/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nWebService.java new file mode 100644 index 00000000000..3584e67e4c5 --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nWebService.java @@ -0,0 +1,35 @@ +/* + * 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; + +import org.sonar.api.web.AbstractRubyTemplate; +import org.sonar.api.web.RubyRailsWebservice; + +public class I18nWebService extends AbstractRubyTemplate implements RubyRailsWebservice { + + public String getId() { + return "i18n_manager"; + } + + @Override + protected String getTemplatePath() { + return "i18n_manager_controller.rb"; + } +} diff --git a/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/InstalledPlugin.java b/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/InstalledPlugin.java new file mode 100644 index 00000000000..d0348df1370 --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/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.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-i18n-plugin/src/main/resources/org/sonar/plugins/i18n/i18n_manager_controller.rb b/plugins/sonar-i18n-plugin/src/main/resources/org/sonar/plugins/i18n/i18n_manager_controller.rb new file mode 100644 index 00000000000..e3f7ee50a96 --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/main/resources/org/sonar/plugins/i18n/i18n_manager_controller.rb @@ -0,0 +1,66 @@ +# +# 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 Sonar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 +# + +require "json" + +class Api::I18nManagerController < Api::ApiController + + def index + render :text => "Use one of the following:<br><ul>" + + "<li>/api/plugins/i18n_manager/unknown_keys?format=text|json</li>" + + "</ul>" + end + + # GET /api/plugins/i18n_manager/unknown_keys + # Examples : + # curl -v http://localhost:9000/api/plugins/i18n_manager -u admin:admin + # + + def unknown_keys + begin + output = "" + properties = i18n_manager.unknown_keys + + properties.keys.sort.each {|key| output += "#{key}=#{properties[key]}\n" } + + output = "# No unknown keys" if output.empty? + + respond_to do |format| + format.json { render :json => JSON(properties) } + format.xml { render :xml => xml_not_supported } + format.text { render :text => output } + end + + rescue ApiException => e + render_error(e.msg, e.code) + + rescue Exception => e + logger.error("Fails to execute #{request.url} : #{e.message}") + render_error(e.message) + end + end + + private + + def i18n_manager + java_facade.getComponentByClassname('i18n', 'org.sonar.plugins.i18n.I18nManager') + end + +end
\ No newline at end of file diff --git a/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nManagerTest.java b/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nManagerTest.java new file mode 100644 index 00000000000..f4d075750f6 --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nManagerTest.java @@ -0,0 +1,121 @@ +/* + * 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; + +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.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +public class I18nManagerTest { + + public static String TEST_PLUGIN_CLASS_NAME = "org.sonar.plugins.i18n.utils.StandardPlugin"; + public static String FRENCH_PACK_CLASS_NAME = "org.sonar.plugins.i18n.utils.FrenchLanguagePack"; + public static String QUEBEC_PACK_CLASS_NAME = "org.sonar.plugins.i18n.utils.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); + assertEquals(1, manager.getUnknownKeys().size()); + 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-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nPluginTest.java b/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nPluginTest.java new file mode 100644 index 00000000000..9fc007149e8 --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nPluginTest.java @@ -0,0 +1,33 @@ +/* + * 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; + +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.number.OrderingComparisons.greaterThanOrEqualTo; + +public class I18nPluginTest { + + @Test + public void shouldGetExtensions() { + assertThat(new I18nPlugin().getExtensions().size(), greaterThanOrEqualTo(2)); + } +} diff --git a/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/FrenchLanguagePack.java b/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/FrenchLanguagePack.java new file mode 100644 index 00000000000..98c09780bfb --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/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.i18n.utils; + +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-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/QuebecLanguagePack.java b/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/QuebecLanguagePack.java new file mode 100644 index 00000000000..5e9913e2156 --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/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.i18n.utils; + +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-i18n-plugin/src/test/resources/FrenchPlugin.jar b/plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin.jar Binary files differnew file mode 100644 index 00000000000..ab61bd88697 --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin.jar diff --git a/plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin/META-INF/MANIFEST.MF b/plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..a45029d3531 --- /dev/null +++ b/plugins/sonar-i18n-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-i18n-plugin/src/test/resources/FrenchPlugin/org/sonar/i18n/test_fr.properties b/plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin/org/sonar/i18n/test_fr.properties new file mode 100644 index 00000000000..cc8efa7afc3 --- /dev/null +++ b/plugins/sonar-i18n-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-i18n-plugin/src/test/resources/QuebecPlugin.jar b/plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin.jar Binary files differnew file mode 100644 index 00000000000..101df34ba6d --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin.jar diff --git a/plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin/META-INF/MANIFEST.MF b/plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..a45029d3531 --- /dev/null +++ b/plugins/sonar-i18n-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-i18n-plugin/src/test/resources/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties b/plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties new file mode 100644 index 00000000000..38b5b84ccdf --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties @@ -0,0 +1,2 @@ +cold=frette + diff --git a/plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin.jar b/plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin.jar Binary files differnew file mode 100644 index 00000000000..73deac7e8b6 --- /dev/null +++ b/plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin.jar diff --git a/plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin/META-INF/MANIFEST.MF b/plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..a45029d3531 --- /dev/null +++ b/plugins/sonar-i18n-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-i18n-plugin/src/test/resources/StandardPlugin/org/sonar/i18n/test.properties b/plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin/org/sonar/i18n/test.properties new file mode 100644 index 00000000000..88aabfe61a2 --- /dev/null +++ b/plugins/sonar-i18n-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-i18n-plugin/src/test/resources/logback-test.xml b/plugins/sonar-i18n-plugin/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..cdc8866fe3a --- /dev/null +++ b/plugins/sonar-i18n-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 @@ -47,6 +47,7 @@ <module>plugins/sonar-cpd-plugin</module> <module>plugins/sonar-squid-java-plugin</module> <module>plugins/sonar-design-plugin</module> + <module>plugins/sonar-i18n-plugin</module> </modules> <organization> diff --git a/sonar-application/pom.xml b/sonar-application/pom.xml index 6e83c28617d..da36b404335 100644 --- a/sonar-application/pom.xml +++ b/sonar-application/pom.xml @@ -209,6 +209,12 @@ <scope>runtime</scope> </dependency> <dependency> + <groupId>org.codehaus.sonar.plugins</groupId> + <artifactId>sonar-i18n-plugin</artifactId> + <version>${project.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> <groupId>org.sonatype.jsw-binaries</groupId> <artifactId>jsw-binaries</artifactId> <version>3.2.3.6</version> diff --git a/sonar-application/src/main/assembly/conf/logback.xml b/sonar-application/src/main/assembly/conf/logback.xml index 407cbdbe265..c11fca34b93 100644 --- a/sonar-application/src/main/assembly/conf/logback.xml +++ b/sonar-application/src/main/assembly/conf/logback.xml @@ -103,6 +103,10 @@ <level value="INFO"/> </logger> + <logger name="org.sonar.plugins.i18n.I18nManager"> + <level value="INFO"/> + </logger> + <root> <level value="WARN"/> <appender-ref ref="SONAR_FILE"/> diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/i18n/I18n.java b/sonar-plugin-api/src/main/java/org/sonar/api/i18n/I18n.java new file mode 100644 index 00000000000..13c989fa043 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/i18n/I18n.java @@ -0,0 +1,158 @@ +/* + * 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.BatchComponent; +import org.sonar.api.ServerComponent; + +import java.util.Locale; + +/** + * + * The <code>I18n</code> Interface is the entry point for the internationalization of the Sonar application and plugins.<br>The corresponding implementation is located in the core I18n plugin. + * <p/> + * I18n is managed in Sonar through the use of key-based resource bundles. + * <br> + * Though any key can be used, the following key-naming conventions, which are applied in the Sonar application and core plugins, are given as guidelines: + * <ul> + * <li> + * Title of <code>View</code> extensions (pages, tabs, widgets): + * <blockquote><code>view.<i>view_id</i>.title</code></blockquote> + * where <i>view_id</i> is the value returned by the <code>View.getId()</code> method + * </li> + * <br> + * <li> + * Free text translation in <code>View</code> extensions (pages, tabs, widgets): + * <blockquote><code>view.<i>view_id</i>.<i>key</i></code></blockquote> + * where: + * <ul> + * <li> + * <code><i>view_id</i></code> is the value returned by the <code>View.getId()</code> method + * </li> + * <li> + * <code><i>key</i></code> is the key of the text in the code of this view (rails page resource for a widget) + * </li> + * </ul> + * </li> + * <br> + * <li> + * Free text translation in the files of the Rails web application: + * <blockquote><code>app.<i>type</i>.<i>path</i>.<i>key</i></code></blockquote> + * where: + * <ul> + * <li> + * <code><i>type</i></code> is the type of rails application file ('controller', 'helper', 'model', 'view'). + * </li> + * <li> + * <code><i>path</i></code> is the path of the rails page related to the rails component type subdirectory ('controllers', 'views', etc ...). + * </li> + * <li> + * <code><i>key</i></code> is the key of the text in the code of this file + * </li> + * </ul><br> + * For example, searching for the <code>login</code> key in the rails file <code>views/layouts/_layout.html.erb</code> gives the following full i18n key: + * <blockquote><code>app.view.layouts.layout.login</code></blockquote> + * </li> + * <br> + * <li> + * Metric name or description: + * <blockquote><code>metric.<i>metric_key</i>.<i>field</i></code></blockquote> + * where: + * <ul> + * <li> + * <code><i>metric_key</i></code> is the key of the metric ('ncloc', etc ...), + * </li> + * <li> + * <code><i>field</i></code> is the field to translate on the metric ('name' or 'description'). + * </li> + * </ul> + * </li> + * <br> + * <li> + * Metric domain: + * <blockquote><code>domain.<i>domain_text</i></code></blockquote> + * where: + * <ul> + * <li> + * <code><i>domain_text</i></code> is the default (english) text of the domain as defined in the Metric object. + * </li> + * </ul> + * </li> + * <br> + * <li> + * General columns which are not metrics (key, build date, etc ...): + * <blockquote><code>general_columns.<i>column_key</i></code></blockquote> + * where: + * <ul> + * <li> + * <code><i>column_key</i></code> is the key of the column ('key', 'date', 'links', etc ...). + * </li> + * </ul> + * </li> + * <br> + * <li> + * Rules: + * <blockquote><code>rule.<i>repository_key</i>.<i>rule_key</i>.<i>field</i></code></blockquote> + * where: + * <ul> + * <li> + * <code><i>repository_key</i></code> is the key of the rule repository, + * </li> + * <li> + * <code><i>rule_key</i></code> is the key of the rule in the repository, + * </li> + * <li> + * <code><i>field</i></code> is the field to translate on the rule ('name' or 'description'). + * </li> + * </ul> + * </li> + * <br> + * <li> + * Rule severities: + * <blockquote><code>severity.<i>severity_text</i></code></blockquote> + * where: + * <ul> + * <li> + * <code><i>severity_text</i></code> is the upper_case text of the RulePriority enum value ('MAJOR', 'MINOR', etc ...). + * </li> + * </ul> + * </li> + * </ul> + * + * @since 2.9 + */ +public interface I18n extends ServerComponent, BatchComponent { + + /** + * Searches the message of the <code>key</code> for the <code>locale</code> in the list of available bundles. + * <br> + * If not found in any bundle, <code>defaultText</code> is returned. + * + * If additional parameters are given (in the objects list), the result is used as a message pattern + * to use in a MessageFormat object along with the given parameters. + * + * @param locale the locale to translate into + * @param key the key of the pattern to translate + * @param defaultValue the default pattern returned when the key is not found in any bundle + * @param parameters the parameters used to format the message from the translated pattern. + * @return the message formatted with the translated pattern and the given parameters + */ + public abstract String message(final Locale locale, final String key, final String defaultValue, final Object... parameters); +} 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 new file mode 100644 index 00000000000..719a493547d --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/i18n/LanguagePack.java @@ -0,0 +1,50 @@ +/* + * 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; + +/** + * @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/pom.xml b/sonar-server/pom.xml index a89e43411ea..8b65aa8e98c 100644 --- a/sonar-server/pom.xml +++ b/sonar-server/pom.xml @@ -458,6 +458,12 @@ <version>${project.version}</version> <scope>provided</scope> </dependency> + <dependency> + <groupId>org.codehaus.sonar.plugins</groupId> + <artifactId>sonar-i18n-plugin</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> </dependencies> </profile> 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 511d71a7f8e..3f3ee2f382c 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 @@ -58,6 +58,7 @@ import org.sonar.server.rules.*; import org.sonar.server.startup.*; import org.sonar.server.ui.AuthenticatorFactory; import org.sonar.server.ui.CodeColorizers; +import org.sonar.server.ui.JRubyI18n; import org.sonar.server.ui.Views; /** @@ -178,6 +179,7 @@ public final class Platform { servicesContainer.as(Characteristics.CACHE).addComponent(DefaultMetricFinder.class); servicesContainer.as(Characteristics.CACHE).addComponent(ProfilesConsole.class); servicesContainer.as(Characteristics.CACHE).addComponent(RulesConsole.class); + servicesContainer.as(Characteristics.CACHE).addComponent(JRubyI18n.class); servicesContainer.start(); } 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 2992bba8585..7e1e99c67cc 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 @@ -22,7 +22,6 @@ package org.sonar.server.ui; import org.apache.commons.configuration.Configuration; import org.picocontainer.PicoContainer; import org.slf4j.LoggerFactory; -import org.sonar.api.Plugins; import org.sonar.api.Property; import org.sonar.api.platform.PluginMetadata; import org.sonar.api.platform.PluginRepository; @@ -58,6 +57,7 @@ import java.util.Set; public final class JRubyFacade { private static final JRubyFacade SINGLETON = new JRubyFacade(); + private JRubyI18n i18n; public static JRubyFacade getInstance() { return SINGLETON; @@ -130,9 +130,6 @@ public final class JRubyFacade { } - - - public List<ViewProxy<Widget>> getWidgets(String resourceScope, String resourceQualifier, String resourceLanguage) { return getContainer().getComponent(Views.class).getWidgets(resourceScope, resourceQualifier, resourceLanguage); } @@ -244,7 +241,7 @@ public final class JRubyFacade { } public void ruleSeverityChanged(int parentProfileId, int activeRuleId, int oldSeverityId, int newSeverityId, String userName) { - getProfilesManager().ruleSeverityChanged(parentProfileId, activeRuleId, RulePriority.values()[oldSeverityId], + getProfilesManager().ruleSeverityChanged(parentProfileId, activeRuleId, RulePriority.values()[oldSeverityId], RulePriority.values()[newSeverityId], userName); } @@ -317,6 +314,13 @@ public final class JRubyFacade { return component; } + public String getI18nMessage(String rubyLocale, String key, String defaultValue, Object... parameters) { + if (i18n == null) { + i18n = getContainer().getComponent(JRubyI18n.class); + } + return i18n.message(rubyLocale, key, defaultValue, parameters); + } + public PicoContainer getContainer() { return Platform.getInstance().getContainer(); } 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 new file mode 100644 index 00000000000..72a1d0e39a0 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyI18n.java @@ -0,0 +1,69 @@ +/* + * 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.server.ui; + +import com.google.common.collect.Maps; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.ServerComponent; +import org.sonar.api.i18n.I18n; + +import java.util.Locale; +import java.util.Map; + +/** + * Bridge between JRuby webapp and Java I18n component + */ +public final class JRubyI18n implements ServerComponent { + + private I18n i18n; + private Map<String,Locale> localesByRubyKey = Maps.newHashMap(); + + public JRubyI18n(I18n i18n) { + this.i18n = i18n; + } + + Locale getLocale(String rubyKey) { + Locale locale = localesByRubyKey.get(rubyKey); + if (locale == null) { + locale = toLocale(rubyKey); + localesByRubyKey.put(rubyKey, locale); + } + return locale; + } + + Map<String, Locale> getLocalesByRubyKey() { + return localesByRubyKey; + } + + static Locale toLocale(String rubyKey) { + Locale locale; + String[] fields = StringUtils.split(rubyKey, "-"); + if (fields.length==1) { + locale = new Locale(fields[0]); + } else { + locale = new Locale(fields[0], fields[1]); + } + return locale; + } + + public String message(String rubyLocale, String key, String defaultValue, Object... parameters) { + return i18n.message(getLocale(rubyLocale), key, defaultValue, parameters); + } +} diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/server_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/server_controller.rb index 00d93e56597..fe5697b7f76 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/server_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/api/server_controller.rb @@ -48,7 +48,7 @@ class Api::ServerController < Api::ApiController end def system - @server=Server.new(java_facade) + @server=Server.new json=[ {:system_info => server_properties_to_json(@server.system_info)}, {:system_statistics => server_properties_to_json(@server.system_statistics)}, 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 cc87fe0dc86..af52d5bf243 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 @@ -29,7 +29,7 @@ class ApplicationController < ActionController::Base end def java_facade - @java_facade ||= Java::OrgSonarServerUi::JRubyFacade.new + Java::OrgSonarServerUi::JRubyFacade.getInstance() end def available_locales diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/system_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/system_controller.rb index dcbc9724087..e836b77eed0 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/system_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/system_controller.rb @@ -24,7 +24,7 @@ class SystemController < ApplicationController before_filter :admin_required def index - @server=Server.new(java_facade) + @server=Server.new respond_to do |format| format.html diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb index 2922bcf0c92..3704dbf3e2c 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb @@ -55,6 +55,10 @@ module ApplicationHelper end end + # i18n + def message(key, default, *parameters) + Java::OrgSonarServerUi::JRubyFacade.getInstance().getI18nMessage(I18n.locale, key, default, parameters.to_java) + end # deprecated since 2.5. Use trend_icon() instead def tendency_icon(metric_or_measure, small=true, no_tendency_img=true) @@ -142,7 +146,7 @@ module ApplicationHelper end def configuration(key, default = nil) - prop_value = controller.java_facade.getContainer().getComponent(Java::OrgApacheCommonsConfiguration::Configuration.java_class).getProperty(key) + prop_value = Java::OrgSonarServerUi::JRubyFacade.getInstance().getContainer().getComponent(Java::OrgApacheCommonsConfiguration::Configuration.java_class).getProperty(key) prop_value.nil? ? default : prop_value end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/i18n_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/i18n_helper.rb new file mode 100644 index 00000000000..d6a764ffd83 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/i18n_helper.rb @@ -0,0 +1,61 @@ +# 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 + # +module I18nHelper + + def self.java_locale() + locale_parts = I18n.locale.to_s.split('-') + return java.util.Locale.new(locale_parts[0]) if locale_parts.size == 1 + return java.util.Locale.new(locale_parts[0], locale_parts[1]) if locale_parts.size >= 2 + end + + def self.i18n + Java::OrgSonarServerUi::JRubyFacade.getInstance().getI18n() + end + + def self.translation(key, defaultText, *objects) + i18n.translation(java_locale(), key, defaultText, objects.to_java) + end + + def self.trans_widget(widget, key, default_text, *objects) + i18n.translation(java_locale(), "view." + widget.widget_key + "." + key, default_text, objects.to_java) + end + + def self.trans_page(view_id, key, default_text, *objects) + i18n.translation(java_locale(), "view." + view_id + "." + key, default_text, objects.to_java) + end + + def self.trans_tab(view_id, key, default_text, *objects) + i18n.translation(java_locale(), "view." + view_id + "." + key, default_text, objects.to_java) + end + + def self.trans_column(column_key, default_text, *objects) + i18n.translation(java_locale(), "general_columns." + column_key, default_text, objects.to_java) + end + + def self.trans_app_view(path, key, default_text, *objects) + i18n.translation(java_locale(), "app.view." + path + "." + key, default_text, objects.to_java) + end + + def self.trans_app_helper(path, key, default_text, *objects) + i18n.translation(java_locale(), "app.helper." + path + "." + key, default_text, objects.to_java) + end + + def self.trans_app_controller(path, key, default_text, *objects) + i18n.translation(java_locale(), "app.controller." + path + "." + key, default_text, objects.to_java) + end + + def self.trans_app_model(path, key, default_text, *objects) + i18n.translation(java_locale(), "app.model." + path + "." + key, default_text, objects.to_java) + end +end
\ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/metrics_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/metrics_helper.rb index 1f5fdab3e33..2be31148e2a 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/metrics_helper.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/metrics_helper.rb @@ -19,14 +19,14 @@ # module MetricsHelper - def domains(metrics) - metrics.map {|m| m.domain}.uniq.compact.sort + def domains(metrics, translate=false) + metrics.map {|m| m.domain(translate)}.uniq.compact.sort end def options_grouped_by_domain(metrics, selected_key='') metrics_per_domain={} metrics.each do |metric| - domain=metric.domain || '' + domain=metric.domain(true) || '' metrics_per_domain[domain]||=[] metrics_per_domain[domain]<<metric end @@ -36,7 +36,7 @@ module MetricsHelper html += "<optgroup label=\"#{html_escape(domain)}\">" metrics_per_domain[domain].each do |m| selected_attr = " selected='selected'" if (m.key==selected_key || m.id==selected_key) - html += "<option value='#{html_escape(m.key)}'#{selected_attr}>#{html_escape(m.short_name)}</option>" + html += "<option value='#{html_escape(m.key)}'#{selected_attr}>#{html_escape(m.short_name(true))}</option>" end html += '</optgroup>' end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/server.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/server.rb index 31401899472..f0baab37f59 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/server.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/server.rb @@ -19,10 +19,6 @@ # class Server - def initialize(java_facade) - @java_facade=java_facade - end - def info system_info + sonar_info + system_statistics + sonar_plugins + system_properties end @@ -70,7 +66,7 @@ class Server add_property(sonar_info, 'Database Login') {sonar_property('sonar.jdbc.username')} add_property(sonar_info, 'Database Driver') {"#{jdbc_metadata.getDriverName()} #{jdbc_metadata.getDriverVersion()}"} add_property(sonar_info, 'Database Driver Class') {sonar_property('sonar.jdbc.driverClassName')} - add_property(sonar_info, 'Database Dialect (Hibernate)') {"#{@java_facade.getDialect().getId()} (#{@java_facade.getDialect().getHibernateDialectClass().getName()})"} + add_property(sonar_info, 'Database Dialect (Hibernate)') {"#{Java::OrgSonarServerUi::JRubyFacade.getInstance().getDialect().getId()} (#{Java::OrgSonarServerUi::JRubyFacade.getInstance().getDialect().getHibernateDialectClass().getName()})"} add_property(sonar_info, 'Database Validation Query') {sonar_property('sonar.jdbc.validationQuery')} add_property(sonar_info, 'Hibernate Default Schema') {sonar_property('sonar.hibernate.default_schema')} add_property(sonar_info, 'External User Authentication') {sonar_property(org.sonar.api.CoreProperties.CORE_AUTHENTICATOR_CLASS)} @@ -83,7 +79,7 @@ class Server def sonar_plugins sonar_plugins=[] - @java_facade.getPluginsMetadata().select{|plugin| !plugin.isCore()}.sort.each do |plugin| + Java::OrgSonarServerUi::JRubyFacade.getInstance().getPluginsMetadata().select{|plugin| !plugin.isCore()}.sort.each do |plugin| add_property(sonar_plugins, plugin.getName()) {plugin.getVersion()} end sonar_plugins @@ -126,7 +122,7 @@ class Server end def sonar_property(key) - @java_facade.getContainer().getComponent(Java::OrgApacheCommonsConfiguration::Configuration.java_class).getProperty(key) + Java::OrgSonarServerUi::JRubyFacade.getInstance().getContainer().getComponent(Java::OrgApacheCommonsConfiguration::Configuration.java_class).getProperty(key) end def jdbc_metadata diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb index 7de0d6da97e..cce189d566f 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb @@ -14,7 +14,7 @@ » <a href="<%= ApplicationController.root_context -%>/sessions/logout">Log out</a> </li> <% else %> - <li><a href="<%= ApplicationController.root_context -%>/sessions/new">Log in</a></li> + <li><a href="<%= ApplicationController.root_context -%>/sessions/new"><%= message('app.view.layouts.layout.login', 'Log in') -%></a></li> <% end %> <li><a href="<%= ApplicationController.root_context -%>/profiles">Configuration</a></li> </ol> 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 new file mode 100644 index 00000000000..fe150e2b754 --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/ui/JRubyI18nTest.java @@ -0,0 +1,51 @@ +/* + * 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.server.ui; + +import org.hamcrest.core.Is; +import org.junit.Test; +import org.sonar.api.i18n.I18n; + +import java.util.Locale; + +import static org.hamcrest.core.IsNot.not; +import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +public class JRubyI18nTest { + @Test + public void shouldConvertLocales() { + assertThat(JRubyI18n.toLocale("fr"), Is.is(Locale.FRENCH)); + assertThat(JRubyI18n.toLocale("fr-CH"), Is.is(new Locale("fr", "CH"))); + } + + @Test + public void shouldCacheLocales() { + JRubyI18n i18n = new JRubyI18n(mock(I18n.class)); + assertThat(i18n.getLocalesByRubyKey().size(), Is.is(0)); + + i18n.getLocale("fr"); + + assertThat(i18n.getLocalesByRubyKey().size(), Is.is(1)); + assertThat(i18n.getLocalesByRubyKey().get("fr"), not(nullValue())); + + } +} |