From aa92267c3ae906a14383c26ef2610ae358807115 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Mon, 13 Jun 2011 10:40:52 +0200 Subject: [PATCH] SONAR-75 Apply first version of contribution by www.serli.com --- .../resources/org/sonar/i18n/core.properties | 436 ++++++++++++++++++ .../org/sonar/i18n/design.properties | 4 + plugins/sonar-i18n-plugin/pom.xml | 70 +++ .../org/sonar/plugins/i18n/I18nManager.java | 203 ++++++++ .../org/sonar/plugins/i18n/I18nPlugin.java | 36 ++ .../sonar/plugins/i18n/I18nWebService.java | 35 ++ .../sonar/plugins/i18n/InstalledPlugin.java | 46 ++ .../plugins/i18n/i18n_manager_controller.rb | 66 +++ .../sonar/plugins/i18n/I18nManagerTest.java | 121 +++++ .../sonar/plugins/i18n/I18nPluginTest.java | 33 ++ .../i18n/utils/FrenchLanguagePack.java | 39 ++ .../i18n/utils/QuebecLanguagePack.java | 39 ++ .../src/test/resources/FrenchPlugin.jar | Bin 0 -> 1058 bytes .../FrenchPlugin/META-INF/MANIFEST.MF | 3 + .../org/sonar/i18n/test_fr.properties | 4 + .../src/test/resources/QuebecPlugin.jar | Bin 0 -> 1051 bytes .../QuebecPlugin/META-INF/MANIFEST.MF | 3 + .../org/sonar/i18n/test_fr_CA.properties | 2 + .../src/test/resources/StandardPlugin.jar | Bin 0 -> 1068 bytes .../StandardPlugin/META-INF/MANIFEST.MF | 3 + .../org/sonar/i18n/test.properties | 4 + .../src/test/resources/logback-test.xml | 17 + pom.xml | 1 + sonar-application/pom.xml | 6 + .../src/main/assembly/conf/logback.xml | 4 + .../main/java/org/sonar/api/i18n/I18n.java | 158 +++++++ .../java/org/sonar/api/i18n/LanguagePack.java | 50 ++ sonar-server/pom.xml | 6 + .../org/sonar/server/platform/Platform.java | 2 + .../java/org/sonar/server/ui/JRubyFacade.java | 14 +- .../java/org/sonar/server/ui/JRubyI18n.java | 69 +++ .../app/controllers/api/server_controller.rb | 2 +- .../app/controllers/application_controller.rb | 2 +- .../app/controllers/system_controller.rb | 2 +- .../WEB-INF/app/helpers/application_helper.rb | 6 +- .../webapp/WEB-INF/app/helpers/i18n_helper.rb | 61 +++ .../WEB-INF/app/helpers/metrics_helper.rb | 8 +- .../main/webapp/WEB-INF/app/models/server.rb | 10 +- .../app/views/layouts/_layout.html.erb | 2 +- .../org/sonar/server/ui/JRubyI18nTest.java | 51 ++ 40 files changed, 1597 insertions(+), 21 deletions(-) create mode 100755 plugins/sonar-core-plugin/src/main/resources/org/sonar/i18n/core.properties create mode 100755 plugins/sonar-design-plugin/src/main/resources/org/sonar/i18n/design.properties create mode 100644 plugins/sonar-i18n-plugin/pom.xml create mode 100644 plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nManager.java create mode 100644 plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nPlugin.java create mode 100644 plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nWebService.java create mode 100644 plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/InstalledPlugin.java create mode 100644 plugins/sonar-i18n-plugin/src/main/resources/org/sonar/plugins/i18n/i18n_manager_controller.rb create mode 100644 plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nManagerTest.java create mode 100644 plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nPluginTest.java create mode 100644 plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/FrenchLanguagePack.java create mode 100644 plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/QuebecLanguagePack.java create mode 100644 plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin.jar create mode 100644 plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin/META-INF/MANIFEST.MF create mode 100644 plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin/org/sonar/i18n/test_fr.properties create mode 100644 plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin.jar create mode 100644 plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin/META-INF/MANIFEST.MF create mode 100644 plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties create mode 100644 plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin.jar create mode 100644 plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin/META-INF/MANIFEST.MF create mode 100644 plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin/org/sonar/i18n/test.properties create mode 100644 plugins/sonar-i18n-plugin/src/test/resources/logback-test.xml create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/i18n/I18n.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/i18n/LanguagePack.java create mode 100644 sonar-server/src/main/java/org/sonar/server/ui/JRubyI18n.java create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/helpers/i18n_helper.rb create mode 100644 sonar-server/src/test/java/org/sonar/server/ui/JRubyI18nTest.java 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 @@ + + + 4.0.0 + + sonar + org.codehaus.sonar + 2.9-SNAPSHOT + ../.. + + org.codehaus.sonar.plugins + sonar-i18n-plugin + sonar-plugin + 2.9-SNAPSHOT + Sonar :: Plugins :: I18n + Sonar I18n plugin + + + + org.codehaus.sonar + sonar-plugin-api + provided + + + org.codehaus.sonar + sonar-core + provided + + + + + org.codehaus.sonar + sonar-testing-harness + test + + + junit + junit + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.0.2 + + 1.5 + 1.5 + UTF-8 + + + + org.codehaus.sonar + sonar-packaging-maven-plugin + 1.1 + true + + i18n + org.sonar.plugins.i18n.I18nPlugin + I18n + + + + + + + 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 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 installedPlugins) { + Logs.INFO.info("Loading i18n bundles"); + Set 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 alreadyLoadedResources) { + String bundleBaseName = buildBundleBaseName(pluginKey); + String bundleDefaultPropertiesFile = bundleBaseName + ".properties"; + try { + LOG.debug("Search for ResourceBundle base file '" + bundleDefaultPropertiesFile + "' in the classloader : " + classloader); + List 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 keysToAdd = (Enumeration) 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 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 create(PluginRepository pluginRepository) { + List 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:
    " + + "
  • /api/plugins/i18n_manager/unknown_keys?format=text|json
  • " + + "
" + 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 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 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 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 getPluginKeys() { + return Arrays.asList("test"); + } + + @Override + public List 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 getPluginKeys() { + return Arrays.asList("test"); + } + + @Override + public List 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 new file mode 100644 index 0000000000000000000000000000000000000000..ab61bd88697b95679b6be178af00fc16f16b0949 GIT binary patch literal 1058 zcmWIWW@h1H0D(6(r|iHCD8b1f!{F;0;;8HC=cXST!pXqgE%(WHg8V1n(h6<{MwS=M z3=Ci*0H+ZwKqK;t(h<5h08JoTHy2cQaeiK65yFg3_{?ChMfC(fiW!-P7I_eZ_7U>R zoUk=E+m~PJumOe!2#X+xhOeWar<-eVh@P(-IAAhzUU`Esn%8_2^D@&?i%WFFQj3Z+ z^Yg3}4D}3nxtxnq6H8K4be$?e0%m#!@rLFK8jb}8IjIVsdC7X3yj;9oh(MJBdTA5O zw?rwRD?nHg(@P~lOXAat^a_gd3sQ?pGE+C2Ee8PshPRF&8YywGLJ|j>|M3~e!hmd?Gtf9lLLuEG zE)#85_{aZ4KcfD}CRP!bc+WGo2@ uIUq5U1G338fF^?i65@TZbFn23pf#Yxf#CsGHlX_$*nsdq&>&tW5Dx&ym*4XM literal 0 HcmV?d00001 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 new file mode 100644 index 0000000000000000000000000000000000000000..101df34ba6d2935b55d770ec69d8f2d5c6c658c8 GIT binary patch literal 1051 zcmWIWW@h1H0D-qPr|iHCD8b1f!{F;0;;8HC=cXST!pXqgEBDEFf&3@m(h6<{MwS=M z3=Ci*0H+ZwKqK;t(h<7ZNzu&()m@yQmso@_g9D!#!L_KK;72hd)6gOhVvsM;5WHTQ z6Sl@?`|?X2Ho(vTVG-oe@OAX_baM?3(erf!2TVrJEAMV#;GhMJZ(?3%T555LZdhtj zab|v=m4cz30WX(xQEFmIYKpE?B}l+b&mi8=TtUOJpdcqz!80#ePm`C6mkSZ7kw7nn zbB7r50$l;ZDwtj>0a_BDRuu2-s8>*wUyxczh0ogcbpmC7oLAps?C?+*RO@bvA z!scNn6l9kjMKKSWY=DM>k_{g3VkR1hp^QM|mNfDKDR}6iBq5;5pd^IHWXwc>Z1N1C m$)JFQcpvOs97zIb6DUbw*u=^PbRq*A5Hc|_Ffao#0|Nk($=7cH literal 0 HcmV?d00001 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 new file mode 100644 index 0000000000000000000000000000000000000000..73deac7e8b6af049a30b0593e2ee705547071aa0 GIT binary patch literal 1068 zcmWIWW@h1H0D(DSYiz&_D8b1f!{F;0;;8HC=cXST!pXp#k@L#CTkex@X$3a}Bg+eB z1_m$@05>A9=9C?>5iCF>@{7_Ty64M&@@>SYy9iA;7gTp~eqLe`#Ecb$%xFe4gCE6= zOhb!2h(X7I2I2JzC^WV&ztjN=5)c+a4h>&NKTkK;;1E4uH+%u(o0ylGmRek*8* zhQ~lJ-M&3xmKKnw4a9PoUMc}vqE}FqUyxc1;cz z^UUkqdC!fkf-yfUEvHtktbA!X^Qh#udn;8>N-kxMVF>VMWRhdXl{O@RE&%}nhPRF& z8Y!i)LQ)DwDnS^BnNE<6a|Rj*Ni{%|K&b|gNtkH{*`!9ONw5?IG!K-5@R*00evr*O zieer#H31C;r6xRvVx}dCp^QM|mNfDKDR}6iq%3>^i7Ra&n>+((GAJM+-UmAuBc%ZC Y0HqWR*R!$#9ml{1gnxjhNHKwU09;7wf&c&j literal 0 HcmV?d00001 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 @@ + + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index d10e9aba268..e48661061c7 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,7 @@ plugins/sonar-cpd-plugin plugins/sonar-squid-java-plugin plugins/sonar-design-plugin + plugins/sonar-i18n-plugin 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 @@ -208,6 +208,12 @@ ${project.version} runtime + + org.codehaus.sonar.plugins + sonar-i18n-plugin + ${project.version} + runtime + org.sonatype.jsw-binaries jsw-binaries 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 @@ + + + + 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 I18n Interface is the entry point for the internationalization of the Sonar application and plugins.
The corresponding implementation is located in the core I18n plugin. + *

+ * I18n is managed in Sonar through the use of key-based resource bundles. + *
+ * 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: + *

    + *
  • + * Title of View extensions (pages, tabs, widgets): + *
    view.view_id.title
    + * where view_id is the value returned by the View.getId() method + *
  • + *
    + *
  • + * Free text translation in View extensions (pages, tabs, widgets): + *
    view.view_id.key
    + * where: + *
      + *
    • + * view_id is the value returned by the View.getId() method + *
    • + *
    • + * key is the key of the text in the code of this view (rails page resource for a widget) + *
    • + *
    + *
  • + *
    + *
  • + * Free text translation in the files of the Rails web application: + *
    app.type.path.key
    + * where: + *
      + *
    • + * type is the type of rails application file ('controller', 'helper', 'model', 'view'). + *
    • + *
    • + * path is the path of the rails page related to the rails component type subdirectory ('controllers', 'views', etc ...). + *
    • + *
    • + * key is the key of the text in the code of this file + *
    • + *

    + * For example, searching for the login key in the rails file views/layouts/_layout.html.erb gives the following full i18n key: + *
    app.view.layouts.layout.login
    + *
  • + *
    + *
  • + * Metric name or description: + *
    metric.metric_key.field
    + * where: + *
      + *
    • + * metric_key is the key of the metric ('ncloc', etc ...), + *
    • + *
    • + * field is the field to translate on the metric ('name' or 'description'). + *
    • + *
    + *
  • + *
    + *
  • + * Metric domain: + *
    domain.domain_text
    + * where: + *
      + *
    • + * domain_text is the default (english) text of the domain as defined in the Metric object. + *
    • + *
    + *
  • + *
    + *
  • + * General columns which are not metrics (key, build date, etc ...): + *
    general_columns.column_key
    + * where: + *
      + *
    • + * column_key is the key of the column ('key', 'date', 'links', etc ...). + *
    • + *
    + *
  • + *
    + *
  • + * Rules: + *
    rule.repository_key.rule_key.field
    + * where: + *
      + *
    • + * repository_key is the key of the rule repository, + *
    • + *
    • + * rule_key is the key of the rule in the repository, + *
    • + *
    • + * field is the field to translate on the rule ('name' or 'description'). + *
    • + *
    + *
  • + *
    + *
  • + * Rule severities: + *
    severity.severity_text
    + * where: + *
      + *
    • + * severity_text is the upper_case text of the RulePriority enum value ('MAJOR', 'MINOR', etc ...). + *
    • + *
    + *
  • + *
+ * + * @since 2.9 + */ +public interface I18n extends ServerComponent, BatchComponent { + + /** + * Searches the message of the key for the locale in the list of available bundles. + *
+ * If not found in any bundle, defaultText 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 getPluginKeys(); + + /** + * @return the locales + */ + public abstract Collection 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 @@ ${project.version} provided
+ + org.codehaus.sonar.plugins + sonar-i18n-plugin + ${project.version} + provided + 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> 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 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 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]<" metrics_per_domain[domain].each do |m| selected_attr = " selected='selected'" if (m.key==selected_key || m.id==selected_key) - html += "" + html += "" end html += '' 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 @@ » Log out <% else %> -
  • Log in
  • +
  • <%= message('app.view.layouts.layout.login', 'Log in') -%>
  • <% end %>
  • Configuration
  • 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())); + + } +} -- 2.39.5