--- /dev/null
+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
+
--- /dev/null
+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
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">\r
+ <modelVersion>4.0.0</modelVersion>\r
+ <parent>\r
+ <artifactId>sonar</artifactId>\r
+ <groupId>org.codehaus.sonar</groupId>\r
+ <version>2.9-SNAPSHOT</version>\r
+ <relativePath>../..</relativePath>\r
+ </parent>\r
+ <groupId>org.codehaus.sonar.plugins</groupId>\r
+ <artifactId>sonar-i18n-plugin</artifactId>\r
+ <packaging>sonar-plugin</packaging>\r
+ <version>2.9-SNAPSHOT</version>\r
+ <name>Sonar :: Plugins :: I18n</name>\r
+ <description>Sonar I18n plugin</description>\r
+\r
+ <dependencies>\r
+ <dependency>\r
+ <groupId>org.codehaus.sonar</groupId>\r
+ <artifactId>sonar-plugin-api</artifactId>\r
+ <scope>provided</scope>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>org.codehaus.sonar</groupId>\r
+ <artifactId>sonar-core</artifactId>\r
+ <scope>provided</scope>\r
+ </dependency>\r
+\r
+ <!-- unit tests -->\r
+ <dependency>\r
+ <groupId>org.codehaus.sonar</groupId>\r
+ <artifactId>sonar-testing-harness</artifactId>\r
+ <scope>test</scope>\r
+ </dependency>\r
+ <dependency>\r
+ <groupId>junit</groupId>\r
+ <artifactId>junit</artifactId>\r
+ <scope>test</scope>\r
+ </dependency>\r
+ </dependencies>\r
+\r
+ <build>\r
+ <plugins>\r
+ <plugin>\r
+ <groupId>org.apache.maven.plugins</groupId>\r
+ <artifactId>maven-compiler-plugin</artifactId>\r
+ <version>2.0.2</version>\r
+ <configuration>\r
+ <source>1.5</source>\r
+ <target>1.5</target>\r
+ <encoding>UTF-8</encoding>\r
+ </configuration>\r
+ </plugin>\r
+ <plugin>\r
+ <groupId>org.codehaus.sonar</groupId>\r
+ <artifactId>sonar-packaging-maven-plugin</artifactId>\r
+ <version>1.1</version>\r
+ <extensions>true</extensions>\r
+ <configuration>\r
+ <pluginKey>i18n</pluginKey>\r
+ <pluginClass>org.sonar.plugins.i18n.I18nPlugin</pluginClass>\r
+ <pluginName>I18n</pluginName>\r
+ <pluginDescription>\r
+ <![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>\r
+ </configuration>\r
+ </plugin>\r
+ </plugins>\r
+ </build>\r
+</project>\r
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+/*\r
+ * Sonar, open source software quality management tool.\r
+ * Copyright (C) 2008-2011 SonarSource\r
+ * mailto:contact AT sonarsource DOT com\r
+ *\r
+ * Sonar is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU Lesser General Public\r
+ * License as published by the Free Software Foundation; either\r
+ * version 3 of the License, or (at your option) any later version.\r
+ *\r
+ * Sonar is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+ * Lesser General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU Lesser General Public\r
+ * License along with Sonar; if not, write to the Free Software\r
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02\r
+ */\r
+package org.sonar.plugins.i18n;\r
+\r
+import org.sonar.api.SonarPlugin;\r
+\r
+import java.util.Arrays;\r
+import java.util.List;\r
+\r
+/**\r
+ * @since 2.9\r
+ */\r
+public final class I18nPlugin extends SonarPlugin {\r
+\r
+ public List getExtensions() {\r
+ return Arrays.asList(I18nManager.class, I18nWebService.class);\r
+ }\r
+\r
+}\r
--- /dev/null
+/*
+ * 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";
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+#
+# 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
--- /dev/null
+/*
+ * 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;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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));
+ }
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+Manifest-Version: 1.0\r
+Created-By: 1.6.0_17 (Apple Inc.)\r
+\r
--- /dev/null
+it=Il
+is=fait
+cold=froid
+
--- /dev/null
+Manifest-Version: 1.0\r
+Created-By: 1.6.0_17 (Apple Inc.)\r
+\r
--- /dev/null
+cold=frette
+
--- /dev/null
+Manifest-Version: 1.0\r
+Created-By: 1.6.0_17 (Apple Inc.)\r
+\r
--- /dev/null
+it=It
+is=is
+cold=cold
+only.english=Ketchup
--- /dev/null
+<?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
<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>
<version>${project.version}</version>
<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>
<level value="INFO"/>
</logger>
+ <logger name="org.sonar.plugins.i18n.I18nManager">
+ <level value="INFO"/>
+ </logger>
+
<root>
<level value="WARN"/>
<appender-ref ref="SONAR_FILE"/>
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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();
+}
<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>
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;
/**
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();
}
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;
public final class JRubyFacade {
private static final JRubyFacade SINGLETON = new JRubyFacade();
+ private JRubyI18n i18n;
public static JRubyFacade getInstance() {
return SINGLETON;
}
-
-
-
public List<ViewProxy<Widget>> getWidgets(String resourceScope, String resourceQualifier, String resourceLanguage) {
return getContainer().getComponent(Views.class).getWidgets(resourceScope, resourceQualifier, resourceLanguage);
}
}
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);
}
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();
}
--- /dev/null
+/*
+ * 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);
+ }
+}
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)},
end
def java_facade
- @java_facade ||= Java::OrgSonarServerUi::JRubyFacade.new
+ Java::OrgSonarServerUi::JRubyFacade.getInstance()
end
def available_locales
before_filter :admin_required
def index
- @server=Server.new(java_facade)
+ @server=Server.new
respond_to do |format|
format.html
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)
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
--- /dev/null
+# 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
#
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
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
#
class Server
- def initialize(java_facade)
- @java_facade=java_facade
- end
-
def info
system_info + sonar_info + system_statistics + sonar_plugins + system_properties
end
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)}
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
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
» <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>
--- /dev/null
+/*
+ * 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()));
+
+ }
+}