@@ -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 | |||
@@ -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 |
@@ -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> |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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"; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
Manifest-Version: 1.0 | |||
Created-By: 1.6.0_17 (Apple Inc.) | |||
@@ -0,0 +1,4 @@ | |||
it=Il | |||
is=fait | |||
cold=froid | |||
@@ -0,0 +1,3 @@ | |||
Manifest-Version: 1.0 | |||
Created-By: 1.6.0_17 (Apple Inc.) | |||
@@ -0,0 +1,2 @@ | |||
cold=frette | |||
@@ -0,0 +1,3 @@ | |||
Manifest-Version: 1.0 | |||
Created-By: 1.6.0_17 (Apple Inc.) | |||
@@ -0,0 +1,4 @@ | |||
it=It | |||
is=is | |||
cold=cold | |||
only.english=Ketchup |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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"/> |
@@ -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); | |||
} |
@@ -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(); | |||
} |
@@ -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> | |||
@@ -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(); | |||
} |
@@ -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(); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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)}, |
@@ -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 |
@@ -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 |
@@ -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 | |||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -14,7 +14,7 @@ | |||
» <a href="<%= ApplicationController.root_context -%>/sessions/logout">Log out</a> | |||
</li> | |||
<% else %> | |||
<li><a href="<%= ApplicationController.root_context -%>/sessions/new">Log in</a></li> | |||
<li><a href="<%= ApplicationController.root_context -%>/sessions/new"><%= message('app.view.layouts.layout.login', 'Log in') -%></a></li> | |||
<% end %> | |||
<li><a href="<%= ApplicationController.root_context -%>/profiles">Configuration</a></li> | |||
</ol> |
@@ -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())); | |||
} | |||
} |