diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ac7cc3c50..a82163b76 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -266,7 +266,7 @@ module ApplicationHelper when Integer object.to_s when Float - sprintf "%.2f", object + number_with_delimiter(sprintf('%.2f', object), delimiter: nil) when User, Group html ? link_to_principal(object) : object.to_s when Project @@ -684,7 +684,7 @@ module ApplicationHelper def html_hours(text) text.gsub( - %r{(\d+)([\.:])(\d+)}, + %r{(\d+)([\.,:])(\d+)}, '\1\2\3' ).html_safe end diff --git a/lib/redmine/export/pdf/issues_pdf_helper.rb b/lib/redmine/export/pdf/issues_pdf_helper.rb index 85330e9cb..834cd6db8 100644 --- a/lib/redmine/export/pdf/issues_pdf_helper.rb +++ b/lib/redmine/export/pdf/issues_pdf_helper.rb @@ -21,6 +21,8 @@ module Redmine module Export module PDF module IssuesPdfHelper + include ActionView::Helpers::NumberHelper + # Returns a PDF string of a single issue def issue_to_pdf(issue, assoc={}) pdf = ITCPDF.new(current_language) @@ -406,7 +408,7 @@ module Redmine elsif value.is_a?(Time) format_time(value) elsif value.is_a?(Float) - sprintf "%.2f", value + number_with_delimiter(sprintf('%.2f', value), delimiter: nil) else value end diff --git a/lib/redmine/field_format.rb b/lib/redmine/field_format.rb index a27311141..5e81055f1 100644 --- a/lib/redmine/field_format.rb +++ b/lib/redmine/field_format.rb @@ -539,8 +539,9 @@ module Redmine end def validate_single_value(custom_field, value, customized=nil) + value = normalize_float(value) errs = super - errs << ::I18n.t('activerecord.errors.messages.invalid') unless (Kernel.Float(value) rescue nil) + errs << ::I18n.t('activerecord.errors.messages.invalid') unless Kernel.Float(value, exception: false) errs end diff --git a/lib/redmine/i18n.rb b/lib/redmine/i18n.rb index 8434cd9be..6c4d2aad7 100644 --- a/lib/redmine/i18n.rb +++ b/lib/redmine/i18n.rb @@ -21,6 +21,8 @@ require 'redmine' module Redmine module I18n + include ActionView::Helpers::NumberHelper + def self.included(base) base.extend Redmine::I18n end @@ -95,10 +97,22 @@ module Redmine m = ((hours - h) * 60).round "%d:%02d" % [h, m] else - "%.2f" % hours.to_f + number_with_delimiter(sprintf('%.2f', hours.to_f), delimiter: nil) end end + # Will consider language specific separator in user input + # and normalize them to a unified format to be accepted by Kernel.Float(). + # + # @param value [String] A string represenation of a float value. + # + # @note The delimiter cannot be used here if it is a decimal point since it + # will clash with the dot separator. + def normalize_float(value) + separator = ::I18n.t('number.format.separator') + value.gsub(/[#{separator}]/, separator => '.') + end + def day_name(day) ::I18n.t('date.day_names')[day % 7] end diff --git a/test/helpers/application_helper_test.rb b/test/helpers/application_helper_test.rb index 8dccb8b7e..82b1c3278 100644 --- a/test/helpers/application_helper_test.rb +++ b/test/helpers/application_helper_test.rb @@ -2171,6 +2171,17 @@ class ApplicationHelperTest < Redmine::HelperTest end end + def test_format_hours_should_use_locale_decimal_separator + to_test = {'en' => '0.75', 'de' => '0,75'} + with_settings :timespan_format => 'decimal' do + to_test.each do |locale, expected| + with_locale locale do + assert_equal expected, format_hours(0.75) + end + end + end + end + def test_html_hours assert_equal '0:45', html_hours('0:45') diff --git a/test/unit/lib/redmine/export/pdf/issues_pdf_test.rb b/test/unit/lib/redmine/export/pdf/issues_pdf_test.rb index 4dd40b9fc..e150aca84 100644 --- a/test/unit/lib/redmine/export/pdf/issues_pdf_test.rb +++ b/test/unit/lib/redmine/export/pdf/issues_pdf_test.rb @@ -33,8 +33,13 @@ class IssuesPdfHelperTest < ActiveSupport::TestCase time_entry = TimeEntry.create!(:spent_on => Date.today, :hours => 4.3432, :user => user, :author => user, :project_id => 1, :issue => issue, :activity => TimeEntryActivity.first) - results = fetch_row_values(issue, query, 0) - assert_equal ["2", "Add ingredients categories", "4.34"], results + to_test = {'en' => '4.34', 'de' => '4,34'} + to_test.each do |locale, expected| + with_locale locale do + results = fetch_row_values(issue, query, 0) + assert_equal ['2', 'Add ingredients categories', expected], results + end + end end def test_fetch_row_values_should_be_able_to_handle_parent_issue_subject diff --git a/test/unit/lib/redmine/field_format/numeric_format_test.rb b/test/unit/lib/redmine/field_format/numeric_format_test.rb index 9ced022fb..c1914d2e8 100644 --- a/test/unit/lib/redmine/field_format/numeric_format_test.rb +++ b/test/unit/lib/redmine/field_format/numeric_format_test.rb @@ -23,6 +23,10 @@ require 'redmine/field_format' class Redmine::NumericFieldFormatTest < ActionView::TestCase include ApplicationHelper + fixtures :projects, :users, :issue_statuses, :enumerations, + :trackers, :projects_trackers, :roles, :member_roles, + :members, :enabled_modules + def setup User.current = nil end @@ -34,4 +38,28 @@ class Redmine::NumericFieldFormatTest < ActionView::TestCase assert_equal 3, field.format.formatted_custom_value(self, custom_value, false) assert_equal '3', field.format.formatted_custom_value(self, custom_value, true) end + + def test_float_field_value_should_validate_when_given_with_various_separator + field = IssueCustomField.generate!(field_format: 'float') + issue = Issue.generate!(tracker: Tracker.find(1), status: IssueStatus.find(1), priority: IssuePriority.find(6)) + to_test = {'en' => '3.33', 'de' => '3,33'} + to_test.each do |locale, expected| + with_locale locale do + assert field.format.validate_single_value(field, expected, issue) + end + end + end + + def test_float_field_should_format_with_various_locale_separator + field = IssueCustomField.generate!(field_format: 'float') + issue = Issue.generate!(tracker: Tracker.find(1), status: IssueStatus.find(1), priority: IssuePriority.find(6)) + issue.custom_field_values = { field.id => '1234.56' } + issue.save! + to_test = {'en' => '1234.56', 'de' => '1234,56'} + to_test.each do |locale, expected| + with_locale locale do + assert_equal expected, format_object(issue.reload.custom_field_values.last, false) + end + end + end end