Skip to content

Commit

Permalink
🐛 bug: fix datetime and number fallback localization
Browse files Browse the repository at this point in the history
Closes #168
  • Loading branch information
kazupon committed Jun 2, 2017
1 parent a4c971e commit be9e1bd
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 58 deletions.
132 changes: 92 additions & 40 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,35 +346,61 @@ export default class VueI18n {
this._vm.dateTimeFormats[locale] = Vue.util.extend(this.getDateTimeFormat(locale), format)
}

_d (value: number | Date, _locale: Locale, key: ?string): DateTimeFormatResult {
_localizeDateTime (
value: number | Date,
locale: Locale,
fallback: Locale,
dateTimeFormats: DateTimeFormats,
key: ?string
): ?DateTimeFormatResult {
let _locale: Locale = locale
let formats: DateTimeFormat = dateTimeFormats[_locale]

// fallback locale
if (isNull(formats) || isNull(formats[key])) {
if (process.env.NODE_ENV !== 'production') {
warn(`Fall back to '${fallback}' datetime formats from '${locale} datetime formats.`)
}
_locale = fallback
formats = dateTimeFormats[_locale]
}

if (isNull(formats) || isNull(formats[key])) {
return null
} else {
const format: ?DateTimeFormatOptions = formats[key]
const id = `${_locale}__${key}`
let formatter = this._dateTimeFormatters[id]
if (!formatter) {
formatter = this._dateTimeFormatters[id] = new Intl.DateTimeFormat(_locale, format)
}
return formatter.format(value)
}
}

_d (value: number | Date, locale: Locale, key: ?string): DateTimeFormatResult {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !VueI18n.availabilities.dateTimeFormat) {
warn('Cannot format a Date value due to not support Intl.DateTimeFormat.')
return ''
}

let ret = ''
const dateTimeFormats = this._getDateTimeFormats()
if (key) {
let locale: Locale = _locale
if (isNull(dateTimeFormats[_locale][key])) {
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
warn(`Fall back to the dateTimeFormat of key '${key}' with '${this.fallbackLocale}' locale.`)
}
locale = this.fallbackLocale
}
const id = `${locale}__${key}`
let formatter = this._dateTimeFormatters[id]
const format = dateTimeFormats[locale][key]
if (!formatter) {
formatter = this._dateTimeFormatters[id] = Intl.DateTimeFormat(locale, format)
if (!key) {
return new Intl.DateTimeFormat(locale).format(value)
}

const ret: ?DateTimeFormatResult =
this._localizeDateTime(value, locale, this.fallbackLocale, this._getDateTimeFormats(), key)
if (this._isFallbackRoot(ret)) {
if (process.env.NODE_ENV !== 'production') {
warn(`Fall back to datetime localization of root: key '${key}' .`)
}
ret = formatter.format(value)
/* istanbul ignore if */
if (!this._root) { throw Error('unexpected error') }
return this._root.d(value, key, locale)
} else {
ret = Intl.DateTimeFormat(_locale).format(value)
return ret
}

return ret
}

d (value: number | Date, ...args: any): DateTimeFormatResult {
Expand Down Expand Up @@ -416,35 +442,61 @@ export default class VueI18n {
this._vm.numberFormats[locale] = Vue.util.extend(this.getNumberFormat(locale), format)
}

_n (value: number, _locale: Locale, key: ?string): NumberFormatResult {
_localizeNumber (
value: number,
locale: Locale,
fallback: Locale,
numberFormats: NumberFormats,
key: string
): ?NumberFormatResult {
let _locale: Locale = locale
let formats: NumberFormat = numberFormats[_locale]

// fallback locale
if (isNull(formats) || isNull(formats[key])) {
if (process.env.NODE_ENV !== 'production') {
warn(`Fall back to '${fallback}' number formats from '${locale} number formats.`)
}
_locale = fallback
formats = numberFormats[_locale]
}

if (isNull(formats) || isNull(formats[key])) {
return null
} else {
const format: ?NumberFormatOptions = formats[key]
const id = `${_locale}__${key}`
let formatter = this._numberFormatters[id]
if (!formatter) {
formatter = this._numberFormatters[id] = new Intl.NumberFormat(_locale, format)
}
return formatter.format(value)
}
}

_n (value: number, locale: Locale, key: ?string): NumberFormatResult {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !VueI18n.availabilities.numberFormat) {
warn('Cannot format a Date value due to not support Intl.NumberFormat.')
return ''
}

let ret = ''
const numberFormats = this._getNumberFormats()
if (key) {
let locale: Locale = _locale
if (isNull(numberFormats[_locale][key])) {
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
warn(`Fall back to the numberFormat of key '${key}' with '${this.fallbackLocale}' locale.`)
}
locale = this.fallbackLocale
}
const id = `${locale}__${key}`
let formatter = this._numberFormatters[id]
const format = numberFormats[locale][key]
if (!formatter) {
formatter = this._numberFormatters[id] = Intl.NumberFormat(locale, format)
if (!key) {
return new Intl.NumberFormat(locale).format(value)
}

const ret: ?NumberFormatResult =
this._localizeNumber(value, locale, this.fallbackLocale, this._getNumberFormats(), key)
if (this._isFallbackRoot(ret)) {
if (process.env.NODE_ENV !== 'production') {
warn(`Fall back to number localization of root: key '${key}' .`)
}
ret = formatter.format(value)
/* istanbul ignore if */
if (!this._root) { throw Error('unexpected error') }
return this._root.n(value, key, locale)
} else {
ret = Intl.NumberFormat(_locale).format(value)
return ret
}

return ret
}

n (value: number, ...args: any): NumberFormatResult {
Expand Down
59 changes: 41 additions & 18 deletions test/unit/component.test.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import dateTimeFormats from './fixture/datetime'
import numberFormats from './fixture/number'

describe('component translation', () => {
let vm, i18n
const dt = new Date(Date.UTC(2012, 11, 20, 3, 0, 0))
const money = 101
const messages = {
'en-US': {
who: 'root',
fallback: 'fallback'
},
'ja-JP': {
who: 'ルート',
fallback: 'フォールバック'
}
}

beforeEach(done => {
i18n = new VueI18n({
locale: 'ja',
messages: {
en: {
who: 'root',
fallback: 'fallback'
},
ja: {
who: 'ルート',
fallback: 'フォールバック'
}
}
locale: 'ja-JP',
messages,
dateTimeFormats,
numberFormats
})

const el = document.createElement('div')
Expand All @@ -21,11 +30,11 @@ describe('component translation', () => {
components: {
child1: { // translation with component
i18n: {
locale: 'en',
locale: 'en-US',
sync: false,
messages: {
en: { who: 'child1' },
ja: { who: '子1' }
'en-US': { who: 'child1' },
'ja-JP': { who: '子1' }
}
},
components: {
Expand All @@ -41,6 +50,8 @@ describe('component translation', () => {
return h('div', {}, [
h('p', { ref: 'who' }, [this.$t('who')]),
h('p', { ref: 'fallback' }, [this.$t('fallback')]),
h('p', { ref: 'datetime' }, [this.$d(dt, 'short')]),
h('p', { ref: 'number' }, [this.$n(money, 'currency')]),
h('sub-child1', { ref: 'sub-child1' })
])
}
Expand All @@ -50,8 +61,8 @@ describe('component translation', () => {
'sub-child2': {
i18n: {
messages: {
en: { who: 'sub-child2' },
ja: { who: 'サブの子2' }
'en-US': { who: 'sub-child2' },
'ja-JP': { who: 'サブの子2' }
}
},
render (h) {
Expand Down Expand Up @@ -84,23 +95,35 @@ describe('component translation', () => {
const root = vm.$refs.who
const child1 = vm.$refs.child1.$refs.who
const child1Fallback = vm.$refs.child1.$refs.fallback
const child1DateTime = vm.$refs.child1.$refs.datetime
const child1Number = vm.$refs.child1.$refs.number
const child2 = vm.$refs.child2.$refs.who
const subChild1 = vm.$refs.child1.$refs['sub-child1'].$refs.who
const subChild2 = vm.$refs.child2.$refs['sub-child2'].$refs.who
assert.equal(root.textContent, 'ルート')
assert.equal(child1.textContent, 'child1')
assert.equal(child1Fallback.textContent, 'フォールバック')
assert.equal(
child1DateTime.textContent,
isWebkit ? '12/20/2012, 03:00' : '12/19/2012, 10:00 PM'
)
assert.equal(child1Number.textContent, '$101.00')
assert.equal(child2.textContent, 'ルート')
assert.equal(subChild1.textContent, 'ルート')
assert.equal(subChild2.textContent, 'サブの子2')

// change locale
i18n.locale = 'en'
vm.$refs.child1.$i18n.locale = 'ja'
i18n.locale = 'en-US'
vm.$refs.child1.$i18n.locale = 'ja-JP'
nextTick(() => {
assert.equal(root.textContent, 'root')
assert.equal(child1.textContent, '子1')
assert.equal(child1Fallback.textContent, 'fallback')
assert.equal(
child1DateTime.textContent,
isWebkit ? '2012/12/20 3:00' : '2012/12/20 12:00'
)
assert.equal(child1Number.textContent, '¥101')
assert.equal(child2.textContent, 'root')
assert.equal(subChild1.textContent, 'root')
assert.equal(subChild2.textContent, 'sub-child2')
Expand Down

0 comments on commit be9e1bd

Please sign in to comment.