@intlify/core-base 11.4.7 → 11.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,25 +1,8 @@
1
1
  /*!
2
- * core-base v11.4.7
2
+ * core-base v11.4.9
3
3
  * (c) 2026 kazuya kawaguchi
4
4
  * Released under the MIT License.
5
5
  */
6
- function warn(msg, err) {
7
- if (typeof console !== 'undefined') {
8
- console.warn(`[intlify] ` + msg);
9
- /* istanbul ignore if */
10
- if (err) {
11
- console.warn(err.stack);
12
- }
13
- }
14
- }
15
- const hasWarned = {};
16
- function warnOnce(msg) {
17
- if (!hasWarned[msg]) {
18
- hasWarned[msg] = true;
19
- warn(msg);
20
- }
21
- }
22
-
23
6
  /**
24
7
  * Original Utilities
25
8
  * written by kazuya kawaguchi
@@ -70,6 +53,93 @@ const isEmptyObject = (val) => isPlainObject(val) && Object.keys(val).length ===
70
53
  const assign = Object.assign;
71
54
  const _create = Object.create;
72
55
  const create = (obj = null) => _create(obj);
56
+ const hasOwnProperty = Object.prototype.hasOwnProperty;
57
+ function hasOwn(obj, key) {
58
+ return hasOwnProperty.call(obj, key);
59
+ }
60
+ /* eslint-enable */
61
+ /**
62
+ * Useful Utilities By Evan you
63
+ * Modified by kazuya kawaguchi
64
+ * MIT License
65
+ * https://github.com/vuejs/vue-next/blob/master/packages/shared/src/index.ts
66
+ * https://github.com/vuejs/vue-next/blob/master/packages/shared/src/codeframe.ts
67
+ */
68
+ const isArray = Array.isArray;
69
+ const isFunction = (val) => typeof val === 'function';
70
+ const isString = (val) => typeof val === 'string';
71
+ const isBoolean = (val) => typeof val === 'boolean';
72
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
+ const isObject = (val) => val !== null && typeof val === 'object';
74
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
+ const isPromise = (val) => {
76
+ return isObject(val) && isFunction(val.then) && isFunction(val.catch);
77
+ };
78
+ const objectToString = Object.prototype.toString;
79
+ const toTypeString = (value) => objectToString.call(value);
80
+ const isPlainObject = (val) => toTypeString(val) === '[object Object]';
81
+ // for converting list and named values to displayed strings.
82
+ const toDisplayString = (val) => {
83
+ return val == null
84
+ ? ''
85
+ : isArray(val) || (isPlainObject(val) && val.toString === objectToString)
86
+ ? JSON.stringify(val, null, 2)
87
+ : String(val);
88
+ };
89
+ function join(items, separator = '') {
90
+ return items.reduce((str, item, index) => (index === 0 ? str + item : str + separator + item), '');
91
+ }
92
+ const RANGE = 2;
93
+ function generateCodeFrame(source, start = 0, end = source.length) {
94
+ const lines = source.split(/\r?\n/);
95
+ let count = 0;
96
+ const res = [];
97
+ for (let i = 0; i < lines.length; i++) {
98
+ count += lines[i].length + 1;
99
+ if (count >= start) {
100
+ for (let j = i - RANGE; j <= i + RANGE || end > count; j++) {
101
+ if (j < 0 || j >= lines.length)
102
+ continue;
103
+ const line = j + 1;
104
+ res.push(`${line}${' '.repeat(3 - String(line).length)}| ${lines[j]}`);
105
+ const lineLength = lines[j].length;
106
+ if (j === i) {
107
+ // push underline
108
+ const pad = start - (count - lineLength) + 1;
109
+ const length = Math.max(1, end > count ? lineLength - pad : end - start);
110
+ res.push(` | ` + ' '.repeat(pad) + '^'.repeat(length));
111
+ }
112
+ else if (j > i) {
113
+ if (end > count) {
114
+ const length = Math.max(Math.min(end - count, lineLength), 1);
115
+ res.push(` | ` + '^'.repeat(length));
116
+ }
117
+ count += lineLength + 1;
118
+ }
119
+ }
120
+ break;
121
+ }
122
+ }
123
+ return res.join('\n');
124
+ }
125
+
126
+ function warn(msg, err) {
127
+ if (typeof console !== 'undefined') {
128
+ console.warn(`[intlify] ` + msg);
129
+ /* istanbul ignore if */
130
+ if (err) {
131
+ console.warn(err.stack);
132
+ }
133
+ }
134
+ }
135
+ const hasWarned = {};
136
+ function warnOnce(msg) {
137
+ if (!hasWarned[msg]) {
138
+ hasWarned[msg] = true;
139
+ warn(msg);
140
+ }
141
+ }
142
+
73
143
  function escapeHtml(rawText) {
74
144
  return rawText
75
145
  .replace(/&/g, '&amp;') // escape `&` first to avoid double escaping
@@ -82,16 +152,37 @@ function escapeHtml(rawText) {
82
152
  }
83
153
  function escapeAttributeValue(value) {
84
154
  return value
85
- .replace(/&(?![a-zA-Z0-9#]{2,6};)/g, '&amp;') // escape unescaped `&`
155
+ .replace(/&(?![a-z0-9#]{2,6};)/gi, '&amp;') // escape unescaped `&`
86
156
  .replace(/"/g, '&quot;')
87
157
  .replace(/'/g, '&apos;')
88
158
  .replace(/</g, '&lt;')
89
159
  .replace(/>/g, '&gt;');
90
160
  }
91
- const javascriptSchemePattern = /^\s*javascript\s*(?::|&#0*58;?|&#x0*3a;?|&colon;?)/i;
161
+ const javascriptSchemePattern = /^javascript:/i;
92
162
  const urlAttributePattern = /^(?:href|src|action|formaction)$/i;
163
+ const numericCharacterReferencePattern = /&#(?:x([0-9a-f]+)|(\d+));?/gi;
164
+ const namedWhitespaceCharacterReferencePattern = /&(?:Tab|NewLine);/g;
165
+ const colonCharacterReferencePattern = /&colon;?/gi;
166
+ // eslint-disable-next-line no-control-regex -- URL scheme normalization requires the full control range
167
+ const controlOrWhitespacePattern = /[\u0000-\u0020\u007f-\u009f]/g;
168
+ const eventHandlerPattern = /(?:^|[\s"'<>/])on\w+\s*=\s*["']?[^"'>]+["']?/i;
169
+ const eventHandlerAttributePattern = /(^|[\s"'<>/])on(\w+\s*=)/gi;
170
+ const unquotedUrlAttributePattern = /(^|[\s"'<>/])((?:href|src|action|formaction)\s*=\s*)([^\s"'=<>`]+)/gi;
171
+ function decodeNumericCharacterReference(match, hex, decimal) {
172
+ const digits = hex || decimal;
173
+ if (!digits) {
174
+ return match;
175
+ }
176
+ const codePoint = Number.parseInt(digits, hex ? 16 : 10);
177
+ return codePoint <= 0x7f ? String.fromCharCode(codePoint) : match;
178
+ }
93
179
  function hasJavascriptScheme(value) {
94
- return javascriptSchemePattern.test(value);
180
+ const normalized = value
181
+ .replace(numericCharacterReferencePattern, decodeNumericCharacterReference)
182
+ .replace(namedWhitespaceCharacterReferencePattern, '')
183
+ .replace(colonCharacterReferencePattern, ':')
184
+ .replace(controlOrWhitespacePattern, '');
185
+ return javascriptSchemePattern.test(normalized);
95
186
  }
96
187
  function sanitizeStyleValue(value) {
97
188
  const urlPattern = /url\s*\(/gi;
@@ -155,88 +246,18 @@ function sanitizeTranslatedHtml(html) {
155
246
  // Process attributes with single quotes
156
247
  html = html.replace(/([\w:-]+)\s*=\s*'([^']*)'/g, (_, attrName, attrValue) => `${attrName}='${sanitizeAttributeValue(attrName, attrValue)}'`);
157
248
  // Detect and neutralize event handler attributes
158
- const eventHandlerPattern = /\s*on\w+\s*=\s*["']?[^"'>]+["']?/gi;
159
249
  if (eventHandlerPattern.test(html)) {
160
250
  {
161
251
  warn('Potentially dangerous event handlers detected in translation. ' +
162
252
  'Consider removing onclick, onerror, etc. from your translation messages.');
163
253
  }
164
254
  // Neutralize event handler attributes by escaping 'on'
165
- html = html.replace(/(\s+)(on)(\w+\s*=)/gi, '$1&#111;n$3');
255
+ html = html.replace(eventHandlerAttributePattern, '$1&#111;n$2');
166
256
  }
167
257
  // Disable javascript: URLs in unquoted attributes
168
- html = html.replace(/(\s+(?:href|src|action|formaction)\s*=\s*)([^\s"'=<>`]+)/gi, (match, prefix, attrValue) => hasJavascriptScheme(attrValue) ? `${prefix}about:blank` : match);
258
+ html = html.replace(unquotedUrlAttributePattern, (match, boundary, prefix, attrValue) => hasJavascriptScheme(attrValue) ? `${boundary}${prefix}about:blank` : match);
169
259
  return html;
170
260
  }
171
- const hasOwnProperty = Object.prototype.hasOwnProperty;
172
- function hasOwn(obj, key) {
173
- return hasOwnProperty.call(obj, key);
174
- }
175
- /* eslint-enable */
176
- /**
177
- * Useful Utilities By Evan you
178
- * Modified by kazuya kawaguchi
179
- * MIT License
180
- * https://github.com/vuejs/vue-next/blob/master/packages/shared/src/index.ts
181
- * https://github.com/vuejs/vue-next/blob/master/packages/shared/src/codeframe.ts
182
- */
183
- const isArray = Array.isArray;
184
- const isFunction = (val) => typeof val === 'function';
185
- const isString = (val) => typeof val === 'string';
186
- const isBoolean = (val) => typeof val === 'boolean';
187
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
188
- const isObject = (val) => val !== null && typeof val === 'object';
189
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
190
- const isPromise = (val) => {
191
- return isObject(val) && isFunction(val.then) && isFunction(val.catch);
192
- };
193
- const objectToString = Object.prototype.toString;
194
- const toTypeString = (value) => objectToString.call(value);
195
- const isPlainObject = (val) => toTypeString(val) === '[object Object]';
196
- // for converting list and named values to displayed strings.
197
- const toDisplayString = (val) => {
198
- return val == null
199
- ? ''
200
- : isArray(val) || (isPlainObject(val) && val.toString === objectToString)
201
- ? JSON.stringify(val, null, 2)
202
- : String(val);
203
- };
204
- function join(items, separator = '') {
205
- return items.reduce((str, item, index) => (index === 0 ? str + item : str + separator + item), '');
206
- }
207
- const RANGE = 2;
208
- function generateCodeFrame(source, start = 0, end = source.length) {
209
- const lines = source.split(/\r?\n/);
210
- let count = 0;
211
- const res = [];
212
- for (let i = 0; i < lines.length; i++) {
213
- count += lines[i].length + 1;
214
- if (count >= start) {
215
- for (let j = i - RANGE; j <= i + RANGE || end > count; j++) {
216
- if (j < 0 || j >= lines.length)
217
- continue;
218
- const line = j + 1;
219
- res.push(`${line}${' '.repeat(3 - String(line).length)}| ${lines[j]}`);
220
- const lineLength = lines[j].length;
221
- if (j === i) {
222
- // push underline
223
- const pad = start - (count - lineLength) + 1;
224
- const length = Math.max(1, end > count ? lineLength - pad : end - start);
225
- res.push(` | ` + ' '.repeat(pad) + '^'.repeat(length));
226
- }
227
- else if (j > i) {
228
- if (end > count) {
229
- const length = Math.max(Math.min(end - count, lineLength), 1);
230
- res.push(` | ` + '^'.repeat(length));
231
- }
232
- count += lineLength + 1;
233
- }
234
- }
235
- break;
236
- }
237
- }
238
- return res.join('\n');
239
- }
240
261
 
241
262
  function createPosition(line, column, offset) {
242
263
  return { line, column, offset };
@@ -2085,7 +2106,7 @@ function resolveLocale(locale) {
2085
2106
  * @remarks
2086
2107
  * A fallback locale function implemented with a simple fallback algorithm.
2087
2108
  *
2088
- * Basically, it returns the value as specified in the `fallbackLocale` props, and is processed with the fallback inside intlify.
2109
+ * Basically, the chain consists of `start` plus the specified fallback: for a string or array `fallbackLocale`, the value as specified in the props is used; for a map `fallbackLocale`, the map's **keys** are used and the `default` key is skipped since it is not a locale name.
2089
2110
  *
2090
2111
  * @param ctx - A {@link CoreContext | context}
2091
2112
  * @param fallback - A {@link FallbackLocale | fallback locale}
@@ -2102,7 +2123,7 @@ function fallbackWithSimple(ctx, fallback, start) {
2102
2123
  ...(isArray(fallback)
2103
2124
  ? fallback
2104
2125
  : isObject(fallback)
2105
- ? Object.keys(fallback)
2126
+ ? Object.keys(fallback).filter(locale => locale !== 'default')
2106
2127
  : isString(fallback)
2107
2128
  ? [fallback]
2108
2129
  : [start])
@@ -2130,29 +2151,36 @@ function fallbackWithLocaleChain(ctx, fallback, start) {
2130
2151
  if (!context.__localeChainCache) {
2131
2152
  context.__localeChainCache = new Map();
2132
2153
  }
2133
- let chain = context.__localeChainCache.get(startLocale);
2134
- if (!chain) {
2135
- chain = [];
2136
- // first block defined by start
2137
- let block = [start];
2138
- // while any intervening block found
2139
- while (isArray(block)) {
2140
- block = appendBlockToChain(chain, block, fallback);
2141
- }
2142
- // prettier-ignore
2143
- // last block defined by default
2144
- const defaults = isArray(fallback) || !isPlainObject(fallback)
2145
- ? fallback
2146
- : fallback['default']
2147
- ? fallback['default']
2148
- : null;
2149
- // convert defaults to array
2150
- block = isString(defaults) ? [defaults] : defaults;
2151
- if (isArray(block)) {
2152
- appendBlockToChain(chain, block, false);
2153
- }
2154
- context.__localeChainCache.set(startLocale, chain);
2154
+ const fallbackKey = friendlyJSONstringify(fallback);
2155
+ let chains = context.__localeChainCache.get(startLocale);
2156
+ if (!chains) {
2157
+ chains = new Map();
2158
+ context.__localeChainCache.set(startLocale, chains);
2159
+ }
2160
+ const cached = chains.get(fallbackKey);
2161
+ if (cached) {
2162
+ return cached;
2163
+ }
2164
+ const chain = [];
2165
+ // first block defined by start
2166
+ let block = [start];
2167
+ // while any intervening block found
2168
+ while (isArray(block)) {
2169
+ block = appendBlockToChain(chain, block, fallback);
2170
+ }
2171
+ // prettier-ignore
2172
+ // last block defined by default
2173
+ const defaults = isArray(fallback) || !isPlainObject(fallback)
2174
+ ? fallback
2175
+ : fallback['default']
2176
+ ? fallback['default']
2177
+ : null;
2178
+ // convert defaults to array
2179
+ block = isString(defaults) ? [defaults] : defaults;
2180
+ if (isArray(block)) {
2181
+ appendBlockToChain(chain, block, false);
2155
2182
  }
2183
+ chains.set(fallbackKey, chain);
2156
2184
  return chain;
2157
2185
  }
2158
2186
  function appendBlockToChain(chain, block, blocks) {
@@ -2511,7 +2539,7 @@ function getWarnMessage(code, ...args) {
2511
2539
  * Intlify core-base version
2512
2540
  * @internal
2513
2541
  */
2514
- const VERSION = '11.4.7';
2542
+ const VERSION = '11.4.9';
2515
2543
  const NOT_REOSLVED = -1;
2516
2544
  const DEFAULT_LOCALE = 'en-US';
2517
2545
  const MISSING_RESOLVE_VALUE = '';
@@ -2761,6 +2789,88 @@ function isImplicitFallback(targetLocale, locales) {
2761
2789
  }
2762
2790
  /* eslint-enable @typescript-eslint/no-explicit-any */
2763
2791
 
2792
+ function resolveFormatLocale(context, key, locale, formats, missingWarn, fallbackWarn, type) {
2793
+ const { fallbackLocale, localeFallbacker, onWarn } = context;
2794
+ const locales = localeFallbacker(context, // eslint-disable-line @typescript-eslint/no-explicit-any
2795
+ fallbackLocale, locale);
2796
+ let from = locale;
2797
+ for (let i = 0; i < locales.length; i++) {
2798
+ const targetLocale = locales[i];
2799
+ if (locale !== targetLocale &&
2800
+ isTranslateFallbackWarn(fallbackWarn, key)) {
2801
+ onWarn(getWarnMessage(type === 'datetime format'
2802
+ ? CoreWarnCodes.FALLBACK_TO_DATE_FORMAT
2803
+ : CoreWarnCodes.FALLBACK_TO_NUMBER_FORMAT, {
2804
+ key,
2805
+ target: targetLocale
2806
+ }));
2807
+ }
2808
+ if (locale !== targetLocale) {
2809
+ const emitter = context.__v_emitter;
2810
+ if (emitter) {
2811
+ emitter.emit('fallback', {
2812
+ type,
2813
+ key,
2814
+ from,
2815
+ to: targetLocale,
2816
+ groupId: `${type}:${key}`
2817
+ });
2818
+ }
2819
+ }
2820
+ const format = (formats[targetLocale] || {})[key];
2821
+ if (isPlainObject(format) && isString(targetLocale)) {
2822
+ return targetLocale;
2823
+ }
2824
+ handleMissing(context, key, targetLocale, missingWarn, type);
2825
+ from = targetLocale;
2826
+ }
2827
+ return null;
2828
+ }
2829
+ function getFormatterCacheKey(locale, key, overrides) {
2830
+ let id = `${locale}__${key}`;
2831
+ if (isPlainObject(overrides) && !isEmptyObject(overrides)) {
2832
+ id = `${id}__${JSON.stringify(overrides)}`;
2833
+ }
2834
+ return id;
2835
+ }
2836
+ function clearFormatCache(formatters, locale, format) {
2837
+ for (const key in format) {
2838
+ const prefix = `${locale}__${key}`;
2839
+ for (const id of formatters.keys()) {
2840
+ if (id === prefix || id.startsWith(`${prefix}__`)) {
2841
+ formatters.delete(id);
2842
+ }
2843
+ }
2844
+ }
2845
+ }
2846
+ function parseFormatArgs(args, options, initialOverrides, optionsKeys) {
2847
+ const [, arg2, arg3, arg4] = args;
2848
+ let overrides = initialOverrides;
2849
+ if (isString(arg2)) {
2850
+ options.key = arg2;
2851
+ }
2852
+ else if (isPlainObject(arg2)) {
2853
+ Object.keys(arg2).forEach(key => {
2854
+ if (optionsKeys.includes(key)) {
2855
+ overrides[key] = arg2[key];
2856
+ }
2857
+ else {
2858
+ options[key] = arg2[key];
2859
+ }
2860
+ });
2861
+ }
2862
+ if (isString(arg3)) {
2863
+ options.locale = arg3;
2864
+ }
2865
+ else if (isPlainObject(arg3)) {
2866
+ overrides = arg3;
2867
+ }
2868
+ if (isPlainObject(arg4)) {
2869
+ overrides = arg4;
2870
+ }
2871
+ return overrides;
2872
+ }
2873
+
2764
2874
  const intlDefined = typeof Intl !== 'undefined';
2765
2875
  const Availabilities = {
2766
2876
  dateTimeFormat: intlDefined && typeof Intl.DateTimeFormat !== 'undefined',
@@ -2769,7 +2879,7 @@ const Availabilities = {
2769
2879
 
2770
2880
  // implementation of `datetime` function
2771
2881
  function datetime(context, ...args) {
2772
- const { datetimeFormats, unresolving, fallbackLocale, onWarn, localeFallbacker } = context;
2882
+ const { datetimeFormats, unresolving, onWarn } = context;
2773
2883
  const { __datetimeFormatters } = context;
2774
2884
  if (!Availabilities.dateTimeFormat) {
2775
2885
  onWarn(getWarnMessage(CoreWarnCodes.CANNOT_FORMAT_DATE));
@@ -2792,57 +2902,16 @@ function datetime(context, ...args) {
2792
2902
  : context.fallbackWarn;
2793
2903
  const part = !!options.part;
2794
2904
  const locale = getLocale(context, options);
2795
- const locales = localeFallbacker(context, // eslint-disable-line @typescript-eslint/no-explicit-any
2796
- fallbackLocale, locale);
2797
2905
  if (!isString(key) || key === '') {
2798
2906
  const formatter = new Intl.DateTimeFormat(locale.replace(/!/g, ''), overrides);
2799
2907
  return !part ? formatter.format(value) : formatter.formatToParts(value);
2800
2908
  }
2801
- // resolve format
2802
- let datetimeFormat = {};
2803
- let targetLocale;
2804
- let format = null;
2805
- let from = locale;
2806
- let to = null;
2807
- const type = 'datetime format';
2808
- for (let i = 0; i < locales.length; i++) {
2809
- targetLocale = to = locales[i];
2810
- if (locale !== targetLocale &&
2811
- isTranslateFallbackWarn(fallbackWarn, key)) {
2812
- onWarn(getWarnMessage(CoreWarnCodes.FALLBACK_TO_DATE_FORMAT, {
2813
- key,
2814
- target: targetLocale
2815
- }));
2816
- }
2817
- // for vue-devtools timeline event
2818
- if (locale !== targetLocale) {
2819
- const emitter = context.__v_emitter;
2820
- if (emitter) {
2821
- emitter.emit('fallback', {
2822
- type,
2823
- key,
2824
- from,
2825
- to,
2826
- groupId: `${type}:${key}`
2827
- });
2828
- }
2829
- }
2830
- datetimeFormat =
2831
- datetimeFormats[targetLocale] || {};
2832
- format = datetimeFormat[key];
2833
- if (isPlainObject(format))
2834
- break;
2835
- handleMissing(context, key, targetLocale, missingWarn, type); // eslint-disable-line @typescript-eslint/no-explicit-any
2836
- from = to;
2837
- }
2838
- // checking format and target locale
2839
- if (!isPlainObject(format) || !isString(targetLocale)) {
2909
+ const targetLocale = resolveFormatLocale(context, key, locale, datetimeFormats, missingWarn, fallbackWarn, 'datetime format');
2910
+ if (!isString(targetLocale)) {
2840
2911
  return unresolving ? NOT_REOSLVED : key;
2841
2912
  }
2842
- let id = `${targetLocale}__${key}`;
2843
- if (!isEmptyObject(overrides)) {
2844
- id = `${id}__${JSON.stringify(overrides)}`;
2845
- }
2913
+ const format = datetimeFormats[targetLocale][key];
2914
+ const id = getFormatterCacheKey(targetLocale, key, overrides);
2846
2915
  let formatter = __datetimeFormatters.get(id);
2847
2916
  if (!formatter) {
2848
2917
  formatter = new Intl.DateTimeFormat(targetLocale, assign({}, format, overrides));
@@ -2875,9 +2944,9 @@ const DATETIME_FORMAT_OPTIONS_KEYS = [
2875
2944
  ];
2876
2945
  /** @internal */
2877
2946
  function parseDateTimeArgs(...args) {
2878
- const [arg1, arg2, arg3, arg4] = args;
2947
+ const [arg1] = args;
2879
2948
  const options = create();
2880
- let overrides = create();
2949
+ const initialOverrides = create();
2881
2950
  let value;
2882
2951
  if (isString(arg1)) {
2883
2952
  // Only allow ISO strings - other date formats are often supported,
@@ -2914,45 +2983,18 @@ function parseDateTimeArgs(...args) {
2914
2983
  else {
2915
2984
  throw createCoreError(CoreErrorCodes.INVALID_ARGUMENT);
2916
2985
  }
2917
- if (isString(arg2)) {
2918
- options.key = arg2;
2919
- }
2920
- else if (isPlainObject(arg2)) {
2921
- Object.keys(arg2).forEach(key => {
2922
- if (DATETIME_FORMAT_OPTIONS_KEYS.includes(key)) {
2923
- overrides[key] = arg2[key];
2924
- }
2925
- else {
2926
- options[key] = arg2[key];
2927
- }
2928
- });
2929
- }
2930
- if (isString(arg3)) {
2931
- options.locale = arg3;
2932
- }
2933
- else if (isPlainObject(arg3)) {
2934
- overrides = arg3;
2935
- }
2936
- if (isPlainObject(arg4)) {
2937
- overrides = arg4;
2938
- }
2986
+ const overrides = parseFormatArgs(args, options, initialOverrides, DATETIME_FORMAT_OPTIONS_KEYS);
2939
2987
  return [options.key || '', value, options, overrides];
2940
2988
  }
2941
2989
  /** @internal */
2942
2990
  function clearDateTimeFormat(ctx, locale, format) {
2943
2991
  const context = ctx;
2944
- for (const key in format) {
2945
- const id = `${locale}__${key}`;
2946
- if (!context.__datetimeFormatters.has(id)) {
2947
- continue;
2948
- }
2949
- context.__datetimeFormatters.delete(id);
2950
- }
2992
+ clearFormatCache(context.__datetimeFormatters, locale, format);
2951
2993
  }
2952
2994
 
2953
2995
  // implementation of `number` function
2954
2996
  function number(context, ...args) {
2955
- const { numberFormats, unresolving, fallbackLocale, onWarn, localeFallbacker } = context;
2997
+ const { numberFormats, unresolving, onWarn } = context;
2956
2998
  const { __numberFormatters } = context;
2957
2999
  if (!Availabilities.numberFormat) {
2958
3000
  onWarn(getWarnMessage(CoreWarnCodes.CANNOT_FORMAT_NUMBER));
@@ -2975,57 +3017,16 @@ function number(context, ...args) {
2975
3017
  : context.fallbackWarn;
2976
3018
  const part = !!options.part;
2977
3019
  const locale = getLocale(context, options);
2978
- const locales = localeFallbacker(context, // eslint-disable-line @typescript-eslint/no-explicit-any
2979
- fallbackLocale, locale);
2980
3020
  if (!isString(key) || key === '') {
2981
3021
  const formatter = new Intl.NumberFormat(locale.replace(/!/g, ''), overrides);
2982
3022
  return !part ? formatter.format(value) : formatter.formatToParts(value);
2983
3023
  }
2984
- // resolve format
2985
- let numberFormat = {};
2986
- let targetLocale;
2987
- let format = null;
2988
- let from = locale;
2989
- let to = null;
2990
- const type = 'number format';
2991
- for (let i = 0; i < locales.length; i++) {
2992
- targetLocale = to = locales[i];
2993
- if (locale !== targetLocale &&
2994
- isTranslateFallbackWarn(fallbackWarn, key)) {
2995
- onWarn(getWarnMessage(CoreWarnCodes.FALLBACK_TO_NUMBER_FORMAT, {
2996
- key,
2997
- target: targetLocale
2998
- }));
2999
- }
3000
- // for vue-devtools timeline event
3001
- if (locale !== targetLocale) {
3002
- const emitter = context.__v_emitter;
3003
- if (emitter) {
3004
- emitter.emit('fallback', {
3005
- type,
3006
- key,
3007
- from,
3008
- to,
3009
- groupId: `${type}:${key}`
3010
- });
3011
- }
3012
- }
3013
- numberFormat =
3014
- numberFormats[targetLocale] || {};
3015
- format = numberFormat[key];
3016
- if (isPlainObject(format))
3017
- break;
3018
- handleMissing(context, key, targetLocale, missingWarn, type); // eslint-disable-line @typescript-eslint/no-explicit-any
3019
- from = to;
3020
- }
3021
- // checking format and target locale
3022
- if (!isPlainObject(format) || !isString(targetLocale)) {
3024
+ const targetLocale = resolveFormatLocale(context, key, locale, numberFormats, missingWarn, fallbackWarn, 'number format');
3025
+ if (!isString(targetLocale)) {
3023
3026
  return unresolving ? NOT_REOSLVED : key;
3024
3027
  }
3025
- let id = `${targetLocale}__${key}`;
3026
- if (!isEmptyObject(overrides)) {
3027
- id = `${id}__${JSON.stringify(overrides)}`;
3028
- }
3028
+ const format = numberFormats[targetLocale][key];
3029
+ const id = getFormatterCacheKey(targetLocale, key, overrides);
3029
3030
  let formatter = __numberFormatters.get(id);
3030
3031
  if (!formatter) {
3031
3032
  formatter = new Intl.NumberFormat(targetLocale, assign({}, format, overrides));
@@ -3058,47 +3059,20 @@ const NUMBER_FORMAT_OPTIONS_KEYS = [
3058
3059
  ];
3059
3060
  /** @internal */
3060
3061
  function parseNumberArgs(...args) {
3061
- const [arg1, arg2, arg3, arg4] = args;
3062
+ const [arg1] = args;
3062
3063
  const options = create();
3063
- let overrides = create();
3064
+ const initialOverrides = create();
3064
3065
  if (!isNumber(arg1)) {
3065
3066
  throw createCoreError(CoreErrorCodes.INVALID_ARGUMENT);
3066
3067
  }
3067
3068
  const value = arg1;
3068
- if (isString(arg2)) {
3069
- options.key = arg2;
3070
- }
3071
- else if (isPlainObject(arg2)) {
3072
- Object.keys(arg2).forEach(key => {
3073
- if (NUMBER_FORMAT_OPTIONS_KEYS.includes(key)) {
3074
- overrides[key] = arg2[key];
3075
- }
3076
- else {
3077
- options[key] = arg2[key];
3078
- }
3079
- });
3080
- }
3081
- if (isString(arg3)) {
3082
- options.locale = arg3;
3083
- }
3084
- else if (isPlainObject(arg3)) {
3085
- overrides = arg3;
3086
- }
3087
- if (isPlainObject(arg4)) {
3088
- overrides = arg4;
3089
- }
3069
+ const overrides = parseFormatArgs(args, options, initialOverrides, NUMBER_FORMAT_OPTIONS_KEYS);
3090
3070
  return [options.key || '', value, options, overrides];
3091
3071
  }
3092
3072
  /** @internal */
3093
3073
  function clearNumberFormat(ctx, locale, format) {
3094
3074
  const context = ctx;
3095
- for (const key in format) {
3096
- const id = `${locale}__${key}`;
3097
- if (!context.__numberFormatters.has(id)) {
3098
- continue;
3099
- }
3100
- context.__numberFormatters.delete(id);
3101
- }
3075
+ clearFormatCache(context.__numberFormatters, locale, format);
3102
3076
  }
3103
3077
 
3104
3078
  const DEFAULT_MODIFIER = (str) => str;