Skip to content
This repository was archived by the owner on Dec 31, 2024. It is now read-only.

Commit 87ee7b3

Browse files
committed
⭐ new(number): add number localization
1 parent bb95130 commit 87ee7b3

6 files changed

Lines changed: 209 additions & 4 deletions

File tree

src/extend.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,8 @@ export default function extend (Vue: any): void {
2020
const i18n = this.$i18n
2121
return i18n.d(value, ...args)
2222
}
23+
24+
Vue.prototype.$n = function (value: number, ...args: any): NumberFormatResult {
25+
return this.$i18n.n(value, ...args)
26+
}
2327
}

src/index.js

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
isPlainObject,
1010
isObject,
1111
looseClone,
12-
canUseDateTimeFormat
12+
canUseDateTimeFormat,
13+
canUseNumberFormat
1314
} from './util'
1415
import BaseFormatter from './format'
1516
import getPathValue from './path'
@@ -32,12 +33,14 @@ export default class VueI18n {
3233
_i18nWatcher: Function
3334
_silentTranslationWarn: boolean
3435
_dateTimeFormatters: Object
36+
_numberFormatters: Object
3537

3638
constructor (options: I18nOptions = {}) {
3739
const locale: Locale = options.locale || 'en-US'
3840
const fallbackLocale: Locale = options.fallbackLocale || 'en-US'
3941
const messages: LocaleMessages = options.messages || {}
4042
const dateTimeFormats = options.dateTimeFormats || {}
43+
const numberFormats = options.numberFormats || {}
4144
this._vm = null
4245
this._formatter = options.formatter || new BaseFormatter()
4346
this._missing = options.missing || null
@@ -50,20 +53,28 @@ export default class VueI18n {
5053
? false
5154
: !!options.silentTranslationWarn
5255
this._dateTimeFormatters = {}
56+
this._numberFormatters = {}
5357

5458
this._exist = (message: Object, key: Path): boolean => {
5559
if (!message || !key) { return false }
5660
return !isNull(getPathValue(message, key))
5761
}
5862

59-
this._initVM({ locale, fallbackLocale, messages, dateTimeFormats })
63+
this._initVM({
64+
locale,
65+
fallbackLocale,
66+
messages,
67+
dateTimeFormats,
68+
numberFormats
69+
})
6070
}
6171

6272
_initVM (data: {
6373
locale: Locale,
6474
fallbackLocale: Locale,
6575
messages: LocaleMessages,
66-
dateTimeFormats: DateTimeFormats
76+
dateTimeFormats: DateTimeFormats,
77+
numberFormats: NumberFormats
6778
}): void {
6879
const silent = Vue.config.silent
6980
Vue.config.silent = true
@@ -109,6 +120,7 @@ export default class VueI18n {
109120

110121
get messages (): LocaleMessages { return looseClone(this._vm.messages) }
111122
get dateTimeFormats (): DateTimeFormats { return looseClone(this._vm.dateTimeFormats) }
123+
get numberFormats (): NumberFormats { return looseClone(this._vm.numberFormats) }
112124

113125
get locale (): Locale { return this._vm.locale }
114126
set locale (locale: Locale): void {
@@ -342,10 +354,80 @@ export default class VueI18n {
342354

343355
return this._d(value, locale, key)
344356
}
357+
358+
getNumberFormat (locale: Locale): NumberFormat {
359+
return looseClone(this._vm.numberFormats[locale])
360+
}
361+
362+
setNumberFormat (locale: Locale, format: NumberFormat): void {
363+
this._vm.numberFormats[locale] = format
364+
}
365+
366+
mergeNumberFormat (locale: Locale, format: NumberFormat): void {
367+
this._vm.numberFormats[locale] = Vue.util.extend(this.getNumberFormat(locale), format)
368+
}
369+
370+
_n (value: number, _locale: Locale, key: ?string): NumberFormatResult {
371+
if (process.env.NODE_ENV !== 'production' && !VueI18n.availabilities.numberFormat) {
372+
warn('Cannot format a Date value due to not support Intl.NumberFormat.')
373+
return ''
374+
}
375+
376+
let ret = ''
377+
const numberFormats = this.numberFormats
378+
if (key) {
379+
let locale: Locale = _locale
380+
if (isNull(numberFormats[_locale][key])) {
381+
if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
382+
warn(`Fall back to the numberFormat of key '${key}' with '${this.fallbackLocale}' locale.`)
383+
}
384+
locale = this.fallbackLocale
385+
}
386+
const id = `${locale}__${key}`
387+
let formatter = this._numberFormatters[id]
388+
const format = numberFormats[locale][key]
389+
if (!formatter) {
390+
formatter = this._numberFormatters[id] = Intl.NumberFormat(locale, format)
391+
}
392+
ret = formatter.format(value)
393+
} else {
394+
ret = Intl.NumberFormat(_locale).format(value)
395+
}
396+
397+
return ret
398+
}
399+
400+
n (value: number, ...args: any): NumberFormatResult {
401+
let locale: Locale = this.locale
402+
let key: ?string = null
403+
404+
if (args.length === 1) {
405+
if (typeof args[0] === 'string') {
406+
key = args[0]
407+
} else if (isObject(args[0])) {
408+
if (args[0].locale) {
409+
locale = args[0].locale
410+
}
411+
if (args[0].key) {
412+
key = args[0].key
413+
}
414+
}
415+
} else if (args.length === 2) {
416+
if (typeof args[0] === 'string') {
417+
key = args[0]
418+
}
419+
if (typeof args[1] === 'string') {
420+
locale = args[1]
421+
}
422+
}
423+
424+
return this._n(value, locale, key)
425+
}
345426
}
346427

347428
VueI18n.availabilities = {
348-
dateTimeFormat: canUseDateTimeFormat
429+
dateTimeFormat: canUseDateTimeFormat,
430+
numberFormat: canUseNumberFormat
349431
}
350432
VueI18n.install = install
351433
VueI18n.version = '__VERSION__'

src/util.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,6 @@ export function looseClone (obj: Object): Object {
9898

9999
export const canUseDateTimeFormat: boolean =
100100
typeof Intl !== 'undefined' && typeof Intl.DateTimeFormat !== 'undefined'
101+
102+
export const canUseNumberFormat: boolean =
103+
typeof Intl !== 'undefined' && typeof Intl.NumberFormat !== 'undefined'

test/unit/basic.test.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import messages from './fixture/index'
22
import dateTimeFormats from './fixture/datetime'
3+
import numberFormats from './fixture/number'
34

45
describe('basic', () => {
56
let i18n
@@ -627,4 +628,48 @@ describe('basic', () => {
627628
})
628629
})
629630
})
631+
632+
desc('i18n#n', () => {
633+
let money
634+
beforeEach(() => {
635+
i18n = new VueI18n({
636+
locale: 'en-US',
637+
fallbackLocale: 'ja-JP',
638+
numberFormats
639+
})
640+
money = 10100
641+
})
642+
643+
describe('arguments nothing', () => {
644+
it('should be formatted', () => {
645+
assert.equal(i18n.n(money), '10,100')
646+
})
647+
})
648+
649+
describe('key argument', () => {
650+
it('should be formatted', () => {
651+
assert.equal(i18n.n(money, 'currency'), '$10,100.00')
652+
})
653+
})
654+
655+
describe('locale argument', () => {
656+
describe('with second argument', () => {
657+
it('should be formatted', () => {
658+
assert.equal(i18n.n(money, 'currency', 'ja-JP'), '¥10,100')
659+
})
660+
})
661+
662+
describe('with object argument', () => {
663+
it('should be formatted', () => {
664+
assert.equal(i18n.n(money, { key: 'currency', locale: 'ja-JP' }), '¥10,100')
665+
})
666+
})
667+
})
668+
669+
describe('fallback', () => {
670+
it('should be formatted', () => {
671+
assert.equal(i18n.n(0.9, 'percent'), '90%')
672+
})
673+
})
674+
})
630675
})

