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