UNPKG

48.8 kBJavaScriptView Raw
1/* */
2
3/**
4 * constants
5 */
6
7const numberFormatKeys = [
8 'style',
9 'currency',
10 'currencyDisplay',
11 'useGrouping',
12 'minimumIntegerDigits',
13 'minimumFractionDigits',
14 'maximumFractionDigits',
15 'minimumSignificantDigits',
16 'maximumSignificantDigits',
17 'localeMatcher',
18 'formatMatcher'
19];
20
21/**
22 * utilities
23 */
24
25function warn (msg, err) {
26 if (typeof console !== 'undefined') {
27 console.warn('[vue-i18n] ' + msg);
28 /* istanbul ignore if */
29 if (err) {
30 console.warn(err.stack);
31 }
32 }
33}
34
35function error (msg, err) {
36 if (typeof console !== 'undefined') {
37 console.error('[vue-i18n] ' + msg);
38 /* istanbul ignore if */
39 if (err) {
40 console.error(err.stack);
41 }
42 }
43}
44
45function isObject (obj) {
46 return obj !== null && typeof obj === 'object'
47}
48
49const toString = Object.prototype.toString;
50const OBJECT_STRING = '[object Object]';
51function isPlainObject (obj) {
52 return toString.call(obj) === OBJECT_STRING
53}
54
55function isNull (val) {
56 return val === null || val === undefined
57}
58
59function parseArgs (...args) {
60 let locale = null;
61 let params = null;
62 if (args.length === 1) {
63 if (isObject(args[0]) || Array.isArray(args[0])) {
64 params = args[0];
65 } else if (typeof args[0] === 'string') {
66 locale = args[0];
67 }
68 } else if (args.length === 2) {
69 if (typeof args[0] === 'string') {
70 locale = args[0];
71 }
72 /* istanbul ignore if */
73 if (isObject(args[1]) || Array.isArray(args[1])) {
74 params = args[1];
75 }
76 }
77
78 return { locale, params }
79}
80
81function looseClone (obj) {
82 return JSON.parse(JSON.stringify(obj))
83}
84
85function remove (arr, item) {
86 if (arr.length) {
87 const index = arr.indexOf(item);
88 if (index > -1) {
89 return arr.splice(index, 1)
90 }
91 }
92}
93
94const hasOwnProperty = Object.prototype.hasOwnProperty;
95function hasOwn (obj, key) {
96 return hasOwnProperty.call(obj, key)
97}
98
99function merge (target) {
100 const output = Object(target);
101 for (let i = 1; i < arguments.length; i++) {
102 const source = arguments[i];
103 if (source !== undefined && source !== null) {
104 let key;
105 for (key in source) {
106 if (hasOwn(source, key)) {
107 if (isObject(source[key])) {
108 output[key] = merge(output[key], source[key]);
109 } else {
110 output[key] = source[key];
111 }
112 }
113 }
114 }
115 }
116 return output
117}
118
119function looseEqual (a, b) {
120 if (a === b) { return true }
121 const isObjectA = isObject(a);
122 const isObjectB = isObject(b);
123 if (isObjectA && isObjectB) {
124 try {
125 const isArrayA = Array.isArray(a);
126 const isArrayB = Array.isArray(b);
127 if (isArrayA && isArrayB) {
128 return a.length === b.length && a.every((e, i) => {
129 return looseEqual(e, b[i])
130 })
131 } else if (!isArrayA && !isArrayB) {
132 const keysA = Object.keys(a);
133 const keysB = Object.keys(b);
134 return keysA.length === keysB.length && keysA.every((key) => {
135 return looseEqual(a[key], b[key])
136 })
137 } else {
138 /* istanbul ignore next */
139 return false
140 }
141 } catch (e) {
142 /* istanbul ignore next */
143 return false
144 }
145 } else if (!isObjectA && !isObjectB) {
146 return String(a) === String(b)
147 } else {
148 return false
149 }
150}
151
152/* */
153
154function extend (Vue) {
155 if (!Vue.prototype.hasOwnProperty('$i18n')) {
156 // $FlowFixMe
157 Object.defineProperty(Vue.prototype, '$i18n', {
158 get () { return this._i18n }
159 });
160 }
161
162 Vue.prototype.$t = function (key, ...values) {
163 const i18n = this.$i18n;
164 return i18n._t(key, i18n.locale, i18n._getMessages(), this, ...values)
165 };
166
167 Vue.prototype.$tc = function (key, choice, ...values) {
168 const i18n = this.$i18n;
169 return i18n._tc(key, i18n.locale, i18n._getMessages(), this, choice, ...values)
170 };
171
172 Vue.prototype.$te = function (key, locale) {
173 const i18n = this.$i18n;
174 return i18n._te(key, i18n.locale, i18n._getMessages(), locale)
175 };
176
177 Vue.prototype.$d = function (value, ...args) {
178 return this.$i18n.d(value, ...args)
179 };
180
181 Vue.prototype.$n = function (value, ...args) {
182 return this.$i18n.n(value, ...args)
183 };
184}
185
186/* */
187
188var mixin = {
189 beforeCreate () {
190 const options = this.$options;
191 options.i18n = options.i18n || (options.__i18n ? {} : null);
192
193 if (options.i18n) {
194 if (options.i18n instanceof VueI18n) {
195 // init locale messages via custom blocks
196 if (options.__i18n) {
197 try {
198 let localeMessages = {};
199 options.__i18n.forEach(resource => {
200 localeMessages = merge(localeMessages, JSON.parse(resource));
201 });
202 Object.keys(localeMessages).forEach((locale) => {
203 options.i18n.mergeLocaleMessage(locale, localeMessages[locale]);
204 });
205 } catch (e) {
206 {
207 error(`Cannot parse locale messages via custom blocks.`, e);
208 }
209 }
210 }
211 this._i18n = options.i18n;
212 this._i18nWatcher = this._i18n.watchI18nData();
213 } else if (isPlainObject(options.i18n)) {
214 // component local i18n
215 if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) {
216 options.i18n.root = this.$root;
217 options.i18n.formatter = this.$root.$i18n.formatter;
218 options.i18n.fallbackLocale = this.$root.$i18n.fallbackLocale;
219 options.i18n.formatFallbackMessages = this.$root.$i18n.formatFallbackMessages;
220 options.i18n.silentTranslationWarn = this.$root.$i18n.silentTranslationWarn;
221 options.i18n.silentFallbackWarn = this.$root.$i18n.silentFallbackWarn;
222 options.i18n.pluralizationRules = this.$root.$i18n.pluralizationRules;
223 options.i18n.preserveDirectiveContent = this.$root.$i18n.preserveDirectiveContent;
224 }
225
226 // init locale messages via custom blocks
227 if (options.__i18n) {
228 try {
229 let localeMessages = {};
230 options.__i18n.forEach(resource => {
231 localeMessages = merge(localeMessages, JSON.parse(resource));
232 });
233 options.i18n.messages = localeMessages;
234 } catch (e) {
235 {
236 warn(`Cannot parse locale messages via custom blocks.`, e);
237 }
238 }
239 }
240
241 const { sharedMessages } = options.i18n;
242 if (sharedMessages && isPlainObject(sharedMessages)) {
243 options.i18n.messages = merge(options.i18n.messages, sharedMessages);
244 }
245
246 this._i18n = new VueI18n(options.i18n);
247 this._i18nWatcher = this._i18n.watchI18nData();
248
249 if (options.i18n.sync === undefined || !!options.i18n.sync) {
250 this._localeWatcher = this.$i18n.watchLocale();
251 }
252 } else {
253 {
254 warn(`Cannot be interpreted 'i18n' option.`);
255 }
256 }
257 } else if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) {
258 // root i18n
259 this._i18n = this.$root.$i18n;
260 } else if (options.parent && options.parent.$i18n && options.parent.$i18n instanceof VueI18n) {
261 // parent i18n
262 this._i18n = options.parent.$i18n;
263 }
264 },
265
266 beforeMount () {
267 const options = this.$options;
268 options.i18n = options.i18n || (options.__i18n ? {} : null);
269
270 if (options.i18n) {
271 if (options.i18n instanceof VueI18n) {
272 // init locale messages via custom blocks
273 this._i18n.subscribeDataChanging(this);
274 this._subscribing = true;
275 } else if (isPlainObject(options.i18n)) {
276 this._i18n.subscribeDataChanging(this);
277 this._subscribing = true;
278 } else {
279 {
280 warn(`Cannot be interpreted 'i18n' option.`);
281 }
282 }
283 } else if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) {
284 this._i18n.subscribeDataChanging(this);
285 this._subscribing = true;
286 } else if (options.parent && options.parent.$i18n && options.parent.$i18n instanceof VueI18n) {
287 this._i18n.subscribeDataChanging(this);
288 this._subscribing = true;
289 }
290 },
291
292 beforeDestroy () {
293 if (!this._i18n) { return }
294
295 const self = this;
296 this.$nextTick(() => {
297 if (self._subscribing) {
298 self._i18n.unsubscribeDataChanging(self);
299 delete self._subscribing;
300 }
301
302 if (self._i18nWatcher) {
303 self._i18nWatcher();
304 self._i18n.destroyVM();
305 delete self._i18nWatcher;
306 }
307
308 if (self._localeWatcher) {
309 self._localeWatcher();
310 delete self._localeWatcher;
311 }
312
313 self._i18n = null;
314 });
315 }
316};
317
318/* */
319
320var interpolationComponent = {
321 name: 'i18n',
322 functional: true,
323 props: {
324 tag: {
325 type: String
326 },
327 path: {
328 type: String,
329 required: true
330 },
331 locale: {
332 type: String
333 },
334 places: {
335 type: [Array, Object]
336 }
337 },
338 render (h, { data, parent, props, slots }) {
339 const { $i18n } = parent;
340 if (!$i18n) {
341 {
342 warn('Cannot find VueI18n instance!');
343 }
344 return
345 }
346
347 const { path, locale, places } = props;
348 const params = slots();
349 const children = $i18n.i(
350 path,
351 locale,
352 onlyHasDefaultPlace(params) || places
353 ? useLegacyPlaces(params.default, places)
354 : params
355 );
356
357 const tag = props.tag || 'span';
358 return tag ? h(tag, data, children) : children
359 }
360};
361
362function onlyHasDefaultPlace (params) {
363 let prop;
364 for (prop in params) {
365 if (prop !== 'default') { return false }
366 }
367 return Boolean(prop)
368}
369
370function useLegacyPlaces (children, places) {
371 const params = places ? createParamsFromPlaces(places) : {};
372
373 if (!children) { return params }
374
375 // Filter empty text nodes
376 children = children.filter(child => {
377 return child.tag || child.text.trim() !== ''
378 });
379
380 const everyPlace = children.every(vnodeHasPlaceAttribute);
381 if (everyPlace) {
382 warn('`place` attribute is deprecated in next major version. Please switch to Vue slots.');
383 }
384
385 return children.reduce(
386 everyPlace ? assignChildPlace : assignChildIndex,
387 params
388 )
389}
390
391function createParamsFromPlaces (places) {
392 {
393 warn('`places` prop is deprecated in next major version. Please switch to Vue slots.');
394 }
395
396 return Array.isArray(places)
397 ? places.reduce(assignChildIndex, {})
398 : Object.assign({}, places)
399}
400
401function assignChildPlace (params, child) {
402 if (child.data && child.data.attrs && child.data.attrs.place) {
403 params[child.data.attrs.place] = child;
404 }
405 return params
406}
407
408function assignChildIndex (params, child, index) {
409 params[index] = child;
410 return params
411}
412
413function vnodeHasPlaceAttribute (vnode) {
414 return Boolean(vnode.data && vnode.data.attrs && vnode.data.attrs.place)
415}
416
417/* */
418
419var numberComponent = {
420 name: 'i18n-n',
421 functional: true,
422 props: {
423 tag: {
424 type: String,
425 default: 'span'
426 },
427 value: {
428 type: Number,
429 required: true
430 },
431 format: {
432 type: [String, Object]
433 },
434 locale: {
435 type: String
436 }
437 },
438 render (h, { props, parent, data }) {
439 const i18n = parent.$i18n;
440
441 if (!i18n) {
442 {
443 warn('Cannot find VueI18n instance!');
444 }
445 return null
446 }
447
448 let key = null;
449 let options = null;
450
451 if (typeof props.format === 'string') {
452 key = props.format;
453 } else if (isObject(props.format)) {
454 if (props.format.key) {
455 key = props.format.key;
456 }
457
458 // Filter out number format options only
459 options = Object.keys(props.format).reduce((acc, prop) => {
460 if (numberFormatKeys.includes(prop)) {
461 return Object.assign({}, acc, { [prop]: props.format[prop] })
462 }
463 return acc
464 }, null);
465 }
466
467 const locale = props.locale || i18n.locale;
468 const parts = i18n._ntp(props.value, locale, key, options);
469
470 const values = parts.map((part, index) => {
471 const slot = data.scopedSlots && data.scopedSlots[part.type];
472 return slot ? slot({ [part.type]: part.value, index, parts }) : part.value
473 });
474
475 return h(props.tag, {
476 attrs: data.attrs,
477 'class': data['class'],
478 staticClass: data.staticClass
479 }, values)
480 }
481};
482
483/* */
484
485function bind (el, binding, vnode) {
486 if (!assert(el, vnode)) { return }
487
488 t(el, binding, vnode);
489}
490
491function update (el, binding, vnode, oldVNode) {
492 if (!assert(el, vnode)) { return }
493
494 const i18n = vnode.context.$i18n;
495 if (localeEqual(el, vnode) &&
496 (looseEqual(binding.value, binding.oldValue) &&
497 looseEqual(el._localeMessage, i18n.getLocaleMessage(i18n.locale)))) { return }
498
499 t(el, binding, vnode);
500}
501
502function unbind (el, binding, vnode, oldVNode) {
503 const vm = vnode.context;
504 if (!vm) {
505 warn('Vue instance does not exists in VNode context');
506 return
507 }
508
509 const i18n = vnode.context.$i18n || {};
510 if (!binding.modifiers.preserve && !i18n.preserveDirectiveContent) {
511 el.textContent = '';
512 }
513 el._vt = undefined;
514 delete el['_vt'];
515 el._locale = undefined;
516 delete el['_locale'];
517 el._localeMessage = undefined;
518 delete el['_localeMessage'];
519}
520
521function assert (el, vnode) {
522 const vm = vnode.context;
523 if (!vm) {
524 warn('Vue instance does not exists in VNode context');
525 return false
526 }
527
528 if (!vm.$i18n) {
529 warn('VueI18n instance does not exists in Vue instance');
530 return false
531 }
532
533 return true
534}
535
536function localeEqual (el, vnode) {
537 const vm = vnode.context;
538 return el._locale === vm.$i18n.locale
539}
540
541function t (el, binding, vnode) {
542 const value = binding.value;
543
544 const { path, locale, args, choice } = parseValue(value);
545 if (!path && !locale && !args) {
546 warn('value type not supported');
547 return
548 }
549
550 if (!path) {
551 warn('`path` is required in v-t directive');
552 return
553 }
554
555 const vm = vnode.context;
556 if (choice) {
557 el._vt = el.textContent = vm.$i18n.tc(path, choice, ...makeParams(locale, args));
558 } else {
559 el._vt = el.textContent = vm.$i18n.t(path, ...makeParams(locale, args));
560 }
561 el._locale = vm.$i18n.locale;
562 el._localeMessage = vm.$i18n.getLocaleMessage(vm.$i18n.locale);
563}
564
565function parseValue (value) {
566 let path;
567 let locale;
568 let args;
569 let choice;
570
571 if (typeof value === 'string') {
572 path = value;
573 } else if (isPlainObject(value)) {
574 path = value.path;
575 locale = value.locale;
576 args = value.args;
577 choice = value.choice;
578 }
579
580 return { path, locale, args, choice }
581}
582
583function makeParams (locale, args) {
584 const params = [];
585
586 locale && params.push(locale);
587 if (args && (Array.isArray(args) || isPlainObject(args))) {
588 params.push(args);
589 }
590
591 return params
592}
593
594let Vue;
595
596function install (_Vue) {
597 /* istanbul ignore if */
598 if (install.installed && _Vue === Vue) {
599 warn('already installed.');
600 return
601 }
602 install.installed = true;
603
604 Vue = _Vue;
605
606 const version = (Vue.version && Number(Vue.version.split('.')[0])) || -1;
607 /* istanbul ignore if */
608 if (version < 2) {
609 warn(`vue-i18n (${install.version}) need to use Vue 2.0 or later (Vue: ${Vue.version}).`);
610 return
611 }
612
613 extend(Vue);
614 Vue.mixin(mixin);
615 Vue.directive('t', { bind, update, unbind });
616 Vue.component(interpolationComponent.name, interpolationComponent);
617 Vue.component(numberComponent.name, numberComponent);
618
619 // use simple mergeStrategies to prevent i18n instance lose '__proto__'
620 const strats = Vue.config.optionMergeStrategies;
621 strats.i18n = function (parentVal, childVal) {
622 return childVal === undefined
623 ? parentVal
624 : childVal
625 };
626}
627
628/* */
629
630class BaseFormatter {
631
632
633 constructor () {
634 this._caches = Object.create(null);
635 }
636
637 interpolate (message, values) {
638 if (!values) {
639 return [message]
640 }
641 let tokens = this._caches[message];
642 if (!tokens) {
643 tokens = parse(message);
644 this._caches[message] = tokens;
645 }
646 return compile(tokens, values)
647 }
648}
649
650
651
652const RE_TOKEN_LIST_VALUE = /^(?:\d)+/;
653const RE_TOKEN_NAMED_VALUE = /^(?:\w)+/;
654
655function parse (format) {
656 const tokens = [];
657 let position = 0;
658
659 let text = '';
660 while (position < format.length) {
661 let char = format[position++];
662 if (char === '{') {
663 if (text) {
664 tokens.push({ type: 'text', value: text });
665 }
666
667 text = '';
668 let sub = '';
669 char = format[position++];
670 while (char !== undefined && char !== '}') {
671 sub += char;
672 char = format[position++];
673 }
674 const isClosed = char === '}';
675
676 const type = RE_TOKEN_LIST_VALUE.test(sub)
677 ? 'list'
678 : isClosed && RE_TOKEN_NAMED_VALUE.test(sub)
679 ? 'named'
680 : 'unknown';
681 tokens.push({ value: sub, type });
682 } else if (char === '%') {
683 // when found rails i18n syntax, skip text capture
684 if (format[(position)] !== '{') {
685 text += char;
686 }
687 } else {
688 text += char;
689 }
690 }
691
692 text && tokens.push({ type: 'text', value: text });
693
694 return tokens
695}
696
697function compile (tokens, values) {
698 const compiled = [];
699 let index = 0;
700
701 const mode = Array.isArray(values)
702 ? 'list'
703 : isObject(values)
704 ? 'named'
705 : 'unknown';
706 if (mode === 'unknown') { return compiled }
707
708 while (index < tokens.length) {
709 const token = tokens[index];
710 switch (token.type) {
711 case 'text':
712 compiled.push(token.value);
713 break
714 case 'list':
715 compiled.push(values[parseInt(token.value, 10)]);
716 break
717 case 'named':
718 if (mode === 'named') {
719 compiled.push((values)[token.value]);
720 } else {
721 {
722 warn(`Type of token '${token.type}' and format of value '${mode}' don't match!`);
723 }
724 }
725 break
726 case 'unknown':
727 {
728 warn(`Detect 'unknown' type of token!`);
729 }
730 break
731 }
732 index++;
733 }
734
735 return compiled
736}
737
738/* */
739
740/**
741 * Path parser
742 * - Inspired:
743 * Vue.js Path parser
744 */
745
746// actions
747const APPEND = 0;
748const PUSH = 1;
749const INC_SUB_PATH_DEPTH = 2;
750const PUSH_SUB_PATH = 3;
751
752// states
753const BEFORE_PATH = 0;
754const IN_PATH = 1;
755const BEFORE_IDENT = 2;
756const IN_IDENT = 3;
757const IN_SUB_PATH = 4;
758const IN_SINGLE_QUOTE = 5;
759const IN_DOUBLE_QUOTE = 6;
760const AFTER_PATH = 7;
761const ERROR = 8;
762
763const pathStateMachine = [];
764
765pathStateMachine[BEFORE_PATH] = {
766 'ws': [BEFORE_PATH],
767 'ident': [IN_IDENT, APPEND],
768 '[': [IN_SUB_PATH],
769 'eof': [AFTER_PATH]
770};
771
772pathStateMachine[IN_PATH] = {
773 'ws': [IN_PATH],
774 '.': [BEFORE_IDENT],
775 '[': [IN_SUB_PATH],
776 'eof': [AFTER_PATH]
777};
778
779pathStateMachine[BEFORE_IDENT] = {
780 'ws': [BEFORE_IDENT],
781 'ident': [IN_IDENT, APPEND],
782 '0': [IN_IDENT, APPEND],
783 'number': [IN_IDENT, APPEND]
784};
785
786pathStateMachine[IN_IDENT] = {
787 'ident': [IN_IDENT, APPEND],
788 '0': [IN_IDENT, APPEND],
789 'number': [IN_IDENT, APPEND],
790 'ws': [IN_PATH, PUSH],
791 '.': [BEFORE_IDENT, PUSH],
792 '[': [IN_SUB_PATH, PUSH],
793 'eof': [AFTER_PATH, PUSH]
794};
795
796pathStateMachine[IN_SUB_PATH] = {
797 "'": [IN_SINGLE_QUOTE, APPEND],
798 '"': [IN_DOUBLE_QUOTE, APPEND],
799 '[': [IN_SUB_PATH, INC_SUB_PATH_DEPTH],
800 ']': [IN_PATH, PUSH_SUB_PATH],
801 'eof': ERROR,
802 'else': [IN_SUB_PATH, APPEND]
803};
804
805pathStateMachine[IN_SINGLE_QUOTE] = {
806 "'": [IN_SUB_PATH, APPEND],
807 'eof': ERROR,
808 'else': [IN_SINGLE_QUOTE, APPEND]
809};
810
811pathStateMachine[IN_DOUBLE_QUOTE] = {
812 '"': [IN_SUB_PATH, APPEND],
813 'eof': ERROR,
814 'else': [IN_DOUBLE_QUOTE, APPEND]
815};
816
817/**
818 * Check if an expression is a literal value.
819 */
820
821const literalValueRE = /^\s?(?:true|false|-?[\d.]+|'[^']*'|"[^"]*")\s?$/;
822function isLiteral (exp) {
823 return literalValueRE.test(exp)
824}
825
826/**
827 * Strip quotes from a string
828 */
829
830function stripQuotes (str) {
831 const a = str.charCodeAt(0);
832 const b = str.charCodeAt(str.length - 1);
833 return a === b && (a === 0x22 || a === 0x27)
834 ? str.slice(1, -1)
835 : str
836}
837
838/**
839 * Determine the type of a character in a keypath.
840 */
841
842function getPathCharType (ch) {
843 if (ch === undefined || ch === null) { return 'eof' }
844
845 const code = ch.charCodeAt(0);
846
847 switch (code) {
848 case 0x5B: // [
849 case 0x5D: // ]
850 case 0x2E: // .
851 case 0x22: // "
852 case 0x27: // '
853 return ch
854
855 case 0x5F: // _
856 case 0x24: // $
857 case 0x2D: // -
858 return 'ident'
859
860 case 0x09: // Tab
861 case 0x0A: // Newline
862 case 0x0D: // Return
863 case 0xA0: // No-break space
864 case 0xFEFF: // Byte Order Mark
865 case 0x2028: // Line Separator
866 case 0x2029: // Paragraph Separator
867 return 'ws'
868 }
869
870 return 'ident'
871}
872
873/**
874 * Format a subPath, return its plain form if it is
875 * a literal string or number. Otherwise prepend the
876 * dynamic indicator (*).
877 */
878
879function formatSubPath (path) {
880 const trimmed = path.trim();
881 // invalid leading 0
882 if (path.charAt(0) === '0' && isNaN(path)) { return false }
883
884 return isLiteral(trimmed) ? stripQuotes(trimmed) : '*' + trimmed
885}
886
887/**
888 * Parse a string path into an array of segments
889 */
890
891function parse$1 (path) {
892 const keys = [];
893 let index = -1;
894 let mode = BEFORE_PATH;
895 let subPathDepth = 0;
896 let c;
897 let key;
898 let newChar;
899 let type;
900 let transition;
901 let action;
902 let typeMap;
903 const actions = [];
904
905 actions[PUSH] = function () {
906 if (key !== undefined) {
907 keys.push(key);
908 key = undefined;
909 }
910 };
911
912 actions[APPEND] = function () {
913 if (key === undefined) {
914 key = newChar;
915 } else {
916 key += newChar;
917 }
918 };
919
920 actions[INC_SUB_PATH_DEPTH] = function () {
921 actions[APPEND]();
922 subPathDepth++;
923 };
924
925 actions[PUSH_SUB_PATH] = function () {
926 if (subPathDepth > 0) {
927 subPathDepth--;
928 mode = IN_SUB_PATH;
929 actions[APPEND]();
930 } else {
931 subPathDepth = 0;
932 if (key === undefined) { return false }
933 key = formatSubPath(key);
934 if (key === false) {
935 return false
936 } else {
937 actions[PUSH]();
938 }
939 }
940 };
941
942 function maybeUnescapeQuote () {
943 const nextChar = path[index + 1];
944 if ((mode === IN_SINGLE_QUOTE && nextChar === "'") ||
945 (mode === IN_DOUBLE_QUOTE && nextChar === '"')) {
946 index++;
947 newChar = '\\' + nextChar;
948 actions[APPEND]();
949 return true
950 }
951 }
952
953 while (mode !== null) {
954 index++;
955 c = path[index];
956
957 if (c === '\\' && maybeUnescapeQuote()) {
958 continue
959 }
960
961 type = getPathCharType(c);
962 typeMap = pathStateMachine[mode];
963 transition = typeMap[type] || typeMap['else'] || ERROR;
964
965 if (transition === ERROR) {
966 return // parse error
967 }
968
969 mode = transition[0];
970 action = actions[transition[1]];
971 if (action) {
972 newChar = transition[2];
973 newChar = newChar === undefined
974 ? c
975 : newChar;
976 if (action() === false) {
977 return
978 }
979 }
980
981 if (mode === AFTER_PATH) {
982 return keys
983 }
984 }
985}
986
987
988
989
990
991class I18nPath {
992
993
994 constructor () {
995 this._cache = Object.create(null);
996 }
997
998 /**
999 * External parse that check for a cache hit first
1000 */
1001 parsePath (path) {
1002 let hit = this._cache[path];
1003 if (!hit) {
1004 hit = parse$1(path);
1005 if (hit) {
1006 this._cache[path] = hit;
1007 }
1008 }
1009 return hit || []
1010 }
1011
1012 /**
1013 * Get path value from path string
1014 */
1015 getPathValue (obj, path) {
1016 if (!isObject(obj)) { return null }
1017
1018 const paths = this.parsePath(path);
1019 if (paths.length === 0) {
1020 return null
1021 } else {
1022 const length = paths.length;
1023 let last = obj;
1024 let i = 0;
1025 while (i < length) {
1026 const value = last[paths[i]];
1027 if (value === undefined) {
1028 return null
1029 }
1030 last = value;
1031 i++;
1032 }
1033
1034 return last
1035 }
1036 }
1037}
1038
1039/* */
1040
1041
1042
1043const htmlTagMatcher = /<\/?[\w\s="/.':;#-\/]+>/;
1044const linkKeyMatcher = /(?:@(?:\.[a-z]+)?:(?:[\w\-_|.]+|\([\w\-_|.]+\)))/g;
1045const linkKeyPrefixMatcher = /^@(?:\.([a-z]+))?:/;
1046const bracketsMatcher = /[()]/g;
1047const defaultModifiers = {
1048 'upper': str => str.toLocaleUpperCase(),
1049 'lower': str => str.toLocaleLowerCase()
1050};
1051
1052const defaultFormatter = new BaseFormatter();
1053
1054class VueI18n {
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078 constructor (options = {}) {
1079 // Auto install if it is not done yet and `window` has `Vue`.
1080 // To allow users to avoid auto-installation in some cases,
1081 // this code should be placed here. See #290
1082 /* istanbul ignore if */
1083 if (!Vue && typeof window !== 'undefined' && window.Vue) {
1084 install(window.Vue);
1085 }
1086
1087 const locale = options.locale || 'en-US';
1088 const fallbackLocale = options.fallbackLocale || 'en-US';
1089 const messages = options.messages || {};
1090 const dateTimeFormats = options.dateTimeFormats || {};
1091 const numberFormats = options.numberFormats || {};
1092
1093 this._vm = null;
1094 this._formatter = options.formatter || defaultFormatter;
1095 this._modifiers = options.modifiers || {};
1096 this._missing = options.missing || null;
1097 this._root = options.root || null;
1098 this._sync = options.sync === undefined ? true : !!options.sync;
1099 this._fallbackRoot = options.fallbackRoot === undefined
1100 ? true
1101 : !!options.fallbackRoot;
1102 this._formatFallbackMessages = options.formatFallbackMessages === undefined
1103 ? false
1104 : !!options.formatFallbackMessages;
1105 this._silentTranslationWarn = options.silentTranslationWarn === undefined
1106 ? false
1107 : options.silentTranslationWarn;
1108 this._silentFallbackWarn = options.silentFallbackWarn === undefined
1109 ? false
1110 : !!options.silentFallbackWarn;
1111 this._dateTimeFormatters = {};
1112 this._numberFormatters = {};
1113 this._path = new I18nPath();
1114 this._dataListeners = [];
1115 this._preserveDirectiveContent = options.preserveDirectiveContent === undefined
1116 ? false
1117 : !!options.preserveDirectiveContent;
1118 this.pluralizationRules = options.pluralizationRules || {};
1119 this._warnHtmlInMessage = options.warnHtmlInMessage || 'off';
1120
1121 this._exist = (message, key) => {
1122 if (!message || !key) { return false }
1123 if (!isNull(this._path.getPathValue(message, key))) { return true }
1124 // fallback for flat key
1125 if (message[key]) { return true }
1126 return false
1127 };
1128
1129 if (this._warnHtmlInMessage === 'warn' || this._warnHtmlInMessage === 'error') {
1130 Object.keys(messages).forEach(locale => {
1131 this._checkLocaleMessage(locale, this._warnHtmlInMessage, messages[locale]);
1132 });
1133 }
1134
1135 this._initVM({
1136 locale,
1137 fallbackLocale,
1138 messages,
1139 dateTimeFormats,
1140 numberFormats
1141 });
1142 }
1143
1144 _checkLocaleMessage (locale, level, message) {
1145 const paths = [];
1146
1147 const fn = (level, locale, message, paths) => {
1148 if (isPlainObject(message)) {
1149 Object.keys(message).forEach(key => {
1150 const val = message[key];
1151 if (isPlainObject(val)) {
1152 paths.push(key);
1153 paths.push('.');
1154 fn(level, locale, val, paths);
1155 paths.pop();
1156 paths.pop();
1157 } else {
1158 paths.push(key);
1159 fn(level, locale, val, paths);
1160 paths.pop();
1161 }
1162 });
1163 } else if (Array.isArray(message)) {
1164 message.forEach((item, index) => {
1165 if (isPlainObject(item)) {
1166 paths.push(`[${index}]`);
1167 paths.push('.');
1168 fn(level, locale, item, paths);
1169 paths.pop();
1170 paths.pop();
1171 } else {
1172 paths.push(`[${index}]`);
1173 fn(level, locale, item, paths);
1174 paths.pop();
1175 }
1176 });
1177 } else if (typeof message === 'string') {
1178 const ret = htmlTagMatcher.test(message);
1179 if (ret) {
1180 const msg = `Detected HTML in message '${message}' of keypath '${paths.join('')}' at '${locale}'. Consider component interpolation with '<i18n>' to avoid XSS. See https://bit.ly/2ZqJzkp`;
1181 if (level === 'warn') {
1182 warn(msg);
1183 } else if (level === 'error') {
1184 error(msg);
1185 }
1186 }
1187 }
1188 };
1189
1190 fn(level, locale, message, paths);
1191 }
1192
1193 _initVM (data) {
1194 const silent = Vue.config.silent;
1195 Vue.config.silent = true;
1196 this._vm = new Vue({ data });
1197 Vue.config.silent = silent;
1198 }
1199
1200 destroyVM () {
1201 this._vm.$destroy();
1202 }
1203
1204 subscribeDataChanging (vm) {
1205 this._dataListeners.push(vm);
1206 }
1207
1208 unsubscribeDataChanging (vm) {
1209 remove(this._dataListeners, vm);
1210 }
1211
1212 watchI18nData () {
1213 const self = this;
1214 return this._vm.$watch('$data', () => {
1215 let i = self._dataListeners.length;
1216 while (i--) {
1217 Vue.nextTick(() => {
1218 self._dataListeners[i] && self._dataListeners[i].$forceUpdate();
1219 });
1220 }
1221 }, { deep: true })
1222 }
1223
1224 watchLocale () {
1225 /* istanbul ignore if */
1226 if (!this._sync || !this._root) { return null }
1227 const target = this._vm;
1228 return this._root.$i18n.vm.$watch('locale', (val) => {
1229 target.$set(target, 'locale', val);
1230 target.$forceUpdate();
1231 }, { immediate: true })
1232 }
1233
1234 get vm () { return this._vm }
1235
1236 get messages () { return looseClone(this._getMessages()) }
1237 get dateTimeFormats () { return looseClone(this._getDateTimeFormats()) }
1238 get numberFormats () { return looseClone(this._getNumberFormats()) }
1239 get availableLocales () { return Object.keys(this.messages).sort() }
1240
1241 get locale () { return this._vm.locale }
1242 set locale (locale) {
1243 this._vm.$set(this._vm, 'locale', locale);
1244 }
1245
1246 get fallbackLocale () { return this._vm.fallbackLocale }
1247 set fallbackLocale (locale) {
1248 this._vm.$set(this._vm, 'fallbackLocale', locale);
1249 }
1250
1251 get formatFallbackMessages () { return this._formatFallbackMessages }
1252 set formatFallbackMessages (fallback) { this._formatFallbackMessages = fallback; }
1253
1254 get missing () { return this._missing }
1255 set missing (handler) { this._missing = handler; }
1256
1257 get formatter () { return this._formatter }
1258 set formatter (formatter) { this._formatter = formatter; }
1259
1260 get silentTranslationWarn () { return this._silentTranslationWarn }
1261 set silentTranslationWarn (silent) { this._silentTranslationWarn = silent; }
1262
1263 get silentFallbackWarn () { return this._silentFallbackWarn }
1264 set silentFallbackWarn (silent) { this._silentFallbackWarn = silent; }
1265
1266 get preserveDirectiveContent () { return this._preserveDirectiveContent }
1267 set preserveDirectiveContent (preserve) { this._preserveDirectiveContent = preserve; }
1268
1269 get warnHtmlInMessage () { return this._warnHtmlInMessage }
1270 set warnHtmlInMessage (level) {
1271 const orgLevel = this._warnHtmlInMessage;
1272 this._warnHtmlInMessage = level;
1273 if (orgLevel !== level && (level === 'warn' || level === 'error')) {
1274 const messages = this._getMessages();
1275 Object.keys(messages).forEach(locale => {
1276 this._checkLocaleMessage(locale, this._warnHtmlInMessage, messages[locale]);
1277 });
1278 }
1279 }
1280
1281 _getMessages () { return this._vm.messages }
1282 _getDateTimeFormats () { return this._vm.dateTimeFormats }
1283 _getNumberFormats () { return this._vm.numberFormats }
1284
1285 _warnDefault (locale, key, result, vm, values) {
1286 if (!isNull(result)) { return result }
1287 if (this._missing) {
1288 const missingRet = this._missing.apply(null, [locale, key, vm, values]);
1289 if (typeof missingRet === 'string') {
1290 return missingRet
1291 }
1292 } else {
1293 if (!this._isSilentTranslationWarn(key)) {
1294 warn(
1295 `Cannot translate the value of keypath '${key}'. ` +
1296 'Use the value of keypath as default.'
1297 );
1298 }
1299 }
1300
1301 if (this._formatFallbackMessages) {
1302 const parsedArgs = parseArgs(...values);
1303 return this._render(key, 'string', parsedArgs.params, key)
1304 } else {
1305 return key
1306 }
1307 }
1308
1309 _isFallbackRoot (val) {
1310 return !val && !isNull(this._root) && this._fallbackRoot
1311 }
1312
1313 _isSilentFallbackWarn (key) {
1314 return this._silentFallbackWarn instanceof RegExp
1315 ? this._silentFallbackWarn.test(key)
1316 : this._silentFallbackWarn
1317 }
1318
1319 _isSilentFallback (locale, key) {
1320 return this._isSilentFallbackWarn(key) && (this._isFallbackRoot() || locale !== this.fallbackLocale)
1321 }
1322
1323 _isSilentTranslationWarn (key) {
1324 return this._silentTranslationWarn instanceof RegExp
1325 ? this._silentTranslationWarn.test(key)
1326 : this._silentTranslationWarn
1327 }
1328
1329 _interpolate (
1330 locale,
1331 message,
1332 key,
1333 host,
1334 interpolateMode,
1335 values,
1336 visitedLinkStack
1337 ) {
1338 if (!message) { return null }
1339
1340 const pathRet = this._path.getPathValue(message, key);
1341 if (Array.isArray(pathRet) || isPlainObject(pathRet)) { return pathRet }
1342
1343 let ret;
1344 if (isNull(pathRet)) {
1345 /* istanbul ignore else */
1346 if (isPlainObject(message)) {
1347 ret = message[key];
1348 if (typeof ret !== 'string') {
1349 if (!this._isSilentTranslationWarn(key) && !this._isSilentFallback(locale, key)) {
1350 warn(`Value of key '${key}' is not a string!`);
1351 }
1352 return null
1353 }
1354 } else {
1355 return null
1356 }
1357 } else {
1358 /* istanbul ignore else */
1359 if (typeof pathRet === 'string') {
1360 ret = pathRet;
1361 } else {
1362 if (!this._isSilentTranslationWarn(key) && !this._isSilentFallback(locale, key)) {
1363 warn(`Value of key '${key}' is not a string!`);
1364 }
1365 return null
1366 }
1367 }
1368
1369 // Check for the existence of links within the translated string
1370 if (ret.indexOf('@:') >= 0 || ret.indexOf('@.') >= 0) {
1371 ret = this._link(locale, message, ret, host, 'raw', values, visitedLinkStack);
1372 }
1373
1374 return this._render(ret, interpolateMode, values, key)
1375 }
1376
1377 _link (
1378 locale,
1379 message,
1380 str,
1381 host,
1382 interpolateMode,
1383 values,
1384 visitedLinkStack
1385 ) {
1386 let ret = str;
1387
1388 // Match all the links within the local
1389 // We are going to replace each of
1390 // them with its translation
1391 const matches = ret.match(linkKeyMatcher);
1392 for (const idx in matches) {
1393 // ie compatible: filter custom array
1394 // prototype method
1395 if (!matches.hasOwnProperty(idx)) {
1396 continue
1397 }
1398 const link = matches[idx];
1399 const linkKeyPrefixMatches = link.match(linkKeyPrefixMatcher);
1400 const [linkPrefix, formatterName] = linkKeyPrefixMatches;
1401
1402 // Remove the leading @:, @.case: and the brackets
1403 const linkPlaceholder = link.replace(linkPrefix, '').replace(bracketsMatcher, '');
1404
1405 if (visitedLinkStack.includes(linkPlaceholder)) {
1406 {
1407 warn(`Circular reference found. "${link}" is already visited in the chain of ${visitedLinkStack.reverse().join(' <- ')}`);
1408 }
1409 return ret
1410 }
1411 visitedLinkStack.push(linkPlaceholder);
1412
1413 // Translate the link
1414 let translated = this._interpolate(
1415 locale, message, linkPlaceholder, host,
1416 interpolateMode === 'raw' ? 'string' : interpolateMode,
1417 interpolateMode === 'raw' ? undefined : values,
1418 visitedLinkStack
1419 );
1420
1421 if (this._isFallbackRoot(translated)) {
1422 if (!this._isSilentTranslationWarn(linkPlaceholder)) {
1423 warn(`Fall back to translate the link placeholder '${linkPlaceholder}' with root locale.`);
1424 }
1425 /* istanbul ignore if */
1426 if (!this._root) { throw Error('unexpected error') }
1427 const root = this._root.$i18n;
1428 translated = root._translate(
1429 root._getMessages(), root.locale, root.fallbackLocale,
1430 linkPlaceholder, host, interpolateMode, values
1431 );
1432 }
1433 translated = this._warnDefault(
1434 locale, linkPlaceholder, translated, host,
1435 Array.isArray(values) ? values : [values]
1436 );
1437
1438 if (this._modifiers.hasOwnProperty(formatterName)) {
1439 translated = this._modifiers[formatterName](translated);
1440 } else if (defaultModifiers.hasOwnProperty(formatterName)) {
1441 translated = defaultModifiers[formatterName](translated);
1442 }
1443
1444 visitedLinkStack.pop();
1445
1446 // Replace the link with the translated
1447 ret = !translated ? ret : ret.replace(link, translated);
1448 }
1449
1450 return ret
1451 }
1452
1453 _render (message, interpolateMode, values, path) {
1454 let ret = this._formatter.interpolate(message, values, path);
1455
1456 // If the custom formatter refuses to work - apply the default one
1457 if (!ret) {
1458 ret = defaultFormatter.interpolate(message, values, path);
1459 }
1460
1461 // if interpolateMode is **not** 'string' ('row'),
1462 // return the compiled data (e.g. ['foo', VNode, 'bar']) with formatter
1463 return interpolateMode === 'string' ? ret.join('') : ret
1464 }
1465
1466 _translate (
1467 messages,
1468 locale,
1469 fallback,
1470 key,
1471 host,
1472 interpolateMode,
1473 args
1474 ) {
1475 let res =
1476 this._interpolate(locale, messages[locale], key, host, interpolateMode, args, [key]);
1477 if (!isNull(res)) { return res }
1478
1479 res = this._interpolate(fallback, messages[fallback], key, host, interpolateMode, args, [key]);
1480 if (!isNull(res)) {
1481 if (!this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) {
1482 warn(`Fall back to translate the keypath '${key}' with '${fallback}' locale.`);
1483 }
1484 return res
1485 } else {
1486 return null
1487 }
1488 }
1489
1490 _t (key, _locale, messages, host, ...values) {
1491 if (!key) { return '' }
1492
1493 const parsedArgs = parseArgs(...values);
1494 const locale = parsedArgs.locale || _locale;
1495
1496 const ret = this._translate(
1497 messages, locale, this.fallbackLocale, key,
1498 host, 'string', parsedArgs.params
1499 );
1500 if (this._isFallbackRoot(ret)) {
1501 if (!this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) {
1502 warn(`Fall back to translate the keypath '${key}' with root locale.`);
1503 }
1504 /* istanbul ignore if */
1505 if (!this._root) { throw Error('unexpected error') }
1506 return this._root.$t(key, ...values)
1507 } else {
1508 return this._warnDefault(locale, key, ret, host, values)
1509 }
1510 }
1511
1512 t (key, ...values) {
1513 return this._t(key, this.locale, this._getMessages(), null, ...values)
1514 }
1515
1516 _i (key, locale, messages, host, values) {
1517 const ret =
1518 this._translate(messages, locale, this.fallbackLocale, key, host, 'raw', values);
1519 if (this._isFallbackRoot(ret)) {
1520 if (!this._isSilentTranslationWarn(key)) {
1521 warn(`Fall back to interpolate the keypath '${key}' with root locale.`);
1522 }
1523 if (!this._root) { throw Error('unexpected error') }
1524 return this._root.$i18n.i(key, locale, values)
1525 } else {
1526 return this._warnDefault(locale, key, ret, host, [values])
1527 }
1528 }
1529
1530 i (key, locale, values) {
1531 /* istanbul ignore if */
1532 if (!key) { return '' }
1533
1534 if (typeof locale !== 'string') {
1535 locale = this.locale;
1536 }
1537
1538 return this._i(key, locale, this._getMessages(), null, values)
1539 }
1540
1541 _tc (
1542 key,
1543 _locale,
1544 messages,
1545 host,
1546 choice,
1547 ...values
1548 ) {
1549 if (!key) { return '' }
1550 if (choice === undefined) {
1551 choice = 1;
1552 }
1553
1554 const predefined = { 'count': choice, 'n': choice };
1555 const parsedArgs = parseArgs(...values);
1556 parsedArgs.params = Object.assign(predefined, parsedArgs.params);
1557 values = parsedArgs.locale === null ? [parsedArgs.params] : [parsedArgs.locale, parsedArgs.params];
1558 return this.fetchChoice(this._t(key, _locale, messages, host, ...values), choice)
1559 }
1560
1561 fetchChoice (message, choice) {
1562 /* istanbul ignore if */
1563 if (!message && typeof message !== 'string') { return null }
1564 const choices = message.split('|');
1565
1566 choice = this.getChoiceIndex(choice, choices.length);
1567 if (!choices[choice]) { return message }
1568 return choices[choice].trim()
1569 }
1570
1571 /**
1572 * @param choice {number} a choice index given by the input to $tc: `$tc('path.to.rule', choiceIndex)`
1573 * @param choicesLength {number} an overall amount of available choices
1574 * @returns a final choice index
1575 */
1576 getChoiceIndex (choice, choicesLength) {
1577 // Default (old) getChoiceIndex implementation - english-compatible
1578 const defaultImpl = (_choice, _choicesLength) => {
1579 _choice = Math.abs(_choice);
1580
1581 if (_choicesLength === 2) {
1582 return _choice
1583 ? _choice > 1
1584 ? 1
1585 : 0
1586 : 1
1587 }
1588
1589 return _choice ? Math.min(_choice, 2) : 0
1590 };
1591
1592 if (this.locale in this.pluralizationRules) {
1593 return this.pluralizationRules[this.locale].apply(this, [choice, choicesLength])
1594 } else {
1595 return defaultImpl(choice, choicesLength)
1596 }
1597 }
1598
1599 tc (key, choice, ...values) {
1600 return this._tc(key, this.locale, this._getMessages(), null, choice, ...values)
1601 }
1602
1603 _te (key, locale, messages, ...args) {
1604 const _locale = parseArgs(...args).locale || locale;
1605 return this._exist(messages[_locale], key)
1606 }
1607
1608 te (key, locale) {
1609 return this._te(key, this.locale, this._getMessages(), locale)
1610 }
1611
1612 getLocaleMessage (locale) {
1613 return looseClone(this._vm.messages[locale] || {})
1614 }
1615
1616 setLocaleMessage (locale, message) {
1617 if (this._warnHtmlInMessage === 'warn' || this._warnHtmlInMessage === 'error') {
1618 this._checkLocaleMessage(locale, this._warnHtmlInMessage, message);
1619 if (this._warnHtmlInMessage === 'error') { return }
1620 }
1621 this._vm.$set(this._vm.messages, locale, message);
1622 }
1623
1624 mergeLocaleMessage (locale, message) {
1625 if (this._warnHtmlInMessage === 'warn' || this._warnHtmlInMessage === 'error') {
1626 this._checkLocaleMessage(locale, this._warnHtmlInMessage, message);
1627 if (this._warnHtmlInMessage === 'error') { return }
1628 }
1629 this._vm.$set(this._vm.messages, locale, merge(this._vm.messages[locale] || {}, message));
1630 }
1631
1632 getDateTimeFormat (locale) {
1633 return looseClone(this._vm.dateTimeFormats[locale] || {})
1634 }
1635
1636 setDateTimeFormat (locale, format) {
1637 this._vm.$set(this._vm.dateTimeFormats, locale, format);
1638 }
1639
1640 mergeDateTimeFormat (locale, format) {
1641 this._vm.$set(this._vm.dateTimeFormats, locale, merge(this._vm.dateTimeFormats[locale] || {}, format));
1642 }
1643
1644 _localizeDateTime (
1645 value,
1646 locale,
1647 fallback,
1648 dateTimeFormats,
1649 key
1650 ) {
1651 let _locale = locale;
1652 let formats = dateTimeFormats[_locale];
1653
1654 // fallback locale
1655 if (isNull(formats) || isNull(formats[key])) {
1656 if (!this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) {
1657 warn(`Fall back to '${fallback}' datetime formats from '${locale}' datetime formats.`);
1658 }
1659 _locale = fallback;
1660 formats = dateTimeFormats[_locale];
1661 }
1662
1663 if (isNull(formats) || isNull(formats[key])) {
1664 return null
1665 } else {
1666 const format = formats[key];
1667 const id = `${_locale}__${key}`;
1668 let formatter = this._dateTimeFormatters[id];
1669 if (!formatter) {
1670 formatter = this._dateTimeFormatters[id] = new Intl.DateTimeFormat(_locale, format);
1671 }
1672 return formatter.format(value)
1673 }
1674 }
1675
1676 _d (value, locale, key) {
1677 /* istanbul ignore if */
1678 if (!VueI18n.availabilities.dateTimeFormat) {
1679 warn('Cannot format a Date value due to not supported Intl.DateTimeFormat.');
1680 return ''
1681 }
1682
1683 if (!key) {
1684 return new Intl.DateTimeFormat(locale).format(value)
1685 }
1686
1687 const ret =
1688 this._localizeDateTime(value, locale, this.fallbackLocale, this._getDateTimeFormats(), key);
1689 if (this._isFallbackRoot(ret)) {
1690 if (!this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) {
1691 warn(`Fall back to datetime localization of root: key '${key}'.`);
1692 }
1693 /* istanbul ignore if */
1694 if (!this._root) { throw Error('unexpected error') }
1695 return this._root.$i18n.d(value, key, locale)
1696 } else {
1697 return ret || ''
1698 }
1699 }
1700
1701 d (value, ...args) {
1702 let locale = this.locale;
1703 let key = null;
1704
1705 if (args.length === 1) {
1706 if (typeof args[0] === 'string') {
1707 key = args[0];
1708 } else if (isObject(args[0])) {
1709 if (args[0].locale) {
1710 locale = args[0].locale;
1711 }
1712 if (args[0].key) {
1713 key = args[0].key;
1714 }
1715 }
1716 } else if (args.length === 2) {
1717 if (typeof args[0] === 'string') {
1718 key = args[0];
1719 }
1720 if (typeof args[1] === 'string') {
1721 locale = args[1];
1722 }
1723 }
1724
1725 return this._d(value, locale, key)
1726 }
1727
1728 getNumberFormat (locale) {
1729 return looseClone(this._vm.numberFormats[locale] || {})
1730 }
1731
1732 setNumberFormat (locale, format) {
1733 this._vm.$set(this._vm.numberFormats, locale, format);
1734 }
1735
1736 mergeNumberFormat (locale, format) {
1737 this._vm.$set(this._vm.numberFormats, locale, merge(this._vm.numberFormats[locale] || {}, format));
1738 }
1739
1740 _getNumberFormatter (
1741 value,
1742 locale,
1743 fallback,
1744 numberFormats,
1745 key,
1746 options
1747 ) {
1748 let _locale = locale;
1749 let formats = numberFormats[_locale];
1750
1751 // fallback locale
1752 if (isNull(formats) || isNull(formats[key])) {
1753 if (!this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) {
1754 warn(`Fall back to '${fallback}' number formats from '${locale}' number formats.`);
1755 }
1756 _locale = fallback;
1757 formats = numberFormats[_locale];
1758 }
1759
1760 if (isNull(formats) || isNull(formats[key])) {
1761 return null
1762 } else {
1763 const format = formats[key];
1764
1765 let formatter;
1766 if (options) {
1767 // If options specified - create one time number formatter
1768 formatter = new Intl.NumberFormat(_locale, Object.assign({}, format, options));
1769 } else {
1770 const id = `${_locale}__${key}`;
1771 formatter = this._numberFormatters[id];
1772 if (!formatter) {
1773 formatter = this._numberFormatters[id] = new Intl.NumberFormat(_locale, format);
1774 }
1775 }
1776 return formatter
1777 }
1778 }
1779
1780 _n (value, locale, key, options) {
1781 /* istanbul ignore if */
1782 if (!VueI18n.availabilities.numberFormat) {
1783 {
1784 warn('Cannot format a Number value due to not supported Intl.NumberFormat.');
1785 }
1786 return ''
1787 }
1788
1789 if (!key) {
1790 const nf = !options ? new Intl.NumberFormat(locale) : new Intl.NumberFormat(locale, options);
1791 return nf.format(value)
1792 }
1793
1794 const formatter = this._getNumberFormatter(value, locale, this.fallbackLocale, this._getNumberFormats(), key, options);
1795 const ret = formatter && formatter.format(value);
1796 if (this._isFallbackRoot(ret)) {
1797 if (!this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) {
1798 warn(`Fall back to number localization of root: key '${key}'.`);
1799 }
1800 /* istanbul ignore if */
1801 if (!this._root) { throw Error('unexpected error') }
1802 return this._root.$i18n.n(value, Object.assign({}, { key, locale }, options))
1803 } else {
1804 return ret || ''
1805 }
1806 }
1807
1808 n (value, ...args) {
1809 let locale = this.locale;
1810 let key = null;
1811 let options = null;
1812
1813 if (args.length === 1) {
1814 if (typeof args[0] === 'string') {
1815 key = args[0];
1816 } else if (isObject(args[0])) {
1817 if (args[0].locale) {
1818 locale = args[0].locale;
1819 }
1820 if (args[0].key) {
1821 key = args[0].key;
1822 }
1823
1824 // Filter out number format options only
1825 options = Object.keys(args[0]).reduce((acc, key) => {
1826 if (numberFormatKeys.includes(key)) {
1827 return Object.assign({}, acc, { [key]: args[0][key] })
1828 }
1829 return acc
1830 }, null);
1831 }
1832 } else if (args.length === 2) {
1833 if (typeof args[0] === 'string') {
1834 key = args[0];
1835 }
1836 if (typeof args[1] === 'string') {
1837 locale = args[1];
1838 }
1839 }
1840
1841 return this._n(value, locale, key, options)
1842 }
1843
1844 _ntp (value, locale, key, options) {
1845 /* istanbul ignore if */
1846 if (!VueI18n.availabilities.numberFormat) {
1847 {
1848 warn('Cannot format to parts a Number value due to not supported Intl.NumberFormat.');
1849 }
1850 return []
1851 }
1852
1853 if (!key) {
1854 const nf = !options ? new Intl.NumberFormat(locale) : new Intl.NumberFormat(locale, options);
1855 return nf.formatToParts(value)
1856 }
1857
1858 const formatter = this._getNumberFormatter(value, locale, this.fallbackLocale, this._getNumberFormats(), key, options);
1859 const ret = formatter && formatter.formatToParts(value);
1860 if (this._isFallbackRoot(ret)) {
1861 if (!this._isSilentTranslationWarn(key)) {
1862 warn(`Fall back to format number to parts of root: key '${key}' .`);
1863 }
1864 /* istanbul ignore if */
1865 if (!this._root) { throw Error('unexpected error') }
1866 return this._root.$i18n._ntp(value, locale, key, options)
1867 } else {
1868 return ret || []
1869 }
1870 }
1871}
1872
1873let availabilities;
1874// $FlowFixMe
1875Object.defineProperty(VueI18n, 'availabilities', {
1876 get () {
1877 if (!availabilities) {
1878 const intlDefined = typeof Intl !== 'undefined';
1879 availabilities = {
1880 dateTimeFormat: intlDefined && typeof Intl.DateTimeFormat !== 'undefined',
1881 numberFormat: intlDefined && typeof Intl.NumberFormat !== 'undefined'
1882 };
1883 }
1884
1885 return availabilities
1886 }
1887});
1888
1889VueI18n.install = install;
1890VueI18n.version = '8.15.1';
1891
1892export default VueI18n;