test/unit/fixture/number.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export default {
2+
'en-US': {
3+
currency: {
4+
style: 'currency', currency: 'USD', currencyDisplay: 'symbol'
5+
},
6+
decimal: {
7+
style: 'decimal', useGrouping: false
8+
}
9+
},
10+
'ja-JP': {
11+
currency: {
12+
style: 'currency', currency: 'JPY', currencyDisplay: 'symbol'
13+
},
14+
numeric: {
15+
style: 'decimal', useGrouping: false
16+
},
17+
percent: {
18+
style: 'percent', useGrouping: false
19+
}
20+
}
21+
}

test/unit/number.test.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import numberFormats from './fixture/number'
2+
3+
const desc = VueI18n.availabilities.numberFormat ? describe : describe.skip
4+
desc('number format', () => {
5+
describe('getNumberFormat / setNumberFormat', () => {
6+
it('should be worked', done => {
7+
const i18n = new VueI18n({
8+
locale: 'en-US',
9+
numberFormats
10+
})
11+
const el = document.createElement('div')
12+
document.body.appendChild(el)
13+
14+
const money = 101
15+
const vm = new Vue({
16+
i18n,
17+
render (h) {
18+
return h('p', { ref: 'text' }, [this.$n(money, 'currency')])
19+
}
20+
}).$mount(el)
21+
22+
const { text } = vm.$refs
23+
const zhFormat = {
24+
currency: {
25+
style: 'currency', currency: 'CNY', currencyDisplay: 'name'
26+
}
27+
}
28+
nextTick(() => {
29+
assert.equal(text.textContent, '$101.00')
30+
i18n.setNumberFormat('zh-CN', zhFormat)
31+
assert.deepEqual(i18n.getNumberFormat('zh-CN'), zhFormat)
32+
i18n.locale = 'zh-CN'
33+
}).then(() => {
34+
assert.equal(text.textContent, '101.00人民币')
35+
}).then(done)
36+
})
37+
})
38+
39+
describe('mergeNumberFormat', () => {
40+
it('should be merged', () => {
41+
const i18n = new VueI18n({
42+
locale: 'ja-JP',
43+
numberFormats
44+
})
45+
const percent = { style: 'percent' }
46+
i18n.mergeNumberFormat('en-US', { percent })
47+
assert.deepEqual(percent, i18n.getNumberFormat('en-US').percent)
48+
})
49+
})
50+
})

0 commit comments

Comments
 (0)