Feature #20310 » 20310-v5.patch
| app/helpers/application_helper.rb | ||
|---|---|---|
| 501 | 501 |
h(page.pretty_title), |
| 502 | 502 |
href, |
| 503 | 503 |
:title => (if options[:timestamp] && page.updated_on |
| 504 |
l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on))
|
|
| 504 |
l(label_by_timestamp_format(:label_updated_time), format_timestamp(page.updated_on))
|
|
| 505 | 505 |
else |
| 506 | 506 |
nil |
| 507 | 507 |
end) |
| ... | ... | |
| 745 | 745 |
end |
| 746 | 746 | |
| 747 | 747 |
def authoring(created, author, options={})
|
| 748 |
l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
|
|
| 748 |
l(label_by_timestamp_format(options[:label] || :label_added_time_by), :author => link_to_user(author), :age => time_tag(created)).html_safe
|
|
| 749 | 749 |
end |
| 750 | 750 | |
| 751 | 751 |
def time_tag(time) |
| 752 | 752 |
return if time.nil? |
| 753 | 753 | |
| 754 |
text = distance_of_time_in_words(Time.now, time)
|
|
| 754 |
text = format_timestamp(time)
|
|
| 755 | 755 |
if @project |
| 756 | 756 |
link_to(text, |
| 757 | 757 |
project_activity_path(@project, :from => User.current.time_to_date(time)), |
| app/helpers/settings_helper.rb | ||
|---|---|---|
| 238 | 238 |
end |
| 239 | 239 |
end |
| 240 | 240 | |
| 241 |
# Returns the options for the timestamp_format setting |
|
| 242 |
# Convert the date and time three days ago into each format and use it as an example |
|
| 243 |
def timestamp_format_setting_options |
|
| 244 |
%w[relative_time relative_time_with_exact_time exact_time].map do |f| |
|
| 245 |
["#{format_timestamp(Time.now.ago(3.days), f)} (#{l('label_' + f)})", f]
|
|
| 246 |
end |
|
| 247 |
end |
|
| 248 | ||
| 241 | 249 |
def gravatar_default_setting_options |
| 242 | 250 |
[['Identicons', 'identicon'], |
| 243 | 251 |
['Monster ids', 'monsterid'], |
| app/helpers/wiki_helper.rb | ||
|---|---|---|
| 73 | 73 |
end |
| 74 | 74 | |
| 75 | 75 |
def wiki_content_update_info(content) |
| 76 |
l(:label_updated_time_by, :author => link_to_user(content.author), :age => time_tag(content.updated_on)).html_safe
|
|
| 76 |
authoring(content.updated_on, content.author, label: :label_updated_time_by)
|
|
| 77 | 77 |
end |
| 78 | 78 |
end |
| app/views/issues/show.html.erb | ||
|---|---|---|
| 49 | 49 |
<p class="author"> |
| 50 | 50 |
<%= authoring @issue.created_on, @issue.author %>. |
| 51 | 51 |
<% if @issue.created_on != @issue.updated_on %> |
| 52 |
<%= l(:label_updated_time, time_tag(@issue.updated_on)).html_safe %>.
|
|
| 52 |
<%= l(label_by_timestamp_format(:label_updated_time), time_tag(@issue.updated_on)).html_safe %>.
|
|
| 53 | 53 |
<% end %> |
| 54 | 54 |
</p> |
| 55 | 55 | |
| app/views/settings/_display.html.erb | ||
|---|---|---|
| 17 | 17 | |
| 18 | 18 |
<p><%= setting_select :timespan_format, [["%.2f" % 0.75, 'decimal'], ['0:45 h', 'minutes']], :blank => false %></p> |
| 19 | 19 | |
| 20 |
<p><%= setting_select :timestamp_format, timestamp_format_setting_options %></p> |
|
| 21 | ||
| 20 | 22 |
<p><%= setting_select :user_format, @options[:user_format] %></p> |
| 21 | 23 | |
| 22 | 24 |
<p><%= setting_check_box :gravatar_enabled, :data => {:enables => '#settings_gravatar_default'} %>
|
| config/locales/en.yml | ||
|---|---|---|
| 453 | 453 |
setting_date_format: Date format |
| 454 | 454 |
setting_time_format: Time format |
| 455 | 455 |
setting_timespan_format: Time span format |
| 456 |
setting_timestamp_format: Timestamp format |
|
| 456 | 457 |
setting_cross_project_issue_relations: Allow cross-project issue relations |
| 457 | 458 |
setting_cross_project_subtasks: Allow cross-project subtasks |
| 458 | 459 |
setting_issue_list_default_columns: Issues list defaults |
| ... | ... | |
| 893 | 894 |
label_f_hour: "%{value} hour"
|
| 894 | 895 |
label_f_hour_plural: "%{value} hours"
|
| 895 | 896 |
label_f_hour_short: "%{value} h"
|
| 897 |
label_relative_time: Relative time |
|
| 898 |
label_relative_time_with_exact_time: Relative time with exact time |
|
| 899 |
label_exact_time: Exact time |
|
| 896 | 900 |
label_time_tracking: Time tracking |
| 897 | 901 |
label_change_plural: Changes |
| 898 | 902 |
label_statistics: Statistics |
| ... | ... | |
| 954 | 958 |
label_added_time_by: "Added by %{author} %{age} ago"
|
| 955 | 959 |
label_updated_time_by: "Updated by %{author} %{age} ago"
|
| 956 | 960 |
label_updated_time: "Updated %{value} ago"
|
| 961 |
label_added_exact_time_by: "Added by %{author} on %{age}"
|
|
| 962 |
label_updated_exact_time_by: "Updated by %{author} on %{age}"
|
|
| 963 |
label_updated_exact_time: "Updated on %{value}"
|
|
| 957 | 964 |
label_jump_to_a_project: Jump to a project... |
| 958 | 965 |
label_file_plural: Files |
| 959 | 966 |
label_changeset_plural: Changesets |
| config/locales/ja.yml | ||
|---|---|---|
| 745 | 745 |
label_added_time_by: "%{author} さんが%{age}前に追加"
|
| 746 | 746 |
label_updated_time_by: "%{author} さんが%{age}前に更新"
|
| 747 | 747 |
label_updated_time: "%{value}前に更新"
|
| 748 |
label_added_exact_time_by: "%{author} さんが%{age}に追加"
|
|
| 749 |
label_updated_exact_time_by: "%{author} さんが%{age}に更新"
|
|
| 750 |
label_updated_exact_time: "%{value}に更新"
|
|
| 748 | 751 |
label_jump_to_a_project: プロジェクトへ移動... |
| 749 | 752 |
label_file_plural: ファイル |
| 750 | 753 |
label_changeset_plural: 更新履歴 |
| ... | ... | |
| 1182 | 1185 |
label_assigned_issues: 担当しているチケット |
| 1183 | 1186 |
label_field_format_enumeration: キー・バリュー リスト |
| 1184 | 1187 |
label_f_hour_short: '%{value}時間'
|
| 1188 |
label_relative_time: 相対日時 |
|
| 1189 |
label_relative_time_with_exact_time: 相対日時と正確な日時 |
|
| 1190 |
label_exact_time: 正確な日時 |
|
| 1185 | 1191 |
field_default_version: デフォルトのバージョン |
| 1186 | 1192 |
error_attachment_extension_not_allowed: 拡張子が %{extension} のファイルの添付は禁止されています
|
| 1187 | 1193 |
setting_attachment_extensions_allowed: 許可する拡張子 |
| ... | ... | |
| 1217 | 1223 |
label_font_monospace: 等幅 |
| 1218 | 1224 |
label_font_proportional: プロポーショナル |
| 1219 | 1225 |
setting_timespan_format: 時間の形式 |
| 1226 |
setting_timestamp_format: タイムスタンプの形式 |
|
| 1220 | 1227 |
label_table_of_contents: 目次 |
| 1221 | 1228 |
setting_commit_logs_formatting: コミットメッセージにテキスト書式を適用 |
| 1222 | 1229 |
setting_mail_handler_enable_regex: 正規表現を使用 |
| config/settings.yml | ||
|---|---|---|
| 174 | 174 |
default: '' |
| 175 | 175 |
timespan_format: |
| 176 | 176 |
default: 'minutes' |
| 177 |
timestamp_format: |
|
| 178 |
default: 'relative_time' |
|
| 177 | 179 |
user_format: |
| 178 | 180 |
default: :firstname_lastname |
| 179 | 181 |
format: symbol |
| lib/redmine/i18n.rb | ||
|---|---|---|
| 89 | 89 |
(include_date ? "#{format_date(local)} " : "") + ::I18n.l(local, **options)
|
| 90 | 90 |
end |
| 91 | 91 | |
| 92 |
def format_timestamp(time, timestamp_format=nil) |
|
| 93 |
case (timestamp_format || Setting.timestamp_format) |
|
| 94 |
when 'relative_time' |
|
| 95 |
distance_of_time_in_words(Time.now, time) |
|
| 96 |
when 'relative_time_with_exact_time' |
|
| 97 |
"#{distance_of_time_in_words(Time.now, time)} (#{format_time(time)})"
|
|
| 98 |
when 'exact_time' |
|
| 99 |
format_time(time) |
|
| 100 |
end |
|
| 101 |
end |
|
| 102 | ||
| 103 |
def label_by_timestamp_format(label_name) |
|
| 104 |
return label_name unless Setting.timestamp_format == 'exact_time' |
|
| 105 | ||
| 106 |
label_name.to_s.gsub('_time', '_exact_time').to_sym
|
|
| 107 |
end |
|
| 108 | ||
| 92 | 109 |
def format_hours(hours) |
| 93 | 110 |
return "" if hours.blank? |
| 94 | 111 | |
| test/helpers/application_helper_test.rb | ||
|---|---|---|
| 1838 | 1838 |
result = render_page_hierarchy(pages_by_parent_id, nil, :timestamp => true) |
| 1839 | 1839 |
assert_select_in( |
| 1840 | 1840 |
result, 'ul.pages-hierarchy li a[title=?]', |
| 1841 |
l(:label_updated_time, |
|
| 1842 |
distance_of_time_in_words(Time.now, parent_page.updated_on))) |
|
| 1841 |
l(:label_updated_time, format_timestamp(parent_page.updated_on))) |
|
| 1843 | 1842 |
assert_select_in( |
| 1844 | 1843 |
result, 'ul.pages-hierarchy li ul.pages-hierarchy a[title=?]', |
| 1845 |
l(:label_updated_time, |
|
| 1846 |
distance_of_time_in_words(Time.now, child_page.updated_on))) |
|
| 1844 |
l(:label_updated_time, format_timestamp(child_page.updated_on))) |
|
| 1847 | 1845 |
end |
| 1848 | 1846 | |
| 1849 | 1847 |
def test_render_page_hierarchy_when_action_is_export |
| test/helpers/settings_helper_test.rb | ||
|---|---|---|
| 29 | 29 |
options = date_format_setting_options('en')
|
| 30 | 30 |
assert_include ["2015-07-14 (yyyy-mm-dd)", "%Y-%m-%d"], options |
| 31 | 31 |
end |
| 32 | ||
| 33 |
def test_timestamp_format_setting_options_should_include_human_readable_format |
|
| 34 |
set_language_if_valid 'en' |
|
| 35 |
sample_time = Time.now.ago(3.days) |
|
| 36 | ||
| 37 |
options = timestamp_format_setting_options |
|
| 38 |
assert_include ["3 days (Relative time)", 'relative_time'], options |
|
| 39 |
assert_include ["3 days (#{format_time(sample_time)}) (Relative time with exact time)", 'relative_time_with_exact_time'], options
|
|
| 40 |
assert_include ["#{format_time(sample_time)} (Exact time)", 'exact_time'], options
|
|
| 41 |
end |
|
| 32 | 42 |
end |
| test/unit/lib/redmine/i18n_test.rb | ||
|---|---|---|
| 22 | 22 |
class Redmine::I18nTest < ActiveSupport::TestCase |
| 23 | 23 |
include Redmine::I18n |
| 24 | 24 |
include ActionView::Helpers::NumberHelper |
| 25 |
include ActionView::Helpers::DateHelper |
|
| 25 | 26 | |
| 26 | 27 |
def setup |
| 27 | 28 |
User.current = nil |
| ... | ... | |
| 125 | 126 |
end |
| 126 | 127 |
end |
| 127 | 128 | |
| 129 |
def test_format_timestamp |
|
| 130 |
User.current = User.find(1) |
|
| 131 |
User.current.pref.time_zone = 'UTC' |
|
| 132 |
User.current.pref.save! |
|
| 133 | ||
| 134 |
travel_to Time.utc(2023, 5, 31, 23, 59, 59) # Time.now => 2023/5/31 23:59:59 |
|
| 135 |
sample_time = Time.utc(2022, 1, 1, 0, 0, 0) |
|
| 136 | ||
| 137 |
with_settings :timestamp_format => 'relative_time' do |
|
| 138 |
assert_equal 'over 1 year', format_timestamp(sample_time) |
|
| 139 |
end |
|
| 140 |
with_settings :timestamp_format => 'relative_time_with_exact_time', :date_format => '%d/%m/%Y', :time_format => '%H:%M' do |
|
| 141 |
assert_equal 'over 1 year (01/01/2022 00:00)', format_timestamp(sample_time) |
|
| 142 |
end |
|
| 143 |
with_settings :timestamp_format => 'exact_time', :date_format => '%d/%m/%Y', :time_format => '%H:%M' do |
|
| 144 |
assert_equal '01/01/2022 00:00', format_timestamp(sample_time) |
|
| 145 |
end |
|
| 146 | ||
| 147 |
# If there is an argument, it takes precedence over the set value |
|
| 148 |
with_settings :timestamp_format => 'exact_time', :date_format => '%d/%m/%Y', :time_format => '%H:%M' do |
|
| 149 |
assert_equal 'over 1 year', format_timestamp(sample_time, 'relative_time') |
|
| 150 |
end |
|
| 151 |
end |
|
| 152 | ||
| 153 |
def test_label_by_timestamp_format |
|
| 154 |
with_settings :timestamp_format => 'relative_time' do |
|
| 155 |
assert_equal :label_added_time_by, label_by_timestamp_format(:label_added_time_by) |
|
| 156 |
assert_equal :label_updated_time_by, label_by_timestamp_format(:label_updated_time_by) |
|
| 157 |
assert_equal :label_updated_time, label_by_timestamp_format(:label_updated_time) |
|
| 158 |
end |
|
| 159 |
with_settings :timestamp_format => 'relative_time_with_exact_time' do |
|
| 160 |
assert_equal :label_added_time_by, label_by_timestamp_format(:label_added_time_by) |
|
| 161 |
assert_equal :label_updated_time_by, label_by_timestamp_format(:label_updated_time_by) |
|
| 162 |
assert_equal :label_updated_time, label_by_timestamp_format(:label_updated_time) |
|
| 163 |
end |
|
| 164 |
with_settings :timestamp_format => 'exact_time' do |
|
| 165 |
assert_equal :label_added_exact_time_by, label_by_timestamp_format(:label_added_time_by) |
|
| 166 |
assert_equal :label_updated_exact_time_by, label_by_timestamp_format(:label_updated_time_by) |
|
| 167 |
assert_equal :label_updated_exact_time, label_by_timestamp_format(:label_updated_time) |
|
| 168 |
end |
|
| 169 |
end |
|
| 170 | ||
| 128 | 171 |
def test_number_to_human_size_for_each_language |
| 129 | 172 |
valid_languages.each do |lang| |
| 130 | 173 |
set_language_if_valid lang |