]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-75 Apply first version of contribution by www.serli.com
authorSimon Brandhof <simon.brandhof@gmail.com>
Mon, 13 Jun 2011 08:40:52 +0000 (10:40 +0200)
committerSimon Brandhof <simon.brandhof@gmail.com>
Mon, 13 Jun 2011 08:40:52 +0000 (10:40 +0200)
40 files changed:
plugins/sonar-core-plugin/src/main/resources/org/sonar/i18n/core.properties [new file with mode: 0755]
plugins/sonar-design-plugin/src/main/resources/org/sonar/i18n/design.properties [new file with mode: 0755]
plugins/sonar-i18n-plugin/pom.xml [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nManager.java [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nPlugin.java [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nWebService.java [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/InstalledPlugin.java [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/main/resources/org/sonar/plugins/i18n/i18n_manager_controller.rb [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nManagerTest.java [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nPluginTest.java [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/FrenchLanguagePack.java [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/QuebecLanguagePack.java [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin.jar [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin/META-INF/MANIFEST.MF [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin/org/sonar/i18n/test_fr.properties [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin.jar [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin/META-INF/MANIFEST.MF [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin.jar [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin/META-INF/MANIFEST.MF [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin/org/sonar/i18n/test.properties [new file with mode: 0644]
plugins/sonar-i18n-plugin/src/test/resources/logback-test.xml [new file with mode: 0644]
pom.xml
sonar-application/pom.xml
sonar-application/src/main/assembly/conf/logback.xml
sonar-plugin-api/src/main/java/org/sonar/api/i18n/I18n.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/i18n/LanguagePack.java [new file with mode: 0644]
sonar-server/pom.xml
sonar-server/src/main/java/org/sonar/server/platform/Platform.java
sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
sonar-server/src/main/java/org/sonar/server/ui/JRubyI18n.java [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/controllers/api/server_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/controllers/system_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb
sonar-server/src/main/webapp/WEB-INF/app/helpers/i18n_helper.rb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/helpers/metrics_helper.rb
sonar-server/src/main/webapp/WEB-INF/app/models/server.rb
sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
sonar-server/src/test/java/org/sonar/server/ui/JRubyI18nTest.java [new file with mode: 0644]

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 (executable)
index 0000000..7f77fa4
--- /dev/null
@@ -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 (executable)
index 0000000..c3af9b6
--- /dev/null
@@ -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 (file)
index 0000000..c9390e6
--- /dev/null
@@ -0,0 +1,70 @@
+<?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
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 (file)
index 0000000..6846f88
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.i18n;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections.EnumerationUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.ServerExtension;
+import org.sonar.api.i18n.I18n;
+import org.sonar.api.i18n.LanguagePack;
+import org.sonar.api.platform.PluginRepository;
+import org.sonar.api.utils.Logs;
+import org.sonar.api.utils.SonarException;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.MessageFormat;
+import java.util.*;
+
+public final class I18nManager implements I18n, ServerExtension, BatchExtension {
+
+  private static final Logger LOG = LoggerFactory.getLogger(I18nManager.class);
+  public static final String packagePathToSearchIn = "org/sonar/i18n";
+
+  private PluginRepository pluginRepository;
+  private LanguagePack[] languagePacks;
+  private Map<String, String> keys = Maps.newHashMap();
+  private Properties unknownKeys = new Properties();
+  private BundleClassLoader bundleClassLoader = new BundleClassLoader();
+
+  public I18nManager(PluginRepository pluginRepository, LanguagePack[] languagePacks) {
+    this.pluginRepository = pluginRepository;
+    this.languagePacks = languagePacks;
+  }
+
+  public I18nManager(PluginRepository pluginRepository) {
+    this(pluginRepository, new LanguagePack[0]);
+  }
+
+
+  public void start() {
+    doStart(InstalledPlugin.create(pluginRepository));
+  }
+
+  void doStart(List<InstalledPlugin> installedPlugins) {
+    Logs.INFO.info("Loading i18n bundles");
+    Set<URI> alreadyLoadedResources = Sets.newHashSet();
+    for (InstalledPlugin plugin : installedPlugins) {
+      searchAndStoreBundleNames(plugin.key, plugin.classloader, alreadyLoadedResources);
+    }
+    for (LanguagePack pack : languagePacks) {
+      addLanguagePack(pack);
+    }
+  }
+
+  private void addLanguagePack(LanguagePack languagePack) {
+    LOG.debug("Search for bundles in language pack : {}", languagePack);
+    for (String pluginKey : languagePack.getPluginKeys()) {
+      String bundleBaseName = buildBundleBaseName(pluginKey);
+      for (Locale locale : languagePack.getLocales()) {
+        String bundlePropertiesFile = new StringBuilder(bundleBaseName).append('_').append(locale.toString()).append(".properties").toString();
+        ClassLoader classloader = languagePack.getClass().getClassLoader();
+        LOG.info("Adding locale {} for bundleName : {} from classloader : {}", new Object[]{locale, bundleBaseName, classloader});
+        bundleClassLoader.addResource(bundlePropertiesFile, classloader);
+      }
+    }
+  }
+
+  private String buildBundleBaseName(String pluginKey) {
+    return packagePathToSearchIn + "/" + pluginKey;
+  }
+
+  private void searchAndStoreBundleNames(String pluginKey, ClassLoader classloader, Set<URI> alreadyLoadedResources) {
+    String bundleBaseName = buildBundleBaseName(pluginKey);
+    String bundleDefaultPropertiesFile = bundleBaseName + ".properties";
+    try {
+      LOG.debug("Search for ResourceBundle base file '" + bundleDefaultPropertiesFile + "' in the classloader : " + classloader);
+      List<URL> resources = EnumerationUtils.toList(classloader.getResources(bundleDefaultPropertiesFile));
+      if (resources.size() > 0) {
+        if (resources.size() > 1) {
+          LOG.warn("File '{}' found several times in the classloader : {}. Only the first one will be taken in account.",
+              bundleDefaultPropertiesFile, classloader);
+        }
+
+        URL propertiesUrl = resources.get(0);
+        if (!alreadyLoadedResources.contains(propertiesUrl.toURI())) {
+          LOG.debug("Found the ResourceBundle base file : {} from classloader : {}", propertiesUrl, classloader);
+          LOG.info("Add bundleName : {} from classloader : {}", bundleBaseName, classloader);
+          bundleClassLoader.addResource(bundleDefaultPropertiesFile, classloader);
+          alreadyLoadedResources.add(propertiesUrl.toURI());
+
+          Properties bundleContent = new Properties();
+          InputStream input = null;
+          try {
+            input = propertiesUrl.openStream();
+            bundleContent.load(input);
+            Enumeration<String> keysToAdd = (Enumeration<String>) bundleContent.propertyNames();
+            while (keysToAdd.hasMoreElements()) {
+              String key = keysToAdd.nextElement();
+              if (keys.containsKey(key)) {
+                LOG.warn("DUPLICATE KEY : Key '{}' defined in bundle '{}' is already defined in bundle '{}'. It is ignored.", new Object[]{
+                    key, bundleBaseName, keys.get(key)});
+              } else {
+                keys.put(key, bundleBaseName);
+              }
+            }
+          } finally {
+            IOUtils.closeQuietly(input);
+          }
+        }
+      }
+    } catch (Exception e) {
+      LOG.error("Fail to load '" + bundleDefaultPropertiesFile + "' in classloader : " + classloader, e);
+      throw new SonarException("Fail to load '" + bundleDefaultPropertiesFile + "' in classloader : " + classloader, e);
+    }
+  }
+
+  public String message(final Locale locale, final String key, final String defaultText, final Object... objects) {
+    String result = defaultText;
+    try {
+      String bundleBaseName = keys.get(key);
+      if (bundleBaseName == null) {
+        LOG.warn("UNKNOWN KEY : Key '{}' not found in any bundle. Default value '{}' is returned.", key, defaultText);
+        unknownKeys.put(key, defaultText);
+      } else {
+        try {
+          ResourceBundle bundle = ResourceBundle.getBundle(bundleBaseName, locale, bundleClassLoader);
+
+          String value = bundle.getString(key);
+          if ("".equals(value)) {
+            LOG.warn("VOID KEY : Key '{}' (from bundle '{}') returns a void value. Default value '{}' is returned.", new Object[]{key,
+                bundleBaseName, defaultText});
+          } else {
+            result = value;
+          }
+        } catch (MissingResourceException e) {
+          LOG.warn("BUNDLE NOT LOADED : Failed loading bundle {} from classloader {}. Default value '{}' is returned.", new Object[]{
+              bundleBaseName, bundleClassLoader, defaultText});
+        }
+      }
+    } catch (Exception e) {
+      LOG.error("Exception when retrieving I18n string.", e);
+    }
+
+    if (objects.length > 0) {
+      LOG.debug("Translation : {}, {}, {}, {}", new String[]{locale.toString(), key, defaultText, Arrays.deepToString(objects)});
+      return MessageFormat.format(result, objects);
+    } else {
+      return result;
+    }
+  }
+
+  /**
+   * @return the unknownKeys
+   */
+  public Properties getUnknownKeys() {
+    return unknownKeys;
+  }
+
+
+  private static class BundleClassLoader extends URLClassLoader {
+    private Map<String, ClassLoader> resources = Maps.newHashMap();
+
+    public BundleClassLoader() {
+      super(new URL[]{}, null);
+    }
+
+    public void addResource(String resourceName, ClassLoader classloader) {
+      resources.put(resourceName, classloader);
+    }
+
+    @Override
+    public URL findResource(String name) {
+      if (resources.containsKey(name)) {
+        return resources.get(name).getResource(name);
+      }
+      return null;
+    }
+  }
+}
diff --git a/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nPlugin.java b/plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nPlugin.java
new file mode 100644 (file)
index 0000000..0e6495c
--- /dev/null
@@ -0,0 +1,36 @@
+/*\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
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 (file)
index 0000000..3584e67
--- /dev/null
@@ -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 (file)
index 0000000..d0348df
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.i18n;
+
+import com.google.common.collect.Lists;
+import org.sonar.api.Plugin;
+import org.sonar.api.platform.PluginMetadata;
+import org.sonar.api.platform.PluginRepository;
+
+import java.util.List;
+
+class InstalledPlugin {
+  String key;
+  ClassLoader classloader;
+
+  InstalledPlugin(String key, ClassLoader classloader) {
+    this.key = key;
+    this.classloader = classloader;
+  }
+
+  static List<InstalledPlugin> create(PluginRepository pluginRepository) {
+    List<InstalledPlugin> result = Lists.newArrayList();
+    for (PluginMetadata metadata : pluginRepository.getMetadata()) {
+      Plugin entryPoint = pluginRepository.getPlugin(metadata.getKey());
+      result.add(new InstalledPlugin(metadata.getKey(), entryPoint.getClass().getClassLoader()));
+    }
+    return result;
+  }
+}
diff --git a/plugins/sonar-i18n-plugin/src/main/resources/org/sonar/plugins/i18n/i18n_manager_controller.rb b/plugins/sonar-i18n-plugin/src/main/resources/org/sonar/plugins/i18n/i18n_manager_controller.rb
new file mode 100644 (file)
index 0000000..e3f7ee5
--- /dev/null
@@ -0,0 +1,66 @@
+#
+# Sonar, entreprise quality control tool.
+# Copyright (C) 2008-2011 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# Sonar is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 3 of the License, or (at your option) any later version.
+#
+# Sonar is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with Sonar; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+#
+
+require "json"
+
+class Api::I18nManagerController < Api::ApiController
+
+  def index
+      render :text => "Use one of the following:<br><ul>" + 
+                      "<li>/api/plugins/i18n_manager/unknown_keys?format=text|json</li>" +
+                      "</ul>"
+  end
+  
+  # GET /api/plugins/i18n_manager/unknown_keys
+  # Examples :
+  #   curl -v http://localhost:9000/api/plugins/i18n_manager -u admin:admin
+  #
+  
+  def unknown_keys
+    begin
+      output = ""
+      properties = i18n_manager.unknown_keys
+      
+      properties.keys.sort.each {|key| output += "#{key}=#{properties[key]}\n" }
+      
+      output = "# No unknown keys" if output.empty?
+
+      respond_to do |format|
+        format.json { render :json => JSON(properties) }
+        format.xml  { render :xml => xml_not_supported }
+        format.text { render :text => output }
+      end
+
+    rescue ApiException => e
+      render_error(e.msg, e.code)
+      
+    rescue Exception => e
+      logger.error("Fails to execute #{request.url} : #{e.message}")
+      render_error(e.message)
+    end
+  end
+  
+  private
+
+  def i18n_manager
+    java_facade.getComponentByClassname('i18n', 'org.sonar.plugins.i18n.I18nManager')
+  end
+  
+end
\ No newline at end of file
diff --git a/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nManagerTest.java b/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nManagerTest.java
new file mode 100644 (file)
index 0000000..f4d0757
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.i18n;
+
+import com.google.common.collect.Lists;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.i18n.LanguagePack;
+import org.sonar.api.platform.PluginRepository;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class I18nManagerTest {
+
+  public static String TEST_PLUGIN_CLASS_NAME = "org.sonar.plugins.i18n.utils.StandardPlugin";
+  public static String FRENCH_PACK_CLASS_NAME = "org.sonar.plugins.i18n.utils.FrenchLanguagePack";
+  public static String QUEBEC_PACK_CLASS_NAME = "org.sonar.plugins.i18n.utils.QuebecLanguagePack";
+
+  private static URL classSource = I18nManagerTest.class.getProtectionDomain().getCodeSource().getLocation();
+  private I18nManager manager;
+
+  @Before
+  public void createManager() throws Exception {
+    List<InstalledPlugin> plugins = Lists.newArrayList(
+        new InstalledPlugin("test", new TestClassLoader(getClass().getClassLoader().getResource("StandardPlugin.jar"))),
+        new InstalledPlugin("fake1", getClass().getClassLoader()),
+        new InstalledPlugin("fake2", getClass().getClassLoader())
+    );
+
+    TestClassLoader frenchPackClassLoader = new TestClassLoader(getClass().getClassLoader().getResource("FrenchPlugin.jar"));
+    LanguagePack frenchPack = (LanguagePack) frenchPackClassLoader.loadClass(FRENCH_PACK_CLASS_NAME).newInstance();
+
+    TestClassLoader quebecPackClassLoader = new TestClassLoader(getClass().getClassLoader().getResource("QuebecPlugin.jar"));
+    LanguagePack quebecPack = (LanguagePack) quebecPackClassLoader.loadClass(QUEBEC_PACK_CLASS_NAME).newInstance();
+
+    manager = new I18nManager(mock(PluginRepository.class), new LanguagePack[]{frenchPack, quebecPack});
+    manager.doStart(plugins);
+  }
+
+  @Test
+  public void shouldTranslateWithoutRegionalVariant() {
+    List<String> sentence = Arrays.asList("it", "is", "cold");
+    String result = "";
+    for (String token : sentence) {
+      result += manager.message(Locale.FRENCH, token, token) + " ";
+    }
+    assertEquals("Il fait froid ", result);
+  }
+
+  @Test
+  public void shouldTranslateWithRegionalVariant() {
+    // it & is are taken from the french language pack
+    // and cold is taken from the quebec language pack
+    List<String> sentence = Arrays.asList("it", "is", "cold");
+    String result = "";
+    for (String token : sentence) {
+      result += manager.message(Locale.CANADA_FRENCH, token, token) + " ";
+    }
+    assertEquals("Il fait frette ", result);
+  }
+
+  @Test
+  public void shouldTranslateReturnsDefaultBundleValue() {
+    String result = manager.message(Locale.FRENCH, "only.english", "Default");
+    assertEquals("Ketchup", result);
+  }
+
+  @Test
+  public void shouldTranslateUnknownValue() {
+    String result = manager.message(Locale.FRENCH, "unknown", "Default value for Unknown");
+    assertEquals("Default value for Unknown", result);
+    assertEquals(1, manager.getUnknownKeys().size());
+    assertEquals("Default value for Unknown", manager.getUnknownKeys().getProperty("unknown"));
+  }
+
+  public static class TestClassLoader extends URLClassLoader {
+    public TestClassLoader(URL url) {
+      super(new URL[]{url, classSource}, Thread.currentThread().getContextClassLoader());
+    }
+
+    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+      Class c = findLoadedClass(name);
+      if (c == null) {
+        if (name.equals(TEST_PLUGIN_CLASS_NAME) || name.equals(QUEBEC_PACK_CLASS_NAME) || name.equals(FRENCH_PACK_CLASS_NAME)) {
+          c = findClass(name);
+        } else {
+          return super.loadClass(name, resolve);
+        }
+      }
+      if (resolve) {
+        resolveClass(c);
+      }
+      return c;
+    }
+  }
+
+}
diff --git a/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nPluginTest.java b/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nPluginTest.java
new file mode 100644 (file)
index 0000000..9fc0071
--- /dev/null
@@ -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 (file)
index 0000000..98c0978
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.i18n.utils;
+
+import org.sonar.api.i18n.LanguagePack;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+public class FrenchLanguagePack extends LanguagePack {
+
+  @Override
+  public List<String> getPluginKeys() {
+    return Arrays.asList("test");
+  }
+
+  @Override
+  public List<Locale> getLocales() {
+    return Arrays.asList(Locale.FRENCH);
+  }
+}
\ No newline at end of file
diff --git a/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/QuebecLanguagePack.java b/plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/QuebecLanguagePack.java
new file mode 100644 (file)
index 0000000..5e9913e
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.i18n.utils;
+
+import org.sonar.api.i18n.LanguagePack;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+public class QuebecLanguagePack extends LanguagePack {
+
+  @Override
+  public List<String> getPluginKeys() {
+    return Arrays.asList("test");
+  }
+
+  @Override
+  public List<Locale> getLocales() {
+    return Arrays.asList(Locale.CANADA_FRENCH);
+  }
+}
\ No newline at end of file
diff --git a/plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin.jar b/plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin.jar
new file mode 100644 (file)
index 0000000..ab61bd8
Binary files /dev/null and b/plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin.jar differ
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 (file)
index 0000000..a45029d
--- /dev/null
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0\r
+Created-By: 1.6.0_17 (Apple Inc.)\r
+\r
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 (file)
index 0000000..cc8efa7
--- /dev/null
@@ -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 (file)
index 0000000..101df34
Binary files /dev/null and b/plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin.jar differ
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 (file)
index 0000000..a45029d
--- /dev/null
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0\r
+Created-By: 1.6.0_17 (Apple Inc.)\r
+\r
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 (file)
index 0000000..38b5b84
--- /dev/null
@@ -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 (file)
index 0000000..73deac7
Binary files /dev/null and b/plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin.jar differ
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 (file)
index 0000000..a45029d
--- /dev/null
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0\r
+Created-By: 1.6.0_17 (Apple Inc.)\r
+\r
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 (file)
index 0000000..88aabfe
--- /dev/null
@@ -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 (file)
index 0000000..cdc8866
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<configuration>
+  <appender name="STDOUT"
+            class="ch.qos.logback.core.ConsoleAppender">
+    <layout class="ch.qos.logback.classic.PatternLayout">
+      <pattern>
+        %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n
+      </pattern>
+    </layout>
+  </appender>
+
+  <root>
+    <level value="INFO"/>
+    <appender-ref ref="STDOUT"/>
+  </root>
+</configuration>
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index d10e9aba2682afc4171101b382aa487983f436f5..e48661061c74c2330da68f320da252f303035f6e 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -47,6 +47,7 @@
     <module>plugins/sonar-cpd-plugin</module>
     <module>plugins/sonar-squid-java-plugin</module>
     <module>plugins/sonar-design-plugin</module>
+    <module>plugins/sonar-i18n-plugin</module>
   </modules>
 
   <organization>
index 6e83c28617d94f337d6f1484dc0d6aa15cffd8d2..da36b40433563762f4e18dff6d349c17b7945692 100644 (file)
       <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>
index 407cbdbe265b5fe00c802ea4c00647350365d6ee..c11fca34b93398c13db262e83ee72490aea3dba1 100644 (file)
     <level value="INFO"/>
   </logger>
 
+  <logger name="org.sonar.plugins.i18n.I18nManager">
+    <level value="INFO"/>
+  </logger>
+
   <root>
     <level value="WARN"/>
     <appender-ref ref="SONAR_FILE"/>
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/i18n/I18n.java b/sonar-plugin-api/src/main/java/org/sonar/api/i18n/I18n.java
new file mode 100644 (file)
index 0000000..13c989f
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.api.i18n;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.api.ServerComponent;
+
+import java.util.Locale;
+
+/**
+ * 
+ * The <code>I18n</code> Interface is the entry point for the internationalization of the Sonar application and plugins.<br>The corresponding implementation is located in the core I18n plugin. 
+ * <p/>
+ * I18n is managed in Sonar through the use of key-based resource bundles.
+ * <br>
+ * Though any key can be used, the following key-naming conventions, which are applied in the Sonar application and core plugins, are given as guidelines:
+ * <ul>
+ * <li>
+ * Title of <code>View</code> extensions (pages, tabs, widgets): 
+ * <blockquote><code>view.<i>view_id</i>.title</code></blockquote>
+ * where <i>view_id</i> is the value returned by the <code>View.getId()</code> method
+ * </li>
+ * <br>
+ * <li>
+ * Free text translation in <code>View</code> extensions (pages, tabs, widgets):
+ * <blockquote><code>view.<i>view_id</i>.<i>key</i></code></blockquote>
+ * where:
+ *  <ul>
+ *    <li>
+ *      <code><i>view_id</i></code> is the value returned by the <code>View.getId()</code> method
+ *    </li>
+ *    <li>
+ *      <code><i>key</i></code> is the key of the text in the code of this view (rails page resource for a widget)
+ *    </li>  
+ *  </ul>
+ * </li>
+ * <br>
+ * <li>
+ * Free text translation in the files of the Rails web application:
+ * <blockquote><code>app.<i>type</i>.<i>path</i>.<i>key</i></code></blockquote>
+ * where:
+ *  <ul>
+ *    <li>
+ *      <code><i>type</i></code> is the type of rails application file ('controller', 'helper', 'model', 'view').
+ *    </li>
+ *    <li>
+ *      <code><i>path</i></code> is the path of the rails page related to the rails component type subdirectory ('controllers', 'views', etc ...).
+ *    </li>
+ *    <li>
+ *      <code><i>key</i></code> is the key of the text in the code of this file
+ *    </li>  
+ *  </ul><br>
+ *  For example, searching for the <code>login</code> key in the rails file <code>views/layouts/_layout.html.erb</code> gives the following full i18n key:
+ *  <blockquote><code>app.view.layouts.layout.login</code></blockquote>
+ * </li>
+ * <br>
+ * <li>
+ * Metric name or description:
+ * <blockquote><code>metric.<i>metric_key</i>.<i>field</i></code></blockquote>
+ * where:
+ *  <ul>
+ *    <li>
+ *      <code><i>metric_key</i></code> is the key of the metric ('ncloc', etc ...),
+ *    </li>
+ *    <li>
+ *      <code><i>field</i></code> is the field to translate on the metric ('name' or 'description').
+ *    </li>
+ *  </ul>
+ * </li>
+ * <br>
+ * <li>
+ * Metric domain:
+ * <blockquote><code>domain.<i>domain_text</i></code></blockquote>
+ * where:
+ *  <ul>
+ *    <li>
+ *      <code><i>domain_text</i></code> is the default (english) text of the domain as defined in the Metric object.
+ *    </li>
+ *  </ul>
+ * </li>
+ * <br>
+ * <li>
+ * General columns which are not metrics (key, build date, etc ...):
+ * <blockquote><code>general_columns.<i>column_key</i></code></blockquote>
+ * where:
+ *  <ul>
+ *    <li>
+ *      <code><i>column_key</i></code> is the key of the column ('key', 'date', 'links', etc ...).
+ *    </li>
+ *  </ul>
+ * </li>
+ * <br>
+ * <li>
+ * Rules:
+ * <blockquote><code>rule.<i>repository_key</i>.<i>rule_key</i>.<i>field</i></code></blockquote>
+ * where:
+ *  <ul>
+ *    <li>
+ *      <code><i>repository_key</i></code> is the key of the rule repository,
+ *    </li>
+ *    <li>
+ *      <code><i>rule_key</i></code> is the key of the rule in the repository,
+ *    </li>
+ *    <li>
+ *      <code><i>field</i></code> is the field to translate on the rule ('name' or 'description').
+ *    </li>
+ *  </ul>
+ * </li>
+ * <br>
+ * <li>
+ * Rule severities:
+ * <blockquote><code>severity.<i>severity_text</i></code></blockquote>
+ * where:
+ *  <ul>
+ *    <li>
+ *      <code><i>severity_text</i></code> is the upper_case text of the RulePriority enum value ('MAJOR', 'MINOR', etc ...).
+ *    </li>
+ *  </ul>
+ * </li>
+ * </ul>
+ *
+ * @since 2.9
+ */
+public interface I18n extends ServerComponent, BatchComponent {
+
+  /**
+   * Searches the message of the <code>key</code> for the <code>locale</code> in the list of available bundles.
+   * <br>
+   * If not found in any bundle, <code>defaultText</code> is returned.
+   * 
+   * If additional parameters are given (in the objects list), the result is used as a message pattern 
+   * to use in a MessageFormat object along with the given parameters.  
+   *
+   * @param locale the locale to translate into
+   * @param key the key of the pattern to translate
+   * @param defaultValue the default pattern returned when the key is not found in any bundle
+   * @param parameters the parameters used to format the message from the translated pattern.
+   * @return the message formatted with the translated pattern and the given parameters 
+   */
+  public abstract String message(final Locale locale, final String key, final String defaultValue, final Object... parameters);
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/i18n/LanguagePack.java b/sonar-plugin-api/src/main/java/org/sonar/api/i18n/LanguagePack.java
new file mode 100644 (file)
index 0000000..719a493
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.api.i18n;
+
+import org.sonar.api.BatchExtension;
+import org.sonar.api.ServerExtension;
+
+import java.util.Collection;
+import java.util.Locale;
+
+/**
+ * @since 2.9
+ */
+public abstract class LanguagePack implements ServerExtension, BatchExtension {
+
+  @Override
+  public String toString() {
+    return new StringBuilder("Language Pack (")
+        .append(getPluginKeys().toString())
+        .append(getLocales().toString())
+        .append(')').toString();
+  }
+
+  /**
+   * @return the pluginKeys
+   */
+  public abstract Collection<String> getPluginKeys();
+
+  /**
+   * @return the locales
+   */
+  public abstract Collection<Locale> getLocales();
+}
index a89e43411ea7be781d9e4469ff15020479c01a2b..8b65aa8e98c60278dbcfaa5776c42668c2cdfea7 100644 (file)
           <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>
 
index 511d71a7f8ef89755f5075f73193c3b50b4c520e..3f3ee2f382c945b48b6fdb0585a0ba53af6319f6 100644 (file)
@@ -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();
   }
index 2992bba858537c92e1efff1454022549c2f1e49e..7e1e99c67ccc2d6a2e6bdea019e2aab1608e6521 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.server.ui;
 import org.apache.commons.configuration.Configuration;
 import org.picocontainer.PicoContainer;
 import org.slf4j.LoggerFactory;
-import org.sonar.api.Plugins;
 import org.sonar.api.Property;
 import org.sonar.api.platform.PluginMetadata;
 import org.sonar.api.platform.PluginRepository;
@@ -58,6 +57,7 @@ import java.util.Set;
 public final class JRubyFacade {
 
   private static final JRubyFacade SINGLETON = new JRubyFacade();
+  private JRubyI18n i18n;
 
   public static JRubyFacade getInstance() {
     return SINGLETON;
@@ -130,9 +130,6 @@ public final class JRubyFacade {
   }
 
 
-
-  
-
   public List<ViewProxy<Widget>> getWidgets(String resourceScope, String resourceQualifier, String resourceLanguage) {
     return getContainer().getComponent(Views.class).getWidgets(resourceScope, resourceQualifier, resourceLanguage);
   }
@@ -244,7 +241,7 @@ public final class JRubyFacade {
   }
 
   public void ruleSeverityChanged(int parentProfileId, int activeRuleId, int oldSeverityId, int newSeverityId, String userName) {
-    getProfilesManager().ruleSeverityChanged(parentProfileId, activeRuleId, RulePriority.values()[oldSeverityId], 
+    getProfilesManager().ruleSeverityChanged(parentProfileId, activeRuleId, RulePriority.values()[oldSeverityId],
         RulePriority.values()[newSeverityId], userName);
   }
 
@@ -317,6 +314,13 @@ public final class JRubyFacade {
     return component;
   }
 
+  public String getI18nMessage(String rubyLocale, String key, String defaultValue, Object... parameters) {
+    if (i18n == null) {
+      i18n = getContainer().getComponent(JRubyI18n.class);
+    }
+    return i18n.message(rubyLocale, key, defaultValue, parameters);
+  }
+
   public PicoContainer getContainer() {
     return Platform.getInstance().getContainer();
   }
diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyI18n.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyI18n.java
new file mode 100644 (file)
index 0000000..72a1d0e
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.server.ui;
+
+import com.google.common.collect.Maps;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.i18n.I18n;
+
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Bridge between JRuby webapp and Java I18n component
+ */
+public final class JRubyI18n implements ServerComponent {
+
+  private I18n i18n;
+  private Map<String,Locale> localesByRubyKey = Maps.newHashMap();
+
+  public JRubyI18n(I18n i18n) {
+    this.i18n = i18n;
+  }
+
+  Locale getLocale(String rubyKey) {
+    Locale locale = localesByRubyKey.get(rubyKey);
+    if (locale == null) {
+      locale = toLocale(rubyKey);
+      localesByRubyKey.put(rubyKey, locale);
+    }
+    return locale;
+  }
+
+  Map<String, Locale> getLocalesByRubyKey() {
+    return localesByRubyKey;
+  }
+
+  static Locale toLocale(String rubyKey) {
+    Locale locale;
+    String[] fields = StringUtils.split(rubyKey, "-");
+    if (fields.length==1) {
+      locale = new Locale(fields[0]);
+    } else {
+      locale = new Locale(fields[0], fields[1]);
+    }
+    return locale;
+  }
+
+  public String message(String rubyLocale, String key, String defaultValue, Object... parameters) {
+    return i18n.message(getLocale(rubyLocale), key, defaultValue, parameters);
+  }
+}
index 00d93e56597fd39950c67d08b4fa5707137f7d3c..fe5697b7f76a503b525e91dff961d9d5d253545e 100644 (file)
@@ -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)},
index cc87fe0dc8620dc470cf5ba9a6ea0e0ff726ff88..af52d5bf243051f8e6ba3fc1f720288475bd073f 100644 (file)
@@ -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
index dcbc97240873b6ea139926709844e6fffde65558..e836b77eed0889c1165ffc3492d93f14944b9cf4 100644 (file)
@@ -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
index 2922bcf0c92b049fd30e7eb0cf6e177ade196937..3704dbf3e2c379a8c041efb64cbdb27f1780cecb 100644 (file)
@@ -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 (file)
index 0000000..d6a764f
--- /dev/null
@@ -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
index 1f5fdab3e337bb35cfd88dbe97912a78cff34edc..2be31148e2a5346a57ca9fb65241859fa1b3bbd7 100644 (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
@@ -36,7 +36,7 @@ module MetricsHelper
       html += "<optgroup label=\"#{html_escape(domain)}\">"
       metrics_per_domain[domain].each do |m|
         selected_attr = " selected='selected'" if (m.key==selected_key || m.id==selected_key)
-        html += "<option value='#{html_escape(m.key)}'#{selected_attr}>#{html_escape(m.short_name)}</option>"
+        html += "<option value='#{html_escape(m.key)}'#{selected_attr}>#{html_escape(m.short_name(true))}</option>"
       end
       html += '</optgroup>'
     end
index 314018994727fd40780a7bc50e1d8be424ace964..f0baab37f59064b98712678c94726bdb566b5e8a 100644 (file)
 #
 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
index 7de0d6da97eaf106b4275fd09ee8a3927d57ee61..cce189d566fe41ee1374e64bada5ff7280645ee1 100644 (file)
@@ -14,7 +14,7 @@
       &raquo; <a href="<%= ApplicationController.root_context -%>/sessions/logout">Log out</a>
       </li>
       <% else %>
-      <li><a href="<%= ApplicationController.root_context -%>/sessions/new">Log in</a></li>
+      <li><a href="<%= ApplicationController.root_context -%>/sessions/new"><%= message('app.view.layouts.layout.login', 'Log in') -%></a></li>
       <% end %>
       <li><a href="<%= ApplicationController.root_context -%>/profiles">Configuration</a></li>
     </ol>
diff --git a/sonar-server/src/test/java/org/sonar/server/ui/JRubyI18nTest.java b/sonar-server/src/test/java/org/sonar/server/ui/JRubyI18nTest.java
new file mode 100644 (file)
index 0000000..fe150e2
--- /dev/null
@@ -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()));
+
+  }
+}