1 |
|
2 |
|
3 | import { install, Vue } from './install'
|
4 | import {
|
5 | warn,
|
6 | isNull,
|
7 | parseArgs,
|
8 | isPlainObject,
|
9 | isObject,
|
10 | looseClone,
|
11 | remove,
|
12 | merge
|
13 | } from './util'
|
14 | import BaseFormatter from './format'
|
15 | import I18nPath from './path'
|
16 |
|
17 | import type { PathValue } from './path'
|
18 |
|
19 | const numberFormatKeys = [
|
20 | 'style',
|
21 | 'currency',
|
22 | 'currencyDisplay',
|
23 | 'useGrouping',
|
24 | 'minimumIntegerDigits',
|
25 | 'minimumFractionDigits',
|
26 | 'maximumFractionDigits',
|
27 | 'minimumSignificantDigits',
|
28 | 'maximumSignificantDigits',
|
29 | 'localeMatcher',
|
30 | 'formatMatcher'
|
31 | ]
|
32 | const linkKeyMatcher = /(?:@(?:\.[a-z]+)?:(?:[\w\-_|.]+|\([\w\-_|.]+\)))/g
|
33 | const linkKeyPrefixMatcher = /^@(?:\.([a-z]+))?:/
|
34 | const bracketsMatcher = /[()]/g
|
35 | const formatters = {
|
36 | 'upper': str => str.toLocaleUpperCase(),
|
37 | 'lower': str => str.toLocaleLowerCase()
|
38 | }
|
39 |
|
40 | const defaultFormatter = new BaseFormatter()
|
41 |
|
42 | export default class VueI18n {
|
43 | static install: () => void
|
44 | static version: string
|
45 | static availabilities: IntlAvailability
|
46 |
|
47 | _vm: any
|
48 | _formatter: Formatter
|
49 | _root: any
|
50 | _sync: boolean
|
51 | _fallbackRoot: boolean
|
52 | _missing: ?MissingHandler
|
53 | _exist: Function
|
54 | _watcher: any
|
55 | _i18nWatcher: Function
|
56 | _silentTranslationWarn: boolean
|
57 | _silentFallbackWarn: boolean
|
58 | _dateTimeFormatters: Object
|
59 | _numberFormatters: Object
|
60 | _path: I18nPath
|
61 | _dataListeners: Array<any>
|
62 | _preserveDirectiveContent: boolean
|
63 | pluralizationRules: {
|
64 | [lang: string]: (choice: number, choicesLength: number) => number
|
65 | }
|
66 |
|
67 | constructor (options: I18nOptions = {}) {
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | if (!Vue && typeof window !== 'undefined' && window.Vue) {
|
73 | install(window.Vue)
|
74 | }
|
75 |
|
76 | const locale: Locale = options.locale || 'en-US'
|
77 | const fallbackLocale: Locale = options.fallbackLocale || 'en-US'
|
78 | const messages: LocaleMessages = options.messages || {}
|
79 | const dateTimeFormats = options.dateTimeFormats || {}
|
80 | const numberFormats = options.numberFormats || {}
|
81 |
|
82 | this._vm = null
|
83 | this._formatter = options.formatter || defaultFormatter
|
84 | this._missing = options.missing || null
|
85 | this._root = options.root || null
|
86 | this._sync = options.sync === undefined ? true : !!options.sync
|
87 | this._fallbackRoot = options.fallbackRoot === undefined
|
88 | ? true
|
89 | : !!options.fallbackRoot
|
90 | this._silentTranslationWarn = options.silentTranslationWarn === undefined
|
91 | ? false
|
92 | : !!options.silentTranslationWarn
|
93 | this._silentFallbackWarn = options.silentFallbackWarn === undefined
|
94 | ? false
|
95 | : !!options.silentFallbackWarn
|
96 | this._dateTimeFormatters = {}
|
97 | this._numberFormatters = {}
|
98 | this._path = new I18nPath()
|
99 | this._dataListeners = []
|
100 | this._preserveDirectiveContent = options.preserveDirectiveContent === undefined
|
101 | ? false
|
102 | : !!options.preserveDirectiveContent
|
103 | this.pluralizationRules = options.pluralizationRules || {}
|
104 |
|
105 | this._exist = (message: Object, key: Path): boolean => {
|
106 | if (!message || !key) { return false }
|
107 | if (this._path.getPathValue(message, key)) { return true }
|
108 |
|
109 | if (message[key]) { return true }
|
110 | return false
|
111 | }
|
112 |
|
113 | this._initVM({
|
114 | locale,
|
115 | fallbackLocale,
|
116 | messages,
|
117 | dateTimeFormats,
|
118 | numberFormats
|
119 | })
|
120 | }
|
121 |
|
122 | _initVM (data: {
|
123 | locale: Locale,
|
124 | fallbackLocale: Locale,
|
125 | messages: LocaleMessages,
|
126 | dateTimeFormats: DateTimeFormats,
|
127 | numberFormats: NumberFormats
|
128 | }): void {
|
129 | const silent = Vue.config.silent
|
130 | Vue.config.silent = true
|
131 | this._vm = new Vue({ data })
|
132 | Vue.config.silent = silent
|
133 | }
|
134 |
|
135 | subscribeDataChanging (vm: any): void {
|
136 | this._dataListeners.push(vm)
|
137 | }
|
138 |
|
139 | unsubscribeDataChanging (vm: any): void {
|
140 | remove(this._dataListeners, vm)
|
141 | }
|
142 |
|
143 | watchI18nData (): Function {
|
144 | const self = this
|
145 | return this._vm.$watch('$data', () => {
|
146 | let i = self._dataListeners.length
|
147 | while (i--) {
|
148 | Vue.nextTick(() => {
|
149 | self._dataListeners[i] && self._dataListeners[i].$forceUpdate()
|
150 | })
|
151 | }
|
152 | }, { deep: true })
|
153 | }
|
154 |
|
155 | watchLocale (): ?Function {
|
156 |
|
157 | if (!this._sync || !this._root) { return null }
|
158 | const target: any = this._vm
|
159 | return this._root.$i18n.vm.$watch('locale', (val) => {
|
160 | target.$set(target, 'locale', val)
|
161 | target.$forceUpdate()
|
162 | }, { immediate: true })
|
163 | }
|
164 |
|
165 | get vm (): any { return this._vm }
|
166 |
|
167 | get messages (): LocaleMessages { return looseClone(this._getMessages()) }
|
168 | get dateTimeFormats (): DateTimeFormats { return looseClone(this._getDateTimeFormats()) }
|
169 | get numberFormats (): NumberFormats { return looseClone(this._getNumberFormats()) }
|
170 |
|
171 | get locale (): Locale { return this._vm.locale }
|
172 | set locale (locale: Locale): void {
|
173 | this._vm.$set(this._vm, 'locale', locale)
|
174 | }
|
175 |
|
176 | get fallbackLocale (): Locale { return this._vm.fallbackLocale }
|
177 | set fallbackLocale (locale: Locale): void {
|
178 | this._vm.$set(this._vm, 'fallbackLocale', locale)
|
179 | }
|
180 |
|
181 | get missing (): ?MissingHandler { return this._missing }
|
182 | set missing (handler: MissingHandler): void { this._missing = handler }
|
183 |
|
184 | get formatter (): Formatter { return this._formatter }
|
185 | set formatter (formatter: Formatter): void { this._formatter = formatter }
|
186 |
|
187 | get silentTranslationWarn (): boolean { return this._silentTranslationWarn }
|
188 | set silentTranslationWarn (silent: boolean): void { this._silentTranslationWarn = silent }
|
189 |
|
190 | get silentFallbackWarn (): boolean { return this._silentFallbackWarn }
|
191 | set silentFallbackWarn (silent: boolean): void { this._silentFallbackWarn = silent }
|
192 |
|
193 | get preserveDirectiveContent (): boolean { return this._preserveDirectiveContent }
|
194 | set preserveDirectiveContent (preserve: boolean): void { this._preserveDirectiveContent = preserve }
|
195 |
|
196 | _getMessages (): LocaleMessages { return this._vm.messages }
|
197 | _getDateTimeFormats (): DateTimeFormats { return this._vm.dateTimeFormats }
|
198 | _getNumberFormats (): NumberFormats { return this._vm.numberFormats }
|
199 |
|
200 | _warnDefault (locale: Locale, key: Path, result: ?any, vm: ?any, values: any): ?string {
|
201 | if (!isNull(result)) { return result }
|
202 | if (this._missing) {
|
203 | const missingRet = this._missing.apply(null, [locale, key, vm, values])
|
204 | if (typeof missingRet === 'string') {
|
205 | return missingRet
|
206 | }
|
207 | } else {
|
208 | if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
|
209 | warn(
|
210 | `Cannot translate the value of keypath '${key}'. ` +
|
211 | 'Use the value of keypath as default.'
|
212 | )
|
213 | }
|
214 | }
|
215 | return key
|
216 | }
|
217 |
|
218 | _isFallbackRoot (val: any): boolean {
|
219 | return !val && !isNull(this._root) && this._fallbackRoot
|
220 | }
|
221 |
|
222 | _isSilentFallback (locale: Locale): boolean {
|
223 | return this._silentFallbackWarn && (this._isFallbackRoot() || locale !== this.fallbackLocale)
|
224 | }
|
225 |
|
226 | _interpolate (
|
227 | locale: Locale,
|
228 | message: LocaleMessageObject,
|
229 | key: Path,
|
230 | host: any,
|
231 | interpolateMode: string,
|
232 | values: any,
|
233 | visitedLinkStack: Array<string>
|
234 | ): any {
|
235 | if (!message) { return null }
|
236 |
|
237 | const pathRet: PathValue = this._path.getPathValue(message, key)
|
238 | if (Array.isArray(pathRet) || isPlainObject(pathRet)) { return pathRet }
|
239 |
|
240 | let ret: mixed
|
241 | if (isNull(pathRet)) {
|
242 |
|
243 | if (isPlainObject(message)) {
|
244 | ret = message[key]
|
245 | if (typeof ret !== 'string') {
|
246 | if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn && !this._isSilentFallback(locale)) {
|
247 | warn(`Value of key '${key}' is not a string!`)
|
248 | }
|
249 | return null
|
250 | }
|
251 | } else {
|
252 | return null
|
253 | }
|
254 | } else {
|
255 |
|
256 | if (typeof pathRet === 'string') {
|
257 | ret = pathRet
|
258 | } else {
|
259 | if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn && !this._isSilentFallback(locale)) {
|
260 | warn(`Value of key '${key}' is not a string!`)
|
261 | }
|
262 | return null
|
263 | }
|
264 | }
|
265 |
|
266 |
|
267 | if (ret.indexOf('@:') >= 0 || ret.indexOf('@.') >= 0) {
|
268 | ret = this._link(locale, message, ret, host, 'raw', values, visitedLinkStack)
|
269 | }
|
270 |
|
271 | return this._render(ret, interpolateMode, values, key)
|
272 | }
|
273 |
|
274 | _link (
|
275 | locale: Locale,
|
276 | message: LocaleMessageObject,
|
277 | str: string,
|
278 | host: any,
|
279 | interpolateMode: string,
|
280 | values: any,
|
281 | visitedLinkStack: Array<string>
|
282 | ): any {
|
283 | let ret: string = str
|
284 |
|
285 |
|
286 |
|
287 |
|
288 | const matches: any = ret.match(linkKeyMatcher)
|
289 | for (const idx in matches) {
|
290 |
|
291 |
|
292 | if (!matches.hasOwnProperty(idx)) {
|
293 | continue
|
294 | }
|
295 | const link: string = matches[idx]
|
296 | const linkKeyPrefixMatches: any = link.match(linkKeyPrefixMatcher)
|
297 | const [linkPrefix, formatterName] = linkKeyPrefixMatches
|
298 |
|
299 |
|
300 | const linkPlaceholder: string = link.replace(linkPrefix, '').replace(bracketsMatcher, '')
|
301 |
|
302 | if (visitedLinkStack.includes(linkPlaceholder)) {
|
303 | if (process.env.NODE_ENV !== 'production') {
|
304 | warn(`Circular reference found. "${link}" is already visited in the chain of ${visitedLinkStack.reverse().join(' <- ')}`)
|
305 | }
|
306 | return ret
|
307 | }
|
308 | visitedLinkStack.push(linkPlaceholder)
|
309 |
|
310 |
|
311 | let translated: any = this._interpolate(
|
312 | locale, message, linkPlaceholder, host,
|
313 | interpolateMode === 'raw' ? 'string' : interpolateMode,
|
314 | interpolateMode === 'raw' ? undefined : values,
|
315 | visitedLinkStack
|
316 | )
|
317 |
|
318 | if (this._isFallbackRoot(translated)) {
|
319 | if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
|
320 | warn(`Fall back to translate the link placeholder '${linkPlaceholder}' with root locale.`)
|
321 | }
|
322 |
|
323 | if (!this._root) { throw Error('unexpected error') }
|
324 | const root: any = this._root.$i18n
|
325 | translated = root._translate(
|
326 | root._getMessages(), root.locale, root.fallbackLocale,
|
327 | linkPlaceholder, host, interpolateMode, values
|
328 | )
|
329 | }
|
330 | translated = this._warnDefault(
|
331 | locale, linkPlaceholder, translated, host,
|
332 | Array.isArray(values) ? values : [values]
|
333 | )
|
334 | if (formatters.hasOwnProperty(formatterName)) {
|
335 | translated = formatters[formatterName](translated)
|
336 | }
|
337 |
|
338 | visitedLinkStack.pop()
|
339 |
|
340 |
|
341 | ret = !translated ? ret : ret.replace(link, translated)
|
342 | }
|
343 |
|
344 | return ret
|
345 | }
|
346 |
|
347 | _render (message: string, interpolateMode: string, values: any, path: string): any {
|
348 | let ret = this._formatter.interpolate(message, values, path)
|
349 |
|
350 |
|
351 | if (!ret) {
|
352 | ret = defaultFormatter.interpolate(message, values, path)
|
353 | }
|
354 |
|
355 |
|
356 |
|
357 | return interpolateMode === 'string' ? ret.join('') : ret
|
358 | }
|
359 |
|
360 | _translate (
|
361 | messages: LocaleMessages,
|
362 | locale: Locale,
|
363 | fallback: Locale,
|
364 | key: Path,
|
365 | host: any,
|
366 | interpolateMode: string,
|
367 | args: any
|
368 | ): any {
|
369 | let res: any =
|
370 | this._interpolate(locale, messages[locale], key, host, interpolateMode, args, [key])
|
371 | if (!isNull(res)) { return res }
|
372 |
|
373 | res = this._interpolate(fallback, messages[fallback], key, host, interpolateMode, args, [key])
|
374 | if (!isNull(res)) {
|
375 | if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn && !this._silentFallbackWarn) {
|
376 | warn(`Fall back to translate the keypath '${key}' with '${fallback}' locale.`)
|
377 | }
|
378 | return res
|
379 | } else {
|
380 | return null
|
381 | }
|
382 | }
|
383 |
|
384 | _t (key: Path, _locale: Locale, messages: LocaleMessages, host: any, ...values: any): any {
|
385 | if (!key) { return '' }
|
386 |
|
387 | const parsedArgs = parseArgs(...values)
|
388 | const locale: Locale = parsedArgs.locale || _locale
|
389 |
|
390 | const ret: any = this._translate(
|
391 | messages, locale, this.fallbackLocale, key,
|
392 | host, 'string', parsedArgs.params
|
393 | )
|
394 | if (this._isFallbackRoot(ret)) {
|
395 | if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn && !this._silentFallbackWarn) {
|
396 | warn(`Fall back to translate the keypath '${key}' with root locale.`)
|
397 | }
|
398 |
|
399 | if (!this._root) { throw Error('unexpected error') }
|
400 | return this._root.$t(key, ...values)
|
401 | } else {
|
402 | return this._warnDefault(locale, key, ret, host, values)
|
403 | }
|
404 | }
|
405 |
|
406 | t (key: Path, ...values: any): TranslateResult {
|
407 | return this._t(key, this.locale, this._getMessages(), null, ...values)
|
408 | }
|
409 |
|
410 | _i (key: Path, locale: Locale, messages: LocaleMessages, host: any, values: Object): any {
|
411 | const ret: any =
|
412 | this._translate(messages, locale, this.fallbackLocale, key, host, 'raw', values)
|
413 | if (this._isFallbackRoot(ret)) {
|
414 | if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
|
415 | warn(`Fall back to interpolate the keypath '${key}' with root locale.`)
|
416 | }
|
417 | if (!this._root) { throw Error('unexpected error') }
|
418 | return this._root.$i18n.i(key, locale, values)
|
419 | } else {
|
420 | return this._warnDefault(locale, key, ret, host, [values])
|
421 | }
|
422 | }
|
423 |
|
424 | i (key: Path, locale: Locale, values: Object): TranslateResult {
|
425 |
|
426 | if (!key) { return '' }
|
427 |
|
428 | if (typeof locale !== 'string') {
|
429 | locale = this.locale
|
430 | }
|
431 |
|
432 | return this._i(key, locale, this._getMessages(), null, values)
|
433 | }
|
434 |
|
435 | _tc (
|
436 | key: Path,
|
437 | _locale: Locale,
|
438 | messages: LocaleMessages,
|
439 | host: any,
|
440 | choice?: number,
|
441 | ...values: any
|
442 | ): any {
|
443 | if (!key) { return '' }
|
444 | if (choice === undefined) {
|
445 | choice = 1
|
446 | }
|
447 |
|
448 | const predefined = { 'count': choice, 'n': choice }
|
449 | const parsedArgs = parseArgs(...values)
|
450 | parsedArgs.params = Object.assign(predefined, parsedArgs.params)
|
451 | values = parsedArgs.locale === null ? [parsedArgs.params] : [parsedArgs.locale, parsedArgs.params]
|
452 | return this.fetchChoice(this._t(key, _locale, messages, host, ...values), choice)
|
453 | }
|
454 |
|
455 | fetchChoice (message: string, choice: number): ?string {
|
456 |
|
457 | if (!message && typeof message !== 'string') { return null }
|
458 | const choices: Array<string> = message.split('|')
|
459 |
|
460 | choice = this.getChoiceIndex(choice, choices.length)
|
461 | if (!choices[choice]) { return message }
|
462 | return choices[choice].trim()
|
463 | }
|
464 |
|
465 | |
466 |
|
467 |
|
468 |
|
469 |
|
470 | getChoiceIndex (choice: number, choicesLength: number): number {
|
471 |
|
472 | const defaultImpl = (_choice: number, _choicesLength: number) => {
|
473 | _choice = Math.abs(_choice)
|
474 |
|
475 | if (_choicesLength === 2) {
|
476 | return _choice
|
477 | ? _choice > 1
|
478 | ? 1
|
479 | : 0
|
480 | : 1
|
481 | }
|
482 |
|
483 | return _choice ? Math.min(_choice, 2) : 0
|
484 | }
|
485 |
|
486 | if (this.locale in this.pluralizationRules) {
|
487 | return this.pluralizationRules[this.locale].apply(this, [choice, choicesLength])
|
488 | } else {
|
489 | return defaultImpl(choice, choicesLength)
|
490 | }
|
491 | }
|
492 |
|
493 | tc (key: Path, choice?: number, ...values: any): TranslateResult {
|
494 | return this._tc(key, this.locale, this._getMessages(), null, choice, ...values)
|
495 | }
|
496 |
|
497 | _te (key: Path, locale: Locale, messages: LocaleMessages, ...args: any): boolean {
|
498 | const _locale: Locale = parseArgs(...args).locale || locale
|
499 | return this._exist(messages[_locale], key)
|
500 | }
|
501 |
|
502 | te (key: Path, locale?: Locale): boolean {
|
503 | return this._te(key, this.locale, this._getMessages(), locale)
|
504 | }
|
505 |
|
506 | getLocaleMessage (locale: Locale): LocaleMessageObject {
|
507 | return looseClone(this._vm.messages[locale] || {})
|
508 | }
|
509 |
|
510 | setLocaleMessage (locale: Locale, message: LocaleMessageObject): void {
|
511 | this._vm.$set(this._vm.messages, locale, message)
|
512 | }
|
513 |
|
514 | mergeLocaleMessage (locale: Locale, message: LocaleMessageObject): void {
|
515 | this._vm.$set(this._vm.messages, locale, merge(this._vm.messages[locale] || {}, message))
|
516 | }
|
517 |
|
518 | getDateTimeFormat (locale: Locale): DateTimeFormat {
|
519 | return looseClone(this._vm.dateTimeFormats[locale] || {})
|
520 | }
|
521 |
|
522 | setDateTimeFormat (locale: Locale, format: DateTimeFormat): void {
|
523 | this._vm.$set(this._vm.dateTimeFormats, locale, format)
|
524 | }
|
525 |
|
526 | mergeDateTimeFormat (locale: Locale, format: DateTimeFormat): void {
|
527 | this._vm.$set(this._vm.dateTimeFormats, locale, merge(this._vm.dateTimeFormats[locale] || {}, format))
|
528 | }
|
529 |
|
530 | _localizeDateTime (
|
531 | value: number | Date,
|
532 | locale: Locale,
|
533 | fallback: Locale,
|
534 | dateTimeFormats: DateTimeFormats,
|
535 | key: string
|
536 | ): ?DateTimeFormatResult {
|
537 | let _locale: Locale = locale
|
538 | let formats: DateTimeFormat = dateTimeFormats[_locale]
|
539 |
|
540 |
|
541 | if (isNull(formats) || isNull(formats[key])) {
|
542 | if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
|
543 | warn(`Fall back to '${fallback}' datetime formats from '${locale} datetime formats.`)
|
544 | }
|
545 | _locale = fallback
|
546 | formats = dateTimeFormats[_locale]
|
547 | }
|
548 |
|
549 | if (isNull(formats) || isNull(formats[key])) {
|
550 | return null
|
551 | } else {
|
552 | const format: ?DateTimeFormatOptions = formats[key]
|
553 | const id = `${_locale}__${key}`
|
554 | let formatter = this._dateTimeFormatters[id]
|
555 | if (!formatter) {
|
556 | formatter = this._dateTimeFormatters[id] = new Intl.DateTimeFormat(_locale, format)
|
557 | }
|
558 | return formatter.format(value)
|
559 | }
|
560 | }
|
561 |
|
562 | _d (value: number | Date, locale: Locale, key: ?string): DateTimeFormatResult {
|
563 |
|
564 | if (process.env.NODE_ENV !== 'production' && !VueI18n.availabilities.dateTimeFormat) {
|
565 | warn('Cannot format a Date value due to not supported Intl.DateTimeFormat.')
|
566 | return ''
|
567 | }
|
568 |
|
569 | if (!key) {
|
570 | return new Intl.DateTimeFormat(locale).format(value)
|
571 | }
|
572 |
|
573 | const ret: ?DateTimeFormatResult =
|
574 | this._localizeDateTime(value, locale, this.fallbackLocale, this._getDateTimeFormats(), key)
|
575 | if (this._isFallbackRoot(ret)) {
|
576 | if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
|
577 | warn(`Fall back to datetime localization of root: key '${key}' .`)
|
578 | }
|
579 |
|
580 | if (!this._root) { throw Error('unexpected error') }
|
581 | return this._root.$i18n.d(value, key, locale)
|
582 | } else {
|
583 | return ret || ''
|
584 | }
|
585 | }
|
586 |
|
587 | d (value: number | Date, ...args: any): DateTimeFormatResult {
|
588 | let locale: Locale = this.locale
|
589 | let key: ?string = null
|
590 |
|
591 | if (args.length === 1) {
|
592 | if (typeof args[0] === 'string') {
|
593 | key = args[0]
|
594 | } else if (isObject(args[0])) {
|
595 | if (args[0].locale) {
|
596 | locale = args[0].locale
|
597 | }
|
598 | if (args[0].key) {
|
599 | key = args[0].key
|
600 | }
|
601 | }
|
602 | } else if (args.length === 2) {
|
603 | if (typeof args[0] === 'string') {
|
604 | key = args[0]
|
605 | }
|
606 | if (typeof args[1] === 'string') {
|
607 | locale = args[1]
|
608 | }
|
609 | }
|
610 |
|
611 | return this._d(value, locale, key)
|
612 | }
|
613 |
|
614 | getNumberFormat (locale: Locale): NumberFormat {
|
615 | return looseClone(this._vm.numberFormats[locale] || {})
|
616 | }
|
617 |
|
618 | setNumberFormat (locale: Locale, format: NumberFormat): void {
|
619 | this._vm.$set(this._vm.numberFormats, locale, format)
|
620 | }
|
621 |
|
622 | mergeNumberFormat (locale: Locale, format: NumberFormat): void {
|
623 | this._vm.$set(this._vm.numberFormats, locale, merge(this._vm.numberFormats[locale] || {}, format))
|
624 | }
|
625 |
|
626 | _localizeNumber (
|
627 | value: number,
|
628 | locale: Locale,
|
629 | fallback: Locale,
|
630 | numberFormats: NumberFormats,
|
631 | key: string,
|
632 | options: ?NumberFormatOptions
|
633 | ): ?NumberFormatResult {
|
634 | let _locale: Locale = locale
|
635 | let formats: NumberFormat = numberFormats[_locale]
|
636 |
|
637 |
|
638 | if (isNull(formats) || isNull(formats[key])) {
|
639 | if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
|
640 | warn(`Fall back to '${fallback}' number formats from '${locale} number formats.`)
|
641 | }
|
642 | _locale = fallback
|
643 | formats = numberFormats[_locale]
|
644 | }
|
645 |
|
646 | if (isNull(formats) || isNull(formats[key])) {
|
647 | return null
|
648 | } else {
|
649 | const format: ?NumberFormatOptions = formats[key]
|
650 |
|
651 | let formatter
|
652 | if (options) {
|
653 |
|
654 | formatter = new Intl.NumberFormat(_locale, Object.assign({}, format, options))
|
655 | } else {
|
656 | const id = `${_locale}__${key}`
|
657 | formatter = this._numberFormatters[id]
|
658 | if (!formatter) {
|
659 | formatter = this._numberFormatters[id] = new Intl.NumberFormat(_locale, format)
|
660 | }
|
661 | }
|
662 | return formatter.format(value)
|
663 | }
|
664 | }
|
665 |
|
666 | _n (value: number, locale: Locale, key: ?string, options: ?NumberFormatOptions): NumberFormatResult {
|
667 |
|
668 | if (!VueI18n.availabilities.numberFormat) {
|
669 | if (process.env.NODE_ENV !== 'production') {
|
670 | warn('Cannot format a Number value due to not supported Intl.NumberFormat.')
|
671 | }
|
672 | return ''
|
673 | }
|
674 |
|
675 | if (!key) {
|
676 | const nf = !options ? new Intl.NumberFormat(locale) : new Intl.NumberFormat(locale, options)
|
677 | return nf.format(value)
|
678 | }
|
679 |
|
680 | const ret: ?NumberFormatResult =
|
681 | this._localizeNumber(value, locale, this.fallbackLocale, this._getNumberFormats(), key, options)
|
682 | if (this._isFallbackRoot(ret)) {
|
683 | if (process.env.NODE_ENV !== 'production' && !this._silentTranslationWarn) {
|
684 | warn(`Fall back to number localization of root: key '${key}' .`)
|
685 | }
|
686 |
|
687 | if (!this._root) { throw Error('unexpected error') }
|
688 | return this._root.$i18n.n(value, Object.assign({}, { key, locale }, options))
|
689 | } else {
|
690 | return ret || ''
|
691 | }
|
692 | }
|
693 |
|
694 | n (value: number, ...args: any): NumberFormatResult {
|
695 | let locale: Locale = this.locale
|
696 | let key: ?string = null
|
697 | let options: ?NumberFormatOptions = null
|
698 |
|
699 | if (args.length === 1) {
|
700 | if (typeof args[0] === 'string') {
|
701 | key = args[0]
|
702 | } else if (isObject(args[0])) {
|
703 | if (args[0].locale) {
|
704 | locale = args[0].locale
|
705 | }
|
706 | if (args[0].key) {
|
707 | key = args[0].key
|
708 | }
|
709 |
|
710 |
|
711 | options = Object.keys(args[0]).reduce((acc, key) => {
|
712 | if (numberFormatKeys.includes(key)) {
|
713 | return Object.assign({}, acc, { [key]: args[0][key] })
|
714 | }
|
715 | return acc
|
716 | }, null)
|
717 | }
|
718 | } else if (args.length === 2) {
|
719 | if (typeof args[0] === 'string') {
|
720 | key = args[0]
|
721 | }
|
722 | if (typeof args[1] === 'string') {
|
723 | locale = args[1]
|
724 | }
|
725 | }
|
726 |
|
727 | return this._n(value, locale, key, options)
|
728 | }
|
729 | }
|
730 |
|
731 | let availabilities: IntlAvailability
|
732 |
|
733 | Object.defineProperty(VueI18n, 'availabilities', {
|
734 | get () {
|
735 | if (!availabilities) {
|
736 | const intlDefined = typeof Intl !== 'undefined'
|
737 | availabilities = {
|
738 | dateTimeFormat: intlDefined && typeof Intl.DateTimeFormat !== 'undefined',
|
739 | numberFormat: intlDefined && typeof Intl.NumberFormat !== 'undefined'
|
740 | }
|
741 | }
|
742 |
|
743 | return availabilities
|
744 | }
|
745 | })
|
746 |
|
747 | VueI18n.install = install
|
748 | VueI18n.version = '__VERSION__'
|