Browse Source

Feature: Added support for English am/pm meridian time format.

Martin Gruner 3 years ago
parent
commit
50e3b98955

+ 5 - 0
app/assets/javascripts/app/lib/app_post/i18n.coffee

@@ -348,6 +348,9 @@ class _i18nSingleton extends Spine.Module
     S      = timeObject.getSeconds()
     M      = timeObject.getMinutes()
     H      = timeObject.getHours()
+    l      = (H + 11) % 12 + 1
+    l      = ' ' + l if l < 10
+
     format = format
       .replace(/dd/, @formatNumber(d, 2))
       .replace(/d/, d)
@@ -358,4 +361,6 @@ class _i18nSingleton extends Spine.Module
       .replace(/SS/, @formatNumber(S, 2))
       .replace(/MM/, @formatNumber(M, 2))
       .replace(/HH/, @formatNumber(H, 2))
+      .replace(/l/, l)
+      .replace(/P/, if H >= 12 then 'pm' else 'am')
     format

+ 2 - 0
app/models/translation.rb

@@ -164,6 +164,8 @@ or
     record.sub!('SS', format('%<second>02d', second: timestamp.sec.to_s))
     record.sub!('MM', format('%<minute>02d', minute: timestamp.min.to_s))
     record.sub!('HH', format('%<hour>02d', hour: timestamp.hour.to_s))
+    record.sub!('l', timestamp.strftime('%l'))
+    record.sub!('P', timestamp.strftime('%P'))
     "#{record} (#{timezone})"
   end
 

+ 7 - 3
i18n/zammad.pot

@@ -20,7 +20,9 @@ msgstr ""
 #. - 'yy' - last 2 digits of year
 #. - 'SS' - 2-digit second
 #. - 'MM' - 2-digit minute
-#. - 'HH' - 2-digit hour
+#. - 'HH' - 2-digit hour (24h)
+#. - 'l' - hour (12h)
+#. - 'P' - Meridian indicator ('am' or 'pm')
 msgid "FORMAT_DATE"
 msgstr "mm/dd/yyyy"
 
@@ -34,9 +36,11 @@ msgstr "mm/dd/yyyy"
 #. - 'yy' - last 2 digits of year
 #. - 'SS' - 2-digit second
 #. - 'MM' - 2-digit minute
-#. - 'HH' - 2-digit hour
+#. - 'HH' - 2-digit hour (24h)
+#. - 'l' - hour (12h)
+#. - 'P' - Meridian indicator ('am' or 'pm')
 msgid "FORMAT_DATETIME"
-msgstr "mm/dd/yyyy HH:MM"
+msgstr "mm/dd/yyyy l:MM P"
 
 #: db/seeds/settings.rb
 msgid "\"Database\" stores all attachments in the database (not recommended for storing large amounts of data). \"Filesystem\" stores the data in the filesystem. You can switch between the modules even on a system that is already in production without any loss of data."

+ 7 - 2
lib/generators/translation_catalog/translation_catalog_generator.rb

@@ -32,7 +32,9 @@ class Generators::TranslationCatalog::TranslationCatalogGenerator < Rails::Gener
     #. - 'yy' - last 2 digits of year
     #. - 'SS' - 2-digit second
     #. - 'MM' - 2-digit minute
-    #. - 'HH' - 2-digit hour
+    #. - 'HH' - 2-digit hour (24h)
+    #. - 'l' - hour (12h)
+    #. - 'P' - Meridian indicator ('am' or 'pm')
   LEGEND
 
   def extract_strings(path)
@@ -91,6 +93,9 @@ class Generators::TranslationCatalog::TranslationCatalogGenerator < Rails::Gener
 
     POT_HEADER
 
+    # Add the default date/time format strings for 'en_US' as translations to
+    #   the source catalog file. They will be read into the Translation model
+    #   and can be customized via the translations admin GUI.
     pot += <<~FORMAT_STRINGS if !options['addon_path']
       #. Default date format to use for the current locale.
       #{DATE_FORMAT_LEGEND}
@@ -100,7 +105,7 @@ class Generators::TranslationCatalog::TranslationCatalogGenerator < Rails::Gener
       #. Default date/time format to use for the current locale.
       #{DATE_FORMAT_LEGEND}
       msgid "FORMAT_DATETIME"
