aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2017-05-11 15:26:03 +0200
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>2017-05-12 12:37:19 +0200
commitb451f859c4fc8e0a694c18e175ddc931e1702cd9 (patch)
tree5058793bdd549539bd6de42c905dc70891862b00
parent4367c635b9e660bec4e30929143d6b1bfd32f2de (diff)
downloadsonarqube-b451f859c4fc8e0a694c18e175ddc931e1702cd9.tar.gz
sonarqube-b451f859c4fc8e0a694c18e175ddc931e1702cd9.zip
SONAR-9193 WS api/l10n/index returns the effective BCP47 language tag used
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/IndexAction.java8
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/platform/ws/l10n-index-example.json211
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ws/IndexActionTest.java10
-rw-r--r--sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java8
-rw-r--r--sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java1
5 files changed, 127 insertions, 111 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/IndexAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/IndexAction.java
index 5cf60d89d1c..99cc708ad64 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/IndexAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/IndexAction.java
@@ -73,9 +73,11 @@ public class IndexAction implements WsAction {
Locale locale = Locale.forLanguageTag(localeParam);
checkArgument(!locale.getISO3Language().isEmpty(), "'%s' cannot be parsed as a BCP47 language tag", localeParam);
JsonWriter json = response.newJsonWriter().beginObject();
- for (String messageKey : i18n.getPropertyKeys()) {
- json.prop(messageKey, i18n.message(locale, messageKey, messageKey));
- }
+ json.prop("effectiveLocale", i18n.getEffectiveLocale(locale).toLanguageTag());
+ json.name("messages");
+ json.beginObject();
+ i18n.getPropertyKeys().forEach(messageKey -> json.prop(messageKey, i18n.message(locale, messageKey, messageKey)));
+ json.endObject();
json.endObject().close();
}
}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/platform/ws/l10n-index-example.json b/server/sonar-server/src/main/resources/org/sonar/server/platform/ws/l10n-index-example.json
index 96e8d6bac83..3a6f9f27b5f 100644
--- a/server/sonar-server/src/main/resources/org/sonar/server/platform/ws/l10n-index-example.json
+++ b/server/sonar-server/src/main/resources/org/sonar/server/platform/ws/l10n-index-example.json
@@ -1,106 +1,109 @@
{
- "cancel": "Cancel",
- "rule.php.S115.param.format": "Regular expression used to check the constant names against.",
- "views.projects.selection_mode": "Project selection mode",
- "rule.php.S1067.param.max": "Maximum number of allowed conditional operators in an expression",
- "quality_profiles.restore_built_in_profiles": "Restore Built-in Profiles",
- "qualifiers.delete_confirm.TRK": "Do you want to delete this project?",
- "roles.page": "Project Permissions",
- "metric.package_cycles.abbreviation": "Pkgs cycles",
- "views.delete_success": "View definition \"{0}\" has been successfully deleted",
- "project_history.col.month": "Month",
- "including_abbreviated": "incl.",
- "coding_rules.filters.activation.help": "Activation criterion is available when a quality profile is selected",
- "metric.it_branch_coverage.description": "Condition coverage by integration tests",
- "measure_filter.sharing": "Sharing",
- "widget.issue_filter.property.filter.name": "Filter",
- "help_tips": "Help tips",
- "select_a_metric": "Select a metric",
- "custom_measures.page.description": "Update the values of custom metrics for this project. Changes will take effect at the project's next analysis. Custom metrics must be created at the global level.",
- "measure_filter.abbr.description": "Description",
- "action_plans.delete": "Delete Action Plan",
- "optional": "Optional",
- "issue.changelog.field.assignee": "Assignee",
- "background_tasks.table.duration": "Duration",
- "source_viewer.tooltip.ut.partially-covered": "Partially covered by unit tests. Click for details.",
- "size": "Size",
- "action_plans.confirm_delete": "Delete this action plan? Associated issues will not be deleted.",
- "quality_gates.delete_condition.confirm.message": "Are you sure you want to delete the \"{0}\" condition?",
- "rule.php.S1808.param.one_space_before": "There should be exactly one space between control structure keyword and opening parenthesis or curly brace",
- "metric.new_coverage.description": "Coverage of new/changed code",
- "widget.time_machine.property.metric9.name": "Metric 9",
- "views.projects.by_regexp": "All projects matching regular expression \"{0}\"",
- "user.add_scm_account": "Add SCM account",
- "to.downcase": "to",
- "widget.sqaleSunburst.cant_display": "SQALE technical debt is 0 so the breakdown by characteristic can't be displayed.",
- "measure_filter.name_contains": "Name contains",
- "name_too_long_x": "Name is too long (maximum is {0} characters)",
- "metric.line_coverage.name": "Line coverage",
- "measure_filter.criteria.key": "Key",
- "widget.project_issue_filter.property.filter.name": "Filter",
- "widget.measure_filter_bubble_chart.name": "Measure Filter as Bubble Chart",
- "issue.assign.formlink": "Assign",
- "metric.overall_lines_to_cover.description": "Lines to cover by all tests",
- "no_results": "No results",
- "rule.php.S1808.param.one_space_after": "There should be exactly one space between closing parenthesis and opening curly braces",
- "result": "Result",
- "measure_filter.key_contains": "Key contains",
- "widget.complexity.description": "Reports on complexity, average complexity and complexity distribution.",
- "analysis_reports.past_reports": "Past Reports",
- "property.sonar.global.exclusions.name": "Global Source File Exclusions",
- "metric.overall_coverage.description": "Overall test coverage",
- "quality_profiles.remove_projects_confirm_button": "Remove All",
- "widget.measure_filter_histogram.name": "Measure Filter as Histogram",
- "metric.covered_lines.description": "Covered lines",
- "widget.measure_filter_pie_chart.property.extraMetric2.name": "Extra Metric 2",
- "coding_rules.most_violated_projects": "Most Violated Projects",
- "views.edit_subview": "Edit \"{0}\"",
- "over_x_days": "over {0} days",
- "project_settings.page": "General Settings",
- "projects_role.codeviewer.desc": "View the project's source code. (Users will also need \"Browse\" permission)",
- "issue.resolution.REMOVED": "Removed",
- "coverage_viewer.overall_tests": "All Tests",
- "metric.test_failures.description": "Number of unit test failures",
- "dashboard.error_unshare_default": "This dashboard can't be unshared as long as it's defined as a default dashboard",
- "issue.effort": "Effort:",
- "background_tasks.failures": "failures",
- "property.category.exclusions.duplications.description": "Configure the files that should be ignored by duplication detection.",
- "widget.treemap-widget.name": "Treemap of Components",
- "widget.measure_filter_histogram.description": "Displays the result of a pre-configured measure filter as a histogram.",
- "property.category.codeCoverage": "Code Coverage",
- "component": "Component",
- "metric.duplications_data.description": "Duplications details",
- "widget.time_machine.property.metric2.name": "Metric 2",
- "quality_gates.operator.GT.short": ">",
- "rules.not_found": "The rule \"{0}\" does not exist",
- "coding_rules.create": "Create",
- "days": "Days",
- "email_configuration.test.send": "Send Test Email",
- "update_key.replace": "Replace",
- "views.invalid.criteria": "Only one criteria can be used (regexp, language or manual measure)",
- "widget.measure_filter_pie_chart.property.chartHeight.name": "Chart Height",
- "dashboard.project_not_found": "The requested project does not exist. Either it has never been analyzed successfully or it has been deleted.",
- "global_permissions.admin": "Administer System",
- "test_viewer.skipped": "skipped",
- "analysis_reports.x_reports": "{0} reports",
- "shortcuts.section.global.shortcuts": "open this window",
- "metric.total-useless-lines.name": "Total Useless Code",
- "dashboard.default_dashboard": "This dashboard is the default one and is displayed when clicking on \"Overview\".",
- "component_navigation.status.in_progress.admin": "The analysis is in progress.<br>More details available on the <a href=\"{0}\">Background Tasks</a> page.",
- "metric.public_api.name": "Public API",
- "widget.measure_filter_treemap.property.heightInPercents.name": "Height",
- "quality_profiles.rename_x_title": "Rename Profile {0} - {1}",
- "widget.select_project": "Please select a project",
- "coding_rules.change_severity_in": "Change Severity In",
- "rule.php.S101.param.format": "Regular expression used to check the class names against.",
- "select2.tooShort": "Please enter at least {0} characters",
- "widget.custom_measures.property.metric10.name": "Metric 10",
- "manual_measures.save_button": "Save",
- "issue_filter.sharing": "Sharing",
- "widget.measure_filter_cloud.property.colorMetric.name": "Color Metric",
- "dashboard.TimeMachine.name": "Time Machine",
- "property.category.security": "Security",
- "coding_rules.validation.invalid_rule_key": "The rule key \"%s\" is invalid, it should only contain: a-z, 0-9, \"_\"",
- "metric.sqale_rating.description": "Density of technical debt computed by dividing the technical debt by the estimated effort to develop from scratch an application.",
- "notification.channel.EmailNotificationChannel": "Email"
+ "locale": "en",
+ "messages": {
+ "cancel": "Cancel",
+ "rule.php.S115.param.format": "Regular expression used to check the constant names against.",
+ "views.projects.selection_mode": "Project selection mode",
+ "rule.php.S1067.param.max": "Maximum number of allowed conditional operators in an expression",
+ "quality_profiles.restore_built_in_profiles": "Restore Built-in Profiles",
+ "qualifiers.delete_confirm.TRK": "Do you want to delete this project?",
+ "roles.page": "Project Permissions",
+ "metric.package_cycles.abbreviation": "Pkgs cycles",
+ "views.delete_success": "View definition \"{0}\" has been successfully deleted",
+ "project_history.col.month": "Month",
+ "including_abbreviated": "incl.",
+ "coding_rules.filters.activation.help": "Activation criterion is available when a quality profile is selected",
+ "metric.it_branch_coverage.description": "Condition coverage by integration tests",
+ "measure_filter.sharing": "Sharing",
+ "widget.issue_filter.property.filter.name": "Filter",
+ "help_tips": "Help tips",
+ "select_a_metric": "Select a metric",
+ "custom_measures.page.description": "Update the values of custom metrics for this project. Changes will take effect at the project's next analysis. Custom metrics must be created at the global level.",
+ "measure_filter.abbr.description": "Description",
+ "action_plans.delete": "Delete Action Plan",
+ "optional": "Optional",
+ "issue.changelog.field.assignee": "Assignee",
+ "background_tasks.table.duration": "Duration",
+ "source_viewer.tooltip.ut.partially-covered": "Partially covered by unit tests. Click for details.",
+ "size": "Size",
+ "action_plans.confirm_delete": "Delete this action plan? Associated issues will not be deleted.",
+ "quality_gates.delete_condition.confirm.message": "Are you sure you want to delete the \"{0}\" condition?",
+ "rule.php.S1808.param.one_space_before": "There should be exactly one space between control structure keyword and opening parenthesis or curly brace",
+ "metric.new_coverage.description": "Coverage of new/changed code",
+ "widget.time_machine.property.metric9.name": "Metric 9",
+ "views.projects.by_regexp": "All projects matching regular expression \"{0}\"",
+ "user.add_scm_account": "Add SCM account",
+ "to.downcase": "to",
+ "widget.sqaleSunburst.cant_display": "SQALE technical debt is 0 so the breakdown by characteristic can't be displayed.",
+ "measure_filter.name_contains": "Name contains",
+ "name_too_long_x": "Name is too long (maximum is {0} characters)",
+ "metric.line_coverage.name": "Line coverage",
+ "measure_filter.criteria.key": "Key",
+ "widget.project_issue_filter.property.filter.name": "Filter",
+ "widget.measure_filter_bubble_chart.name": "Measure Filter as Bubble Chart",
+ "issue.assign.formlink": "Assign",
+ "metric.overall_lines_to_cover.description": "Lines to cover by all tests",
+ "no_results": "No results",
+ "rule.php.S1808.param.one_space_after": "There should be exactly one space between closing parenthesis and opening curly braces",
+ "result": "Result",
+ "measure_filter.key_contains": "Key contains",
+ "widget.complexity.description": "Reports on complexity, average complexity and complexity distribution.",
+ "analysis_reports.past_reports": "Past Reports",
+ "property.sonar.global.exclusions.name": "Global Source File Exclusions",
+ "metric.overall_coverage.description": "Overall test coverage",
+ "quality_profiles.remove_projects_confirm_button": "Remove All",
+ "widget.measure_filter_histogram.name": "Measure Filter as Histogram",
+ "metric.covered_lines.description": "Covered lines",
+ "widget.measure_filter_pie_chart.property.extraMetric2.name": "Extra Metric 2",
+ "coding_rules.most_violated_projects": "Most Violated Projects",
+ "views.edit_subview": "Edit \"{0}\"",
+ "over_x_days": "over {0} days",
+ "project_settings.page": "General Settings",
+ "projects_role.codeviewer.desc": "View the project's source code. (Users will also need \"Browse\" permission)",
+ "issue.resolution.REMOVED": "Removed",
+ "coverage_viewer.overall_tests": "All Tests",
+ "metric.test_failures.description": "Number of unit test failures",
+ "dashboard.error_unshare_default": "This dashboard can't be unshared as long as it's defined as a default dashboard",
+ "issue.effort": "Effort:",
+ "background_tasks.failures": "failures",
+ "property.category.exclusions.duplications.description": "Configure the files that should be ignored by duplication detection.",
+ "widget.treemap-widget.name": "Treemap of Components",
+ "widget.measure_filter_histogram.description": "Displays the result of a pre-configured measure filter as a histogram.",
+ "property.category.codeCoverage": "Code Coverage",
+ "component": "Component",
+ "metric.duplications_data.description": "Duplications details",
+ "widget.time_machine.property.metric2.name": "Metric 2",
+ "quality_gates.operator.GT.short": ">",
+ "rules.not_found": "The rule \"{0}\" does not exist",
+ "coding_rules.create": "Create",
+ "days": "Days",
+ "email_configuration.test.send": "Send Test Email",
+ "update_key.replace": "Replace",
+ "views.invalid.criteria": "Only one criteria can be used (regexp, language or manual measure)",
+ "widget.measure_filter_pie_chart.property.chartHeight.name": "Chart Height",
+ "dashboard.project_not_found": "The requested project does not exist. Either it has never been analyzed successfully or it has been deleted.",
+ "global_permissions.admin": "Administer System",
+ "test_viewer.skipped": "skipped",
+ "analysis_reports.x_reports": "{0} reports",
+ "shortcuts.section.global.shortcuts": "open this window",
+ "metric.total-useless-lines.name": "Total Useless Code",
+ "dashboard.default_dashboard": "This dashboard is the default one and is displayed when clicking on \"Overview\".",
+ "component_navigation.status.in_progress.admin": "The analysis is in progress.<br>More details available on the <a href=\"{0}\">Background Tasks</a> page.",
+ "metric.public_api.name": "Public API",
+ "widget.measure_filter_treemap.property.heightInPercents.name": "Height",
+ "quality_profiles.rename_x_title": "Rename Profile {0} - {1}",
+ "widget.select_project": "Please select a project",
+ "coding_rules.change_severity_in": "Change Severity In",
+ "rule.php.S101.param.format": "Regular expression used to check the class names against.",
+ "select2.tooShort": "Please enter at least {0} characters",
+ "widget.custom_measures.property.metric10.name": "Metric 10",
+ "manual_measures.save_button": "Save",
+ "issue_filter.sharing": "Sharing",
+ "widget.measure_filter_cloud.property.colorMetric.name": "Color Metric",
+ "dashboard.TimeMachine.name": "Time Machine",
+ "property.category.security": "Security",
+ "coding_rules.validation.invalid_rule_key": "The rule key \"%s\" is invalid, it should only contain: a-z, 0-9, \"_\"",
+ "metric.sqale_rating.description": "Density of technical debt computed by dividing the technical debt by the estimated effort to develop from scratch an application.",
+ "notification.channel.EmailNotificationChannel": "Email"
+ }
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/IndexActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/IndexActionTest.java
index 1b0a4b4f8a2..b4c80950d7e 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/IndexActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/IndexActionTest.java
@@ -81,6 +81,7 @@ public class IndexActionTest {
when(i18n.message(PRC, KEY_1, KEY_1)).thenReturn(KEY_1);
when(i18n.message(PRC, KEY_2, KEY_2)).thenReturn(KEY_2);
when(i18n.message(PRC, KEY_3, KEY_3)).thenReturn(KEY_3);
+ when(i18n.getEffectiveLocale(PRC)).thenReturn(PRC);
TestResponse result = call(PRC.toLanguageTag(), DateUtils.formatDateTime(aBitEarlier));
@@ -88,7 +89,7 @@ public class IndexActionTest {
verify(i18n).message(PRC, KEY_1, KEY_1);
verify(i18n).message(PRC, KEY_2, KEY_2);
verify(i18n).message(PRC, KEY_3, KEY_3);
- assertJson(result.getInput()).isSimilarTo("{\"key1\":\"key1\",\"key2\":\"key2\",\"key3\":\"key3\"}");
+ assertJson(result.getInput()).isSimilarTo("{\"effectiveLocale\":\"zh-CN\", \"messages\": {\"key1\":\"key1\",\"key2\":\"key2\",\"key3\":\"key3\"}}");
}
@Test
@@ -100,6 +101,7 @@ public class IndexActionTest {
when(i18n.message(ENGLISH, key1, key1)).thenReturn(key1);
when(i18n.message(ENGLISH, key2, key2)).thenReturn(key2);
when(i18n.message(ENGLISH, key3, key3)).thenReturn(key3);
+ when(i18n.getEffectiveLocale(ENGLISH)).thenReturn(ENGLISH);
TestResponse result = call(null, null);
@@ -107,7 +109,7 @@ public class IndexActionTest {
verify(i18n).message(ENGLISH, key1, key1);
verify(i18n).message(ENGLISH, key2, key2);
verify(i18n).message(ENGLISH, key3, key3);
- assertJson(result.getInput()).isSimilarTo("{\"key1\":\"key1\",\"key2\":\"key2\",\"key3\":\"key3\"}");
+ assertJson(result.getInput()).isSimilarTo("{\"messages\": {\"key1\":\"key1\",\"key2\":\"key2\",\"key3\":\"key3\"}}");
}
@Test
@@ -115,12 +117,13 @@ public class IndexActionTest {
String key1 = "key1";
when(i18n.getPropertyKeys()).thenReturn(ImmutableSet.of(key1));
when(i18n.message(UK, key1, key1)).thenReturn(key1);
+ when(i18n.getEffectiveLocale(UK)).thenReturn(UK);
TestResponse result = call("en-GB", null);
verify(i18n).getPropertyKeys();
verify(i18n).message(UK, key1, key1);
- assertJson(result.getInput()).isSimilarTo("{\"key1\":\"key1\"}");
+ assertJson(result.getInput()).isSimilarTo("{\"messages\": {\"key1\":\"key1\"}}");
}
@Test
@@ -128,6 +131,7 @@ public class IndexActionTest {
String key1 = "key1";
when(i18n.getPropertyKeys()).thenReturn(ImmutableSet.of(key1));
when(i18n.message(UK, key1, key1)).thenReturn(key1);
+ when(i18n.getEffectiveLocale(UK)).thenReturn(UK);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("'en_GB' cannot be parsed as a BCP47 language tag");
diff --git a/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java b/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java
index 42a1707e182..ae746c6580b 100644
--- a/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java
+++ b/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java
@@ -226,8 +226,14 @@ public class DefaultI18n implements I18n, Startable {
return propertyToBundles.keySet();
}
+ public Locale getEffectiveLocale(Locale locale) {
+ Locale bundleLocale = ResourceBundle.getBundle(BUNDLE_PACKAGE + "core", locale, this.classloader, this.control).getLocale();
+ locale.getISO3Language();
+ return bundleLocale.getLanguage().isEmpty() ? Locale.ENGLISH : bundleLocale;
+ }
+
@CheckForNull
- private String formatMessage(@Nullable String message, Object... parameters) {
+ private static String formatMessage(@Nullable String message, Object... parameters) {
if (message == null || parameters.length == 0) {
return message;
}
diff --git a/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java b/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java
index 409d7d94eaf..985810e8cb6 100644
--- a/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java
@@ -145,6 +145,7 @@ public class DefaultI18nTest {
assertThat(underTest.message(Locale.CHINA, "checkstyle.rule1.name", null)).isEqualTo("Rule one");
assertThat(underTest.message(Locale.CHINA, "any", null)).isEqualTo("Any");
assertThat(underTest.message(Locale.CHINA, "sqale.page", null)).isEqualTo("Sqale page title");
+ assertThat(underTest.getEffectiveLocale(Locale.CHINA)).isEqualTo(Locale.ENGLISH);
}
@Test