Browse Source

SONAR-75 Apply first version of contribution by www.serli.com

tags/2.9
Simon Brandhof 13 years ago
parent
commit
aa92267c3a
40 changed files with 1597 additions and 21 deletions
  1. 436
    0
      plugins/sonar-core-plugin/src/main/resources/org/sonar/i18n/core.properties
  2. 4
    0
      plugins/sonar-design-plugin/src/main/resources/org/sonar/i18n/design.properties
  3. 70
    0
      plugins/sonar-i18n-plugin/pom.xml
  4. 203
    0
      plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nManager.java
  5. 36
    0
      plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nPlugin.java
  6. 35
    0
      plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nWebService.java
  7. 46
    0
      plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/InstalledPlugin.java
  8. 66
    0
      plugins/sonar-i18n-plugin/src/main/resources/org/sonar/plugins/i18n/i18n_manager_controller.rb
  9. 121
    0
      plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nManagerTest.java
  10. 33
    0
      plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nPluginTest.java
  11. 39
    0
      plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/FrenchLanguagePack.java
  12. 39
    0
      plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/QuebecLanguagePack.java
  13. BIN
      plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin.jar
  14. 3
    0
      plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin/META-INF/MANIFEST.MF
  15. 4
    0
      plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin/org/sonar/i18n/test_fr.properties
  16. BIN
      plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin.jar
  17. 3
    0
      plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin/META-INF/MANIFEST.MF
  18. 2
    0
      plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties
  19. BIN
      plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin.jar
  20. 3
    0
      plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin/META-INF/MANIFEST.MF
  21. 4
    0
      plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin/org/sonar/i18n/test.properties
  22. 17
    0
      plugins/sonar-i18n-plugin/src/test/resources/logback-test.xml
  23. 1
    0
      pom.xml
  24. 6
    0
      sonar-application/pom.xml
  25. 4
    0
      sonar-application/src/main/assembly/conf/logback.xml
  26. 158
    0
      sonar-plugin-api/src/main/java/org/sonar/api/i18n/I18n.java
  27. 50
    0
      sonar-plugin-api/src/main/java/org/sonar/api/i18n/LanguagePack.java
  28. 6
    0
      sonar-server/pom.xml
  29. 2
    0
      sonar-server/src/main/java/org/sonar/server/platform/Platform.java
  30. 9
    5
      sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
  31. 69
    0
      sonar-server/src/main/java/org/sonar/server/ui/JRubyI18n.java
  32. 1
    1
      sonar-server/src/main/webapp/WEB-INF/app/controllers/api/server_controller.rb
  33. 1
    1
      sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb
  34. 1
    1
      sonar-server/src/main/webapp/WEB-INF/app/controllers/system_controller.rb
  35. 5
    1
      sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb
  36. 61
    0
      sonar-server/src/main/webapp/WEB-INF/app/helpers/i18n_helper.rb
  37. 4
    4
      sonar-server/src/main/webapp/WEB-INF/app/helpers/metrics_helper.rb
  38. 3
    7
      sonar-server/src/main/webapp/WEB-INF/app/models/server.rb
  39. 1
    1
      sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
  40. 51
    0
      sonar-server/src/test/java/org/sonar/server/ui/JRubyI18nTest.java

+ 436
- 0
plugins/sonar-core-plugin/src/main/resources/org/sonar/i18n/core.properties View File

@@ -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


+ 4
- 0
plugins/sonar-design-plugin/src/main/resources/org/sonar/i18n/design.properties View File

@@ -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

+ 70
- 0
plugins/sonar-i18n-plugin/pom.xml View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>sonar</artifactId>
<groupId>org.codehaus.sonar</groupId>
<version>2.9-SNAPSHOT</version>
<relativePath>../..</relativePath>
</parent>
<groupId>org.codehaus.sonar.plugins</groupId>
<artifactId>sonar-i18n-plugin</artifactId>
<packaging>sonar-plugin</packaging>
<version>2.9-SNAPSHOT</version>
<name>Sonar :: Plugins :: I18n</name>
<description>Sonar I18n plugin</description>
<dependencies>
<dependency>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-plugin-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-core</artifactId>
<scope>provided</scope>
</dependency>
<!-- unit tests -->
<dependency>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-testing-harness</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-packaging-maven-plugin</artifactId>
<version>1.1</version>
<extensions>true</extensions>
<configuration>
<pluginKey>i18n</pluginKey>
<pluginClass>org.sonar.plugins.i18n.I18nPlugin</pluginClass>
<pluginName>I18n</pluginName>
<pluginDescription>
<![CDATA[Provides components required to internationalize Sonar application and plugins, as well as a default language bundle for the Sonar kernel modules and web application.]]></pluginDescription>
</configuration>
</plugin>
</plugins>
</build>
</project>

+ 203
- 0
plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nManager.java View File

@@ -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;
}
}
}

+ 36
- 0
plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nPlugin.java View File

@@ -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);
}
}

+ 35
- 0
plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/I18nWebService.java View File

@@ -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";
}
}

+ 46
- 0
plugins/sonar-i18n-plugin/src/main/java/org/sonar/plugins/i18n/InstalledPlugin.java View File

@@ -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;
}
}

+ 66
- 0
plugins/sonar-i18n-plugin/src/main/resources/org/sonar/plugins/i18n/i18n_manager_controller.rb View File

@@ -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