-      msgstr "mm/dd/yyyy HH:MM"
+      msgstr "mm/dd/yyyy l:MM P"
 
     FORMAT_STRINGS
 

+ 7 - 1
public/assets/tests/qunit/html_utils.js

@@ -1362,7 +1362,13 @@ QUnit.test("check replace tags", assert => {
     yfull     = localTime.getFullYear()
     M         = formatNumber(localTime.getMinutes(), 2)
     H         = formatNumber(localTime.getHours(), 2)
-    return m + '/' + d + '/' + yfull + ' ' + H + ':' + M
+    l         = (H + 11) % 12 + 1
+    if (l < 10) {
+      l = ' ' + l
+    }
+    P         = H >= 12 ? 'pm' : 'am'
+
+    return m + '/' + d + '/' + yfull + ' ' + l + ':' + M + ' ' + P
   }
 
   var message = "<div>#{user.firstname} #{user.lastname}</div>"

+ 6 - 3
public/assets/tests/qunit/i18n.js

@@ -217,8 +217,11 @@ QUnit.test('i18n', assert => {
   translated = App.i18n.translateContent('Enables user authentication via %s. Register your app first at [%s](%s).', 'XXX', 'YYY', 'http://lalala')
   assert.equal(translated, 'Enables user authentication via XXX. Register your app first at <a href="http://lalala" target="_blank">YYY</a>.', 'en-us - link')
 
-  timestamp = App.i18n.translateTimestamp('2012-11-06T21:07:24Z', offset)
-  assert.equal(timestamp, '11/06/2012 21:07', 'en - timestamp translated correctly')
+  timestamp = App.i18n.translateTimestamp('2012-11-06T09:07:24Z', offset)
+  assert.equal(timestamp, '11/06/2012  9:07 am', 'en - timestamp translated correctly (pm)')
+
+  timestamp = App.i18n.translateTimestamp('2012-11-06T22:07:24Z', offset)
+  assert.equal(timestamp, '11/06/2012 10:07 pm', 'en - timestamp translated correctly (pm)')
 
   timestamp = App.i18n.translateTimestamp('', offset);
   assert.equal(timestamp, '', 'en - timestamp translated correctly')
@@ -242,7 +245,7 @@ QUnit.test('i18n', assert => {
   assert.equal(date, undefined, 'en - date translated correctly')
 
   date = App.i18n.timeFormat()
-  assert.deepEqual(date, { "FORMAT_DATE": "mm/dd/yyyy", "FORMAT_DATETIME": "mm/dd/yyyy HH:MM" }, 'timeFormat property')
+  assert.deepEqual(date, { "FORMAT_DATE": "mm/dd/yyyy", "FORMAT_DATETIME": "mm/dd/yyyy l:MM P" }, 'timeFormat property')
 
   // Verify that the datepicker gets the correct format too.
   el_date = App.UiElement.date.render({name: 'test', value: '2018-07-06'})

+ 2 - 2
public/assets/tests/qunit/model_ui.js

@@ -54,7 +54,7 @@ QUnit.test( "model ui basic tests", assert => {
   assert.equal( App.viewPrint( ticket, 'state' ), 'open')
   assert.equal( App.viewPrint( ticket, 'state_id' ), 'open')
   assert.equal( App.viewPrint( ticket, 'not_existing' ), '-')
-  assert.equal( App.viewPrint( ticket, 'updated_at' ), '<time class="humanTimeFromNow " datetime="2014-11-07T23:43:08.000Z" title="11/07/2014 23:43">11/07/2014</time>')
+  assert.equal( App.viewPrint( ticket, 'updated_at' ), '<time class="humanTimeFromNow " datetime="2014-11-07T23:43:08.000Z" title="11/07/2014 11:43 pm">11/07/2014</time>')
   assert.equal( App.viewPrint( ticket, 'date' ), '02/07/2015')
   assert.equal( App.viewPrint( ticket, 'textarea' ), '<div>some new</div><div>line</div>')
   assert.equal( App.viewPrint( ticket, 'link1' ), '<a href="http://zammad.com" target="blank">closed</a>')
@@ -65,7 +65,7 @@ QUnit.test( "model ui basic tests", assert => {
   let attr = App.Ticket.configure_attributes.find(e => { return e.name == 'updated_at' })
   attr.include_timezone = true
 
-  assert.equal( App.viewPrint( ticket, 'updated_at' ), '<time class="humanTimeFromNow " datetime="2014-11-07T23:43:08.000Z" title="11/07/2014 23:43 Example/Timezone" timezone="Example/Timezone">11/07/2014</time>')
+  assert.equal( App.viewPrint( ticket, 'updated_at' ), '<time class="humanTimeFromNow " datetime="2014-11-07T23:43:08.000Z" title="11/07/2014 11:43 pm Example/Timezone" timezone="Example/Timezone">11/07/2014</time>')
 
   attr.include_timezone = false
   stub.restore()

+ 6 - 1
public/assets/tests/qunit/ui.js

@@ -362,9 +362,14 @@ QUnit.test("check pretty date", assert => {
     yshort = date.getYear()-100
     M      = date.getMinutes()
     H      = date.getHours()
+    l      = (H + 11) % 12 + 1
+    if (l < 10) {
+      l = ' ' + l
+    }
+    P      = H >= 12 ? 'pm' : 'am'
 
     // YYYY-MM-DD HH::MM
-    return (m < 10 ? '0':'') + m + '/' + (d < 10 ? '0':'') + d + '/' + (yfull) + ' ' + (H < 10 ? '0':'') + H + ':' + (M < 10 ? '0':'') + M
+    return (m < 10 ? '0':'') + m + '/' + (d < 10 ? '0':'') + d + '/' + (yfull) + ' ' + l + ':' + (M < 10 ? '0':'') + M + ' ' + P
   }
 
 });

+ 2 - 2
spec/lib/notification_factory/slack_spec.rb

@@ -60,7 +60,7 @@ RSpec.describe NotificationFactory::Slack do
           .to match(%r{Updated by #{current_user.fullname}})
           .and match(%r{state: aaa -> bbb})
           .and match(%r{group: xxx -> yyy})
-          .and match(%r{pending_time: 04/01/2019 12:00 \(Europe/Berlin\) -> 04/02/2019 01:00 \(Europe/Berlin\)})
+          .and match(%r{pending_time: 04/01/2019 12:00 pm \(Europe/Berlin\) -> 04/02/2019  1:00 am \(Europe/Berlin\)})
           .and match(%r{#{article.body}\z})
       end
     end
@@ -90,7 +90,7 @@ RSpec.describe NotificationFactory::Slack do
       it 'returns a hash with body: <ticket customer, escalation time, & body>' do
         expect(template[:body])
           .to match(%r{A ticket \(#{ticket.title}\) from "#{ticket.customer.fullname}"})
-          .and match(%r{is escalated since "04/01/2019 12:00 \(Europe/Berlin\)"!})
+          .and match(%r{is escalated since "04/01/2019 12:00 pm \(Europe/Berlin\)"!})
           .and match(%r{#{article.body}\z})
       end
     end

+ 2 - 2
spec/models/translation/translation_synchronizes_from_po_spec.rb

@@ -56,7 +56,7 @@ RSpec.describe Translation do
       end
 
       it 'contains the translation for "FORMAT_DATE_TIME"' do
-        expect(described_class.strings_for_locale('en-us')['FORMAT_DATETIME']).to have_attributes(translation: 'mm/dd/yyyy HH:MM', translation_file: 'i18n/zammad.pot')
+        expect(described_class.strings_for_locale('en-us')['FORMAT_DATETIME']).to have_attributes(translation: 'mm/dd/yyyy l:MM P', translation_file: 'i18n/zammad.pot')
       end
     end
 
@@ -210,7 +210,7 @@ RSpec.describe Translation do
     end
 
     it 'adds the en-us FORMAT_DATETIME entry' do
-      expect(described_class.find_source('en-us', 'FORMAT_DATETIME')).to have_attributes(is_synchronized_from_codebase: true, synchronized_from_translation_file: 'i18n/zammad.pot', target: 'mm/dd/yyyy HH:MM')
+      expect(described_class.find_source('en-us', 'FORMAT_DATETIME')).to have_attributes(is_synchronized_from_codebase: true, synchronized_from_translation_file: 'i18n/zammad.pot', target: 'mm/dd/yyyy l:MM P')
     end
 
     it 'adds the custom translated entry with content' do

Some files were not shown because too many files changed in this diff