teach tz.format to recognize localization keys
refs CNVS-19516 so you can say e.g. tz.format(date, 'time.formats.tiny') and it will choose the appropriate format string based on the locale. at the same time, use localized format string (using this new functionality) for $.dateString (and by extensions, datetimeString) instead of hard-coded english-style format strings. finally, refactor out I18nStubber javascript spec helper for testing this. test-plan: - change you profile locale to french - view a time formatted by $.datetimeString in the UI, e.g. due dates in a course's assignments page - should show with "<day> <month> <year> à <24-hour time>" rather than using "<month> <day>, <year>" and/or "<12-hour time with am/pm>" Change-Id: Ic7779917d7af5e0fe9d4ef3cd99e6f12cf141c3c Reviewed-on: https://gerrit.instructure.com/51447 Reviewed-by: Cody Cutrer <cody@instructure.com> Tested-by: Jenkins QA-Review: Jeremy Putnam <jeremyp@instructure.com> Product-Review: Jacob Fugal <jacob@instructure.com>
This commit is contained in:
parent
c5f77d76f4
commit
2c7a691bf0
|
@ -133,7 +133,7 @@ var speakMessage = function ($this, message) {
|
|||
if (date == null) return "";
|
||||
var timezone = options && options.timezone;
|
||||
var format = options && options.format;
|
||||
format = (format !== 'medium') && $.sameYear(date, new Date()) ? '%b %-d' : '%b %-d, %Y';
|
||||
format = (format !== 'medium') && $.sameYear(date, new Date()) ? 'date.formats.short' : 'date.formats.medium';
|
||||
if (typeof timezone == 'string' || timezone instanceof String) {
|
||||
return tz.format(date, format, timezone) || '';
|
||||
} else {
|
||||
|
@ -144,13 +144,10 @@ var speakMessage = function ($this, message) {
|
|||
$.timeString = function(date, options) {
|
||||
if (date == null) return "";
|
||||
var timezone = options && options.timezone;
|
||||
// lookup format according to locale, and then turn %l or %k into %-l or
|
||||
// %-k, respectively, to avoid extra unnecessary space characters
|
||||
var fmt = I18n.t("#time.formats.tiny").replace(/^%([kl])/, "%-$1");
|
||||
if (typeof timezone == 'string' || timezone instanceof String) {
|
||||
return tz.format(date, fmt, timezone) || '';
|
||||
return tz.format(date, 'time.formats.tiny', timezone) || '';
|
||||
} else {
|
||||
return tz.format(date, fmt) || '';
|
||||
return tz.format(date, 'time.formats.tiny') || '';
|
||||
}
|
||||
};
|
||||
$.datetimeString = function(datetime, options) {
|
||||
|
|
|
@ -2,8 +2,9 @@ define([
|
|||
"jquery",
|
||||
"underscore",
|
||||
"require",
|
||||
"vendor/timezone"
|
||||
], function($, _, require, tz) {
|
||||
"vendor/timezone",
|
||||
"i18nObj"
|
||||
], function($, _, require, tz, I18n) {
|
||||
// start with the bare vendor-provided tz() function
|
||||
var _tz = tz;
|
||||
var _preloadedData = {};
|
||||
|
@ -44,6 +45,25 @@ define([
|
|||
var datetime = tz.parse(value);
|
||||
if (datetime == null) return null;
|
||||
|
||||
// translate recognized 'date.formats.*' and 'time.formats.*' to
|
||||
// appropriate format strings according to locale.
|
||||
if (format.match(/^(date|time)\.formats\./)) {
|
||||
var locale_format = I18n.lookup(format);
|
||||
if (locale_format) {
|
||||
// in the process, turn %l, %k, and %e into %-l, %-k, and %-e
|
||||
// (respectively) to avoid extra unnecessary space characters
|
||||
//
|
||||
// javascript doesn't have lookbehind, so do the fixing on the reversed
|
||||
// string so we can use lookahead instead. the funky '(%%)*(?!%)' pattern
|
||||
// in all the regexes is to make sure we match (once unreversed), e.g.,
|
||||
// both %l and %%%l (literal-% + %l) but not %%l (literal-% + l).
|
||||
format = locale_format.
|
||||
split("").reverse().join("").
|
||||
replace(/([lke])(?=%(%%)*(?!%))/, '$1-').
|
||||
split("").reverse().join("");
|
||||
}
|
||||
}
|
||||
|
||||
// some locales may not (according to bigeasy's localization files) use
|
||||
// an am/pm distinction, but could then be incorrectly used with 12-hour
|
||||
// format strings (e.g. %l:%M%P), whether by erroneous format strings in
|
||||
|
@ -51,12 +71,8 @@ define([
|
|||
// result, you might get 3am and 3pm both formatting to the same value.
|
||||
// to prevent this, 12-hour indicators with an am/pm indicator should be
|
||||
// promoted to the equivalent 24-hour indicator when the locale defines
|
||||
// %P as an empty string.
|
||||
|
||||
// javascript doesn't have lookbehind, so do the fixing on the reversed
|
||||
// string so we can use lookahead instead. the funky '(%%)*(?!%)' pattern
|
||||
// in all the regexes is to make sure we match (once unreversed), e.g.,
|
||||
// both %l and %%%l (literal-% + %l) but not %%l (literal-% + l).
|
||||
// %P as an empty string. ("reverse, look-ahead, reverse" pattern for
|
||||
// same reason as above)
|
||||
format = format.split("").reverse().join("");
|
||||
if (_tz(datetime, '%P') === '' &&
|
||||
((format.match(/[lI][-_]?%(%%)*(?!%)/) &&
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
define ['i18nObj'], (I18n) ->
|
||||
frames = []
|
||||
|
||||
I18nStubber =
|
||||
pushFrame: ->
|
||||
frames.push
|
||||
locale: I18n.locale
|
||||
translations: I18n.translations
|
||||
I18n.translations = {'en': {}}
|
||||
|
||||
popFrame: ->
|
||||
throw 'I18nStubber: pop without a stored frame' unless frames.length
|
||||
{locale, translations} = frames.pop()
|
||||
I18n.locale = locale
|
||||
I18n.translations = translations
|
||||
|
||||
stub: (locale, translations) ->
|
||||
throw 'I18nStubber: stub without a stored frame' unless frames.length
|
||||
scope = I18n.translations
|
||||
scope[locale] = {} unless scope[locale]
|
||||
locale = scope[locale]
|
||||
for key, value of translations
|
||||
scope = locale
|
||||
parts = key.split('.')
|
||||
last = parts.pop()
|
||||
for part in parts
|
||||
scope[part] = {} unless scope[part]
|
||||
scope = scope[part]
|
||||
scope[last] = value
|
||||
|
||||
setLocale: (locale) ->
|
||||
throw 'I18nStubber: setLocale without a stored frame' unless frames.length
|
||||
I18n.locale = locale
|
|
@ -4,9 +4,9 @@ define [
|
|||
'vendor/timezone/America/Detroit'
|
||||
'vendor/timezone/America/Juneau'
|
||||
'vendor/timezone/pt_PT'
|
||||
'i18nObj'
|
||||
'helpers/I18nStubber'
|
||||
'jquery.instructure_date_and_time'
|
||||
], ($, tz, detroit, juneau, portuguese, I18n) ->
|
||||
], ($, tz, detroit, juneau, portuguese, I18nStubber) ->
|
||||
module 'fudgeDateForProfileTimezone',
|
||||
setup: ->
|
||||
@snapshot = tz.snapshot()
|
||||
|
@ -137,65 +137,73 @@ define [
|
|||
ok !$.midnight(date3)
|
||||
|
||||
module 'dateString',
|
||||
setup: -> @snapshot = tz.snapshot()
|
||||
teardown: -> tz.restore(@snapshot)
|
||||
setup: ->
|
||||
@snapshot = tz.snapshot()
|
||||
I18nStubber.pushFrame()
|
||||
|
||||
teardown: ->
|
||||
tz.restore(@snapshot)
|
||||
I18nStubber.popFrame()
|
||||
|
||||
test 'should format in profile timezone', ->
|
||||
I18nStubber.stub 'en', 'date.formats.medium': "%b %-d, %Y"
|
||||
tz.changeZone(detroit, 'America/Detroit')
|
||||
equal $.dateString(new Date(0)), 'Dec 31, 1969'
|
||||
|
||||
module 'timeString',
|
||||
setup: ->
|
||||
@snapshot = tz.snapshot()
|
||||
@localeWas = I18n.locale
|
||||
@translationsWas = I18n.translations
|
||||
I18n.translations =
|
||||
'en': {'time': {'formats': {'tiny': "%l:%M%P"}}}
|
||||
'en-GB': {'time': {'formats': {'tiny': "%k:%M"}}}
|
||||
I18nStubber.pushFrame()
|
||||
|
||||
teardown: ->
|
||||
tz.restore(@snapshot)
|
||||
I18n.locale = @localeWas
|
||||
I18n.translations = @translationsWas
|
||||
I18nStubber.popFrame()
|
||||
|
||||
test 'should format in profile timezone', ->
|
||||
I18nStubber.stub 'en', 'time.formats.tiny': "%l:%M%P"
|
||||
tz.changeZone(detroit, 'America/Detroit')
|
||||
equal $.timeString(new Date(0)), '7:00pm'
|
||||
|
||||
test 'should format according to profile locale', ->
|
||||
I18n.locale = 'en-GB'
|
||||
I18nStubber.setLocale 'en-GB'
|
||||
I18nStubber.stub 'en-GB', 'time.formats.tiny': "%k:%M"
|
||||
equal $.timeString(new Date(46800000)), '13:00'
|
||||
|
||||
module 'datetimeString',
|
||||
setup: ->
|
||||
@snapshot = tz.snapshot()
|
||||
@localeWas = I18n.locale
|
||||
@translationsWas = I18n.translations
|
||||
I18n.translations =
|
||||
'en': {'time': {'formats': {'tiny': "%l:%M%P"}}}
|
||||
'pt': {'time': {'event': "%{date} em %{time}"}}
|
||||
I18nStubber.pushFrame()
|
||||
|
||||
teardown: ->
|
||||
tz.restore(@snapshot)
|
||||
I18n.locale = @localeWas
|
||||
I18n.translations = @translationsWas
|
||||
I18nStubber.popFrame()
|
||||
|
||||
test 'should format in profile timezone', ->
|
||||
tz.changeZone(detroit, 'America/Detroit')
|
||||
I18nStubber.stub 'en',
|
||||
'date.formats.medium': "%b %-d, %Y"
|
||||
'time.formats.tiny': "%l:%M%P"
|
||||
'time.event': "%{date} at %{time}"
|
||||
equal $.datetimeString(new Date(0)), 'Dec 31, 1969 at 7:00pm'
|
||||
|
||||
test 'should translate into the profile locale', ->
|
||||
tz.changeLocale(portuguese, 'pt_PT')
|
||||
I18n.locale = 'pt'
|
||||
equal $.datetimeString('1970-01-01 15:00:00Z'), "Jan 1, 1970 em 15:00"
|
||||
I18nStubber.setLocale 'pt'
|
||||
I18nStubber.stub 'pt',
|
||||
'date.formats.medium': "%-d %b %Y"
|
||||
'time.formats.tiny': "%k:%M"
|
||||
'time.event': "%{date} em %{time}"
|
||||
equal $.datetimeString('1970-01-01 15:00:00Z'), "1 Jan 1970 em 15:00"
|
||||
|
||||
# TODO: remove these second argument specs once the pickers know how to parse
|
||||
# localized datetimes
|
||||
test 'should not localize if second argument is false', ->
|
||||
tz.changeLocale(portuguese, 'pt_PT')
|
||||
I18n.locale = 'pt'
|
||||
I18nStubber.setLocale 'pt'
|
||||
equal $.datetimeString('1970-01-01 15:00:00Z', {localized: false}), "Jan 1, 1970 at 3:00pm"
|
||||
|
||||
test 'should still apply profile timezone when second argument is false', ->
|
||||
tz.changeZone(detroit, 'America/Detroit')
|
||||
tz.changeLocale(portuguese, 'pt_PT')
|
||||
I18n.locale = 'pt'
|
||||
I18nStubber.setLocale 'pt'
|
||||
equal $.datetimeString(new Date(0), {localized: false}), 'Dec 31, 1969 at 7:00pm'
|
||||
|
|
|
@ -3,15 +3,20 @@ define [
|
|||
'vendor/timezone/America/Detroit'
|
||||
'vendor/timezone/fr_FR'
|
||||
'vendor/timezone/pt_PT'
|
||||
], (tz, detroit, french, portuguese)->
|
||||
'helpers/I18nStubber'
|
||||
], (tz, detroit, french, portuguese, I18nStubber)->
|
||||
|
||||
module 'timezone',
|
||||
setup: ->
|
||||
@snapshot = tz.snapshot()
|
||||
I18nStubber.pushFrame()
|
||||
|
||||
teardown: ->
|
||||
tz.restore(@snapshot)
|
||||
I18nStubber.popFrame()
|
||||
|
||||
moonwalk = new Date(Date.UTC(1969, 6, 21, 2, 56))
|
||||
epoch = new Date(Date.UTC(1970, 0, 1, 0, 0))
|
||||
|
||||
test 'parse(valid datetime string)', ->
|
||||
equal +tz.parse(moonwalk.toISOString()), +moonwalk
|
||||
|
@ -68,6 +73,32 @@ define [
|
|||
equal tz.format(time, '%I%P'), "15"
|
||||
equal tz.format(time, '%r'), "15:00:00"
|
||||
|
||||
test "format() should recognize date.formats.*", ->
|
||||
I18nStubber.stub 'en', 'date.formats.short': '%b %-d'
|
||||
equal tz.format(moonwalk, 'date.formats.short'), "Jul 21"
|
||||
|
||||
test "format() should recognize time.formats.*", ->
|
||||
I18nStubber.stub 'en', 'time.formats.tiny': '%-l:%M%P'
|
||||
equal tz.format(epoch, 'time.formats.tiny'), "12:00am"
|
||||
|
||||
test "format() should localize when given a localization key", ->
|
||||
tz.changeLocale(french, 'fr_FR')
|
||||
I18nStubber.setLocale 'fr_FR'
|
||||
I18nStubber.stub 'fr_FR', 'date.formats.full': '%-d %b %Y %-l:%M%P'
|
||||
equal tz.format(moonwalk, 'date.formats.full'), "21 juil. 1969 2:56"
|
||||
|
||||
test "format() should automatically convert %l to %-l when given a localization key", ->
|
||||
I18nStubber.stub 'en', 'time.formats.tiny': '%l:%M%P'
|
||||
equal tz.format(moonwalk, 'time.formats.tiny'), "2:56am"
|
||||
|
||||
test "format() should automatically convert %k to %-k when given a localization key", ->
|
||||
I18nStubber.stub 'en', 'time.formats.tiny': '%k:%M'
|
||||
equal tz.format(moonwalk, 'time.formats.tiny'), "2:56"
|
||||
|
||||
test "format() should automatically convert %e to %-e when given a localization key", ->
|
||||
I18nStubber.stub 'en', 'date.formats.short': '%b %e'
|
||||
equal tz.format(epoch, 'date.formats.short'), "Jan 1"
|
||||
|
||||
test 'shift() should adjust the date as appropriate', ->
|
||||
equal +tz.shift(moonwalk, '-1 day'), moonwalk - 86400000
|
||||
|
||||
|
|
Loading…
Reference in New Issue