+ 121
- 0
plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nManagerTest.java View File

@@ -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;
}
}

}

+ 33
- 0
plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/I18nPluginTest.java View File

@@ -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));
}
}

+ 39
- 0
plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/FrenchLanguagePack.java View File

@@ -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);
}
}

+ 39
- 0
plugins/sonar-i18n-plugin/src/test/java/org/sonar/plugins/i18n/utils/QuebecLanguagePack.java View File

@@ -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);
}
}

BIN
plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin.jar View File


+ 3
- 0
plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin/META-INF/MANIFEST.MF View File

@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Created-By: 1.6.0_17 (Apple Inc.)

+ 4
- 0
plugins/sonar-i18n-plugin/src/test/resources/FrenchPlugin/org/sonar/i18n/test_fr.properties View File

@@ -0,0 +1,4 @@
it=Il
is=fait
cold=froid


BIN
plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin.jar View File


+ 3
- 0
plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin/META-INF/MANIFEST.MF View File

@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Created-By: 1.6.0_17 (Apple Inc.)

+ 2
- 0
plugins/sonar-i18n-plugin/src/test/resources/QuebecPlugin/org/sonar/i18n/test_fr_CA.properties View File

@@ -0,0 +1,2 @@
cold=frette


BIN
plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin.jar View File


+ 3
- 0
plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin/META-INF/MANIFEST.MF View File

@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Created-By: 1.6.0_17 (Apple Inc.)

+ 4
- 0
plugins/sonar-i18n-plugin/src/test/resources/StandardPlugin/org/sonar/i18n/test.properties View File

@@ -0,0 +1,4 @@
it=It
is=is
cold=cold
only.english=Ketchup

+ 17
- 0
plugins/sonar-i18n-plugin/src/test/resources/logback-test.xml View File

@@ -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>

+ 1
- 0
pom.xml View File

@@ -47,6 +47,7 @@
<module>plugins/sonar-cpd-plugin</module>
<module>plugins/sonar-squid-java-plugin</module>
<module>plugins/sonar-design-plugin</module>
<module>plugins/sonar-i18n-plugin</module>
</modules>

<organization>

+ 6
- 0
sonar-application/pom.xml View File

@@ -208,6 +208,12 @@
<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>

+ 4
- 0
sonar-application/src/main/assembly/conf/logback.xml View File

@@ -103,6 +103,10 @@
<level value="INFO"/>
</logger>

<logger name="org.sonar.plugins.i18n.I18nManager">
<level value="INFO"/>
</logger>

<root>
<level value="WARN"/>
<appender-ref ref="SONAR_FILE"/>

+ 158
- 0
sonar-plugin-api/src/main/java/org/sonar/api/i18n/I18n.java View File

@@ -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);
}

+ 50
- 0
sonar-plugin-api/src/main/java/org/sonar/api/i18n/LanguagePack.java View File

@@ -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();
}

+ 6
- 0
sonar-server/pom.xml View File

@@ -458,6 +458,12 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.sonar.plugins</groupId>
<artifactId>sonar-i18n-plugin</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</profile>


+ 2
- 0
sonar-server/src/main/java/org/sonar/server/platform/Platform.java View 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();
}

+ 9
- 5
sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java View 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();
}

+ 69
- 0
sonar-server/src/main/java/org/sonar/server/ui/JRubyI18n.java View File

@@ -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);
}
}

+ 1
- 1
sonar-server/src/main/webapp/WEB-INF/app/controllers/api/server_controller.rb View 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)},

+ 1
- 1
sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb View 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

+ 1
- 1
sonar-server/src/main/webapp/WEB-INF/app/controllers/system_controller.rb View 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

+ 5
- 1
sonar-server/src/main/webapp/WEB-INF/app/helpers/application_helper.rb View 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


+ 61
- 0
sonar-server/src/main/webapp/WEB-INF/app/helpers/i18n_helper.rb View File

@@ -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

+ 4
- 4
sonar-server/src/main/webapp/WEB-INF/app/helpers/metrics_helper.rb View File

@@ -19,14 +19,14 @@
#
module MetricsHelper

def domains(metrics)
metrics.map {|m| m.domain}.uniq.compact.sort
def domains(metrics, translate=false)
metrics.map {|m| m.domain(translate)}.uniq.compact.sort
end

def options_grouped_by_domain(metrics, selected_key='')
metrics_per_domain={}
metrics.each do |metric|
domain=metric.domain || ''
domain=metric.domain(true) || ''
metrics_per_domain[domain]||=[]
metrics_per_domain[domain]<<metric
end
@@ -36,7 +36,7 @@ module MetricsHelper
html += "<optgroup label=\"#{html_escape(domain)}\">"
metrics_per_domain[domain].each do |m|
selected_attr = " selected='selected'" if (m.key==selected_key || m.id==selected_key)
html += "<option value='#{html_escape(m.key)}'#{selected_attr}>#{html_escape(m.short_name)}</option>"
html += "<option value='#{html_escape(m.key)}'#{selected_attr}>#{html_escape(m.short_name(true))}</option>"
end
html += '</optgroup>'
end

+ 3
- 7
sonar-server/src/main/webapp/WEB-INF/app/models/server.rb View File

@@ -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

+ 1
- 1
sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb View 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>

+ 51
- 0
sonar-server/src/test/java/org/sonar/server/ui/JRubyI18nTest.java View File

@@ -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()));

}
}

Loading…
Cancel
Save