i18next-cli 1.63.1 → 1.64.1
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.
- package/README.md +5 -2
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/core/translation-manager.js +5 -2
- package/dist/cjs/extractor/parsers/call-expression-handler.js +14 -16
- package/dist/cjs/extractor/parsers/comment-parser.js +7 -3
- package/dist/cjs/extractor/parsers/jsx-handler.js +5 -1
- package/dist/cjs/extractor/utils/function-matcher.js +36 -0
- package/dist/cjs/linter.js +44 -13
- package/dist/cjs/rename-key.js +4 -0
- package/dist/cjs/status.js +123 -21
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/core/translation-manager.js +5 -2
- package/dist/esm/extractor/parsers/call-expression-handler.js +14 -16
- package/dist/esm/extractor/parsers/comment-parser.js +7 -3
- package/dist/esm/extractor/parsers/jsx-handler.js +5 -1
- package/dist/esm/extractor/utils/function-matcher.js +34 -0
- package/dist/esm/linter.js +44 -13
- package/dist/esm/rename-key.js +4 -0
- package/dist/esm/status.js +123 -21
- package/package.json +1 -1
- package/types/extractor/core/translation-manager.d.ts.map +1 -1
- package/types/extractor/parsers/call-expression-handler.d.ts.map +1 -1
- package/types/extractor/parsers/comment-parser.d.ts.map +1 -1
- package/types/extractor/parsers/jsx-handler.d.ts.map +1 -1
- package/types/extractor/utils/function-matcher.d.ts +24 -0
- package/types/extractor/utils/function-matcher.d.ts.map +1 -0
- package/types/linter.d.ts.map +1 -1
- package/types/status.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -773,8 +773,11 @@ export default defineConfig({
|
|
|
773
773
|
mergeNamespaces: false,
|
|
774
774
|
|
|
775
775
|
// Translation functions to detect. Defaults to ['t', '*.t'].
|
|
776
|
-
// Supports
|
|
777
|
-
|
|
776
|
+
// Supports a leading wildcard to match any object (suffix match), e.g.
|
|
777
|
+
// '*.t' matches `i18n.t` / `this._i18n.t`, and a trailing wildcard to match
|
|
778
|
+
// any method on an object (prefix match), e.g. 'tProps.*' matches
|
|
779
|
+
// `tProps.label` / `tProps.title`.
|
|
780
|
+
functions: ['t', '*.t', 'i18next.t', 'tProps.*'],
|
|
778
781
|
|
|
779
782
|
// React components to analyze
|
|
780
783
|
transComponents: ['Trans', 'Translation'],
|
package/dist/cjs/cli.js
CHANGED
|
@@ -37,7 +37,7 @@ const program = new commander.Command();
|
|
|
37
37
|
program
|
|
38
38
|
.name('i18next-cli')
|
|
39
39
|
.description('A unified, high-performance i18next CLI.')
|
|
40
|
-
.version('1.
|
|
40
|
+
.version('1.64.1'); // This string is replaced with the actual version at build time by rollup
|
|
41
41
|
// new: global config override option
|
|
42
42
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
|
|
43
43
|
program
|
|
@@ -394,8 +394,11 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
|
|
|
394
394
|
if (shouldFilterKey(key)) {
|
|
395
395
|
return false;
|
|
396
396
|
}
|
|
397
|
-
if (!hasCount) {
|
|
398
|
-
// Non-plural keys are always included
|
|
397
|
+
if (!hasCount || config.extract.disablePlurals) {
|
|
398
|
+
// Non-plural keys are always included. Under `disablePlurals`, count keys
|
|
399
|
+
// are emitted as plain keys (no plural expansion) but still carry
|
|
400
|
+
// `hasCount` so `status` can recognise them — they must bypass the
|
|
401
|
+
// plural-form filtering below and always be included, exactly as before.
|
|
399
402
|
return true;
|
|
400
403
|
}
|
|
401
404
|
// For plural keys, check if this specific plural form is needed for the target language
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var pluralRules = require('../../utils/plural-rules.js');
|
|
4
4
|
var nesting = require('../../utils/nesting.js');
|
|
5
5
|
var astUtils = require('./ast-utils.js');
|
|
6
|
+
var functionMatcher = require('../utils/function-matcher.js');
|
|
6
7
|
|
|
7
8
|
// Helper to escape regex characters
|
|
8
9
|
const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -72,19 +73,11 @@ class CallExpressionHandler {
|
|
|
72
73
|
let isFunctionToParse = scopeInfo !== undefined; // A scoped variable (from useTranslation, etc.) is always parsed.
|
|
73
74
|
if (!isFunctionToParse) {
|
|
74
75
|
for (const pattern of configuredFunctions) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
// Handle exact match
|
|
84
|
-
if (pattern === functionName) {
|
|
85
|
-
isFunctionToParse = true;
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
76
|
+
// Supports exact ('t', 'i18next.t'), prefix wildcards ('*.t' -> 'i18n.t')
|
|
77
|
+
// and suffix wildcards ('tProps.*' -> 'tProps.label').
|
|
78
|
+
if (functionMatcher.matchesFunctionPattern(functionName, pattern)) {
|
|
79
|
+
isFunctionToParse = true;
|
|
80
|
+
break;
|
|
88
81
|
}
|
|
89
82
|
}
|
|
90
83
|
}
|
|
@@ -344,12 +337,17 @@ class CallExpressionHandler {
|
|
|
344
337
|
// Check if plurals are disabled FIRST, before any plural optimization paths
|
|
345
338
|
if (this.config.extract.disablePlurals) {
|
|
346
339
|
// When plurals are disabled, treat count as a regular option (for interpolation only)
|
|
347
|
-
// Still handle context normally
|
|
340
|
+
// Still handle context normally. We keep `hasCount`/`isOrdinal` on the
|
|
341
|
+
// emitted key so `status` knows it is count-driven and can accept the
|
|
342
|
+
// file's plural variants (or the bare key) instead of demanding the
|
|
343
|
+
// literal base key. File generation ignores these flags under
|
|
344
|
+
// disablePlurals (see translation-manager), so output is unchanged.
|
|
345
|
+
const countMeta = hasCount ? { hasCount: true, isOrdinal: isOrdinalByKey } : {};
|
|
348
346
|
if (keysWithContext.length > 0) {
|
|
349
|
-
keysWithContext.forEach(this.pluginContext.addKey);
|
|
347
|
+
keysWithContext.forEach(k => this.pluginContext.addKey({ ...k, ...countMeta }));
|
|
350
348
|
}
|
|
351
349
|
else {
|
|
352
|
-
this.pluginContext.addKey({ key: finalKey, ns, defaultValue: dv, explicitDefault: explicitDefaultForBase });
|
|
350
|
+
this.pluginContext.addKey({ key: finalKey, ns, defaultValue: dv, explicitDefault: explicitDefaultForBase, ...countMeta });
|
|
353
351
|
}
|
|
354
352
|
continue; // This key is fully handled
|
|
355
353
|
}
|
|
@@ -124,14 +124,18 @@ function extractKeysFromComments(code, pluginContext, config, scopeResolver) {
|
|
|
124
124
|
ns = config.extract.defaultNS;
|
|
125
125
|
// 5. Handle context and count combinations based on disablePlurals setting
|
|
126
126
|
if (config.extract.disablePlurals) {
|
|
127
|
-
// When plurals are disabled, ignore count for key generation
|
|
127
|
+
// When plurals are disabled, ignore count for key generation. We still
|
|
128
|
+
// tag the key with `hasCount` (when a count was present) so `status` can
|
|
129
|
+
// recognise it as count-driven; file generation ignores the flag under
|
|
130
|
+
// disablePlurals (see translation-manager).
|
|
131
|
+
const countMeta = count ? { hasCount: true, isOrdinal } : {};
|
|
128
132
|
if (context) {
|
|
129
133
|
// Only generate context variants (no base key when context is static)
|
|
130
|
-
pluginContext.addKey({ key: `${key}_${context}`, ns, defaultValue: defaultValue ?? key });
|
|
134
|
+
pluginContext.addKey({ key: `${key}_${context}`, ns, defaultValue: defaultValue ?? key, ...countMeta });
|
|
131
135
|
}
|
|
132
136
|
else {
|
|
133
137
|
// Simple key (ignore count)
|
|
134
|
-
pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key });
|
|
138
|
+
pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key, ...countMeta });
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
else {
|
|
@@ -369,12 +369,16 @@ class JSXHandler {
|
|
|
369
369
|
else if (hasCount) {
|
|
370
370
|
// Check if plurals are disabled
|
|
371
371
|
if (this.config.extract.disablePlurals) {
|
|
372
|
-
// When plurals are disabled, just add the base keys (no plural forms)
|
|
372
|
+
// When plurals are disabled, just add the base keys (no plural forms).
|
|
373
|
+
// We keep `hasCount` so `status` recognises the key as count-driven and
|
|
374
|
+
// accepts the file's plural variants (or bare key); file generation
|
|
375
|
+
// ignores it under disablePlurals (see translation-manager).
|
|
373
376
|
extractedKeys.forEach(extractedKey => {
|
|
374
377
|
this.pluginContext.addKey({
|
|
375
378
|
key: extractedKey.key,
|
|
376
379
|
ns: extractedKey.ns,
|
|
377
380
|
defaultValue: extractedKey.defaultValue,
|
|
381
|
+
hasCount: true,
|
|
378
382
|
locations: extractedKey.locations
|
|
379
383
|
});
|
|
380
384
|
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks whether a (possibly dotted) function name matches a configured
|
|
5
|
+
* `functions` pattern.
|
|
6
|
+
*
|
|
7
|
+
* Supported pattern forms:
|
|
8
|
+
* - Exact match: `'t'` matches `t`, `'i18next.t'` matches `i18next.t`.
|
|
9
|
+
* - Prefix wildcard: `'*.t'` matches any call ending in `.t`
|
|
10
|
+
* (e.g. `i18n.t`, `this._i18n.t`).
|
|
11
|
+
* - Suffix wildcard: `'tProps.*'` matches any single-segment member call on the
|
|
12
|
+
* prefix (e.g. `tProps.label`, `tProps.title`) but not deeper nesting like
|
|
13
|
+
* `tProps.label.t`.
|
|
14
|
+
*
|
|
15
|
+
* @param functionName - The dotted callee name (e.g. `tProps.label`).
|
|
16
|
+
* @param pattern - A single configured pattern from `extract.functions`.
|
|
17
|
+
*/
|
|
18
|
+
function matchesFunctionPattern(functionName, pattern) {
|
|
19
|
+
if (pattern === functionName)
|
|
20
|
+
return true;
|
|
21
|
+
// Prefix wildcard, e.g. '*.t' -> matches any callee ending in '.t'
|
|
22
|
+
if (pattern.startsWith('*.')) {
|
|
23
|
+
return functionName.endsWith(pattern.slice(1));
|
|
24
|
+
}
|
|
25
|
+
// Suffix wildcard, e.g. 'tProps.*' -> matches 'tProps.<segment>'
|
|
26
|
+
if (pattern.endsWith('.*')) {
|
|
27
|
+
const prefix = pattern.slice(0, -1); // keep the trailing dot: 'tProps.'
|
|
28
|
+
if (!functionName.startsWith(prefix))
|
|
29
|
+
return false;
|
|
30
|
+
const rest = functionName.slice(prefix.length);
|
|
31
|
+
return rest.length > 0 && !rest.includes('.');
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
exports.matchesFunctionPattern = matchesFunctionPattern;
|
package/dist/cjs/linter.js
CHANGED
|
@@ -10,6 +10,7 @@ var logger = require('./utils/logger.js');
|
|
|
10
10
|
var wrapOra = require('./utils/wrap-ora.js');
|
|
11
11
|
var jsxAttributes = require('./utils/jsx-attributes.js');
|
|
12
12
|
var astUtils = require('./extractor/parsers/ast-utils.js');
|
|
13
|
+
var functionMatcher = require('./extractor/utils/function-matcher.js');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Loads all translation values from the primary locale's JSON files and returns
|
|
@@ -141,28 +142,58 @@ function lintInterpolationParams(ast, code, config, translationValues) {
|
|
|
141
142
|
}
|
|
142
143
|
// Modularized CallExpression handler
|
|
143
144
|
function handleCallExpression(node, ancestors) {
|
|
145
|
+
// Build the full dotted callee name (e.g. 'i18n.t', 'tProps.label') as well as
|
|
146
|
+
// the bare last segment ('t', 'label') so both exact and wildcard patterns match.
|
|
144
147
|
let calleeName = '';
|
|
148
|
+
let fullCalleeName = '';
|
|
145
149
|
if (node.callee) {
|
|
146
|
-
if (node.callee.type === 'Identifier'
|
|
147
|
-
calleeName = node.callee.value;
|
|
150
|
+
if (node.callee.type === 'Identifier') {
|
|
151
|
+
calleeName = node.callee.value || node.callee.name || '';
|
|
152
|
+
fullCalleeName = calleeName;
|
|
148
153
|
}
|
|
149
|
-
else if (node.callee.type === '
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
+
else if (node.callee.type === 'MemberExpression') {
|
|
155
|
+
const parts = [];
|
|
156
|
+
let current = node.callee;
|
|
157
|
+
let computed = false;
|
|
158
|
+
while (current?.type === 'MemberExpression') {
|
|
159
|
+
if (current.property?.type === 'Identifier') {
|
|
160
|
+
parts.unshift(current.property.value || current.property.name);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
computed = true;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
current = current.object;
|
|
167
|
+
}
|
|
168
|
+
if (!computed) {
|
|
169
|
+
if (current?.type === 'ThisExpression') {
|
|
170
|
+
parts.unshift('this');
|
|
171
|
+
}
|
|
172
|
+
else if (current?.type === 'Identifier') {
|
|
173
|
+
parts.unshift(current.value || current.name);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
parts.length = 0;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (node.callee.property?.type === 'Identifier') {
|
|
180
|
+
calleeName = node.callee.property.value || node.callee.property.name;
|
|
181
|
+
}
|
|
182
|
+
fullCalleeName = parts.length ? parts.join('.') : calleeName;
|
|
154
183
|
}
|
|
155
184
|
}
|
|
156
185
|
const fnPatterns = config.extract.functions || ['t', '*.t'];
|
|
157
186
|
let matches = false;
|
|
158
187
|
for (const pattern of fnPatterns) {
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
188
|
+
if (functionMatcher.matchesFunctionPattern(fullCalleeName, pattern)) {
|
|
189
|
+
matches = true;
|
|
190
|
+
break;
|
|
162
191
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
192
|
+
// Backwards-compatible: '*.X' also matches a call whose last segment is X
|
|
193
|
+
// (covers computed member expressions where the full chain is unavailable).
|
|
194
|
+
if (pattern.startsWith('*.') && calleeName === pattern.slice(2)) {
|
|
195
|
+
matches = true;
|
|
196
|
+
break;
|
|
166
197
|
}
|
|
167
198
|
}
|
|
168
199
|
if (matches) {
|
package/dist/cjs/rename-key.js
CHANGED
|
@@ -297,6 +297,10 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
|
|
|
297
297
|
const suffix = fnPattern.slice(2);
|
|
298
298
|
return `\\b[\\w$]+\\.${escapeRegex(suffix)}`;
|
|
299
299
|
}
|
|
300
|
+
if (fnPattern.endsWith('.*')) {
|
|
301
|
+
const prefix = fnPattern.slice(0, -2);
|
|
302
|
+
return `\\b${escapeRegex(prefix)}\\.[\\w$]+`;
|
|
303
|
+
}
|
|
300
304
|
return `\\b${escapeRegex(fnPattern)}`;
|
|
301
305
|
};
|
|
302
306
|
// Helper: check whether the old key exists in a given namespace (from the prebuilt map)
|
package/dist/cjs/status.js
CHANGED
|
@@ -26,6 +26,53 @@ function classifyValue(value) {
|
|
|
26
26
|
return 'empty';
|
|
27
27
|
return 'translated';
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Representative counts used to decide which CLDR plural categories a locale can
|
|
31
|
+
* actually reach in normal usage: small integers (the common case), a handful
|
|
32
|
+
* of larger integers, and common decimals (so categories that only fire for
|
|
33
|
+
* fractional display values — e.g. Polish/Russian `other` — stay required).
|
|
34
|
+
*
|
|
35
|
+
* Any category NOT produced by these counts is treated as "optional". The prime
|
|
36
|
+
* example is French `many`, which `Intl.PluralRules` only selects for values
|
|
37
|
+
* ≥ 1,000,000 — the i18next runtime can technically request it, but real apps
|
|
38
|
+
* almost never hit those values (and the runtime falls back to the base key
|
|
39
|
+
* when the variant is missing), so a missing `_many` should be a soft note
|
|
40
|
+
* rather than a hard "missing key" failure.
|
|
41
|
+
*/
|
|
42
|
+
const REPRESENTATIVE_COUNTS = (() => {
|
|
43
|
+
const counts = [];
|
|
44
|
+
for (let n = 0; n <= 20; n++)
|
|
45
|
+
counts.push(n);
|
|
46
|
+
counts.push(100, 101, 1000, 1001, 10000);
|
|
47
|
+
counts.push(0.5, 1.1, 1.5, 2.5, 3.5);
|
|
48
|
+
return counts;
|
|
49
|
+
})();
|
|
50
|
+
const optionalCategoriesCache = new Map();
|
|
51
|
+
/**
|
|
52
|
+
* Returns the CLDR plural categories for a locale that are NOT reachable by any
|
|
53
|
+
* representative count (see {@link REPRESENTATIVE_COUNTS}). These are reported
|
|
54
|
+
* by `status` as optional: a missing variant is a soft note instead of a hard
|
|
55
|
+
* absence, mirroring how the i18next runtime resolves such forms.
|
|
56
|
+
*/
|
|
57
|
+
function getOptionalPluralCategories(locale, isOrdinal) {
|
|
58
|
+
const type = isOrdinal ? 'ordinal' : 'cardinal';
|
|
59
|
+
const cacheKey = `${locale}|${type}`;
|
|
60
|
+
const cached = optionalCategoriesCache.get(cacheKey);
|
|
61
|
+
if (cached)
|
|
62
|
+
return cached;
|
|
63
|
+
let optional;
|
|
64
|
+
try {
|
|
65
|
+
const rules = pluralRules.safePluralRules(locale, { type });
|
|
66
|
+
const all = rules.resolvedOptions().pluralCategories;
|
|
67
|
+
const reachable = new Set(REPRESENTATIVE_COUNTS.map(n => rules.select(n)));
|
|
68
|
+
optional = new Set(all.filter(c => !reachable.has(c)));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
optional = new Set();
|
|
72
|
+
}
|
|
73
|
+
optionalCategoriesCache.set(cacheKey, optional);
|
|
74
|
+
return optional;
|
|
75
|
+
}
|
|
29
76
|
/**
|
|
30
77
|
* Runs a health check on the project's i18next translations and displays a status report.
|
|
31
78
|
*
|
|
@@ -222,6 +269,7 @@ async function generateStatusReport(config) {
|
|
|
222
269
|
let totalTranslatedForLocale = 0;
|
|
223
270
|
let totalEmptyForLocale = 0;
|
|
224
271
|
let totalAbsentForLocale = 0;
|
|
272
|
+
let totalOptionalForLocale = 0;
|
|
225
273
|
let totalKeysForLocale = 0;
|
|
226
274
|
const namespaces = new Map();
|
|
227
275
|
const mergedTranslations = mergeNamespaces
|
|
@@ -250,6 +298,7 @@ async function generateStatusReport(config) {
|
|
|
250
298
|
let translatedInNs = 0;
|
|
251
299
|
let emptyInNs = 0;
|
|
252
300
|
let absentInNs = 0;
|
|
301
|
+
let optionalInNs = 0;
|
|
253
302
|
let totalInNs = 0;
|
|
254
303
|
const keyDetails = [];
|
|
255
304
|
// Get the plural categories for THIS specific locale
|
|
@@ -314,37 +363,85 @@ async function generateStatusReport(config) {
|
|
|
314
363
|
// Only count this key if it's a plural form used by this locale
|
|
315
364
|
if (localePluralCategories.includes(category) && !processedKeys.has(baseKey)) {
|
|
316
365
|
processedKeys.add(baseKey);
|
|
317
|
-
totalInNs++;
|
|
318
366
|
const state = resolveAndClassify(baseKey);
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
367
|
+
const optionalCategories = getOptionalPluralCategories(locale, isOrdinalVariant);
|
|
368
|
+
// An optional category (e.g. French `_many`) is a soft note whenever
|
|
369
|
+
// it isn't translated — whether absent or an empty placeholder that
|
|
370
|
+
// `extract` wrote. It is never a hard failure. See #270 and
|
|
371
|
+
// getOptionalPluralCategories.
|
|
372
|
+
if (state !== 'translated' && optionalCategories.has(category)) {
|
|
373
|
+
optionalInNs++;
|
|
374
|
+
keyDetails.push({ key: baseKey, state: 'optional' });
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
totalInNs++;
|
|
378
|
+
if (state === 'translated')
|
|
379
|
+
translatedInNs++;
|
|
380
|
+
else if (state === 'empty')
|
|
381
|
+
emptyInNs++;
|
|
382
|
+
else
|
|
383
|
+
absentInNs++;
|
|
384
|
+
keyDetails.push({ key: baseKey, state });
|
|
385
|
+
}
|
|
326
386
|
}
|
|
327
387
|
}
|
|
328
388
|
else {
|
|
329
|
-
// This is a base plural key without expanded variants
|
|
330
|
-
//
|
|
389
|
+
// This is a base plural key without expanded variants. Mirror the
|
|
390
|
+
// i18next runtime, where t(key, { count }) resolves `key + suffix`
|
|
391
|
+
// and falls back to the bare `key`. A family is therefore satisfied
|
|
392
|
+
// either by its plural variants OR by a bare key (the convention
|
|
393
|
+
// used when `disablePlurals` is enabled and no variants are written).
|
|
331
394
|
const localePluralCategories = getLocalePluralCategories(locale, isOrdinal || false);
|
|
332
|
-
|
|
333
|
-
|
|
395
|
+
const optionalCategories = getOptionalPluralCategories(locale, isOrdinal || false);
|
|
396
|
+
const variants = localePluralCategories.map(category => ({
|
|
397
|
+
category,
|
|
398
|
+
pluralKey: isOrdinal
|
|
334
399
|
? `${baseKey}${pluralSeparator}ordinal${pluralSeparator}${category}`
|
|
335
|
-
: `${baseKey}${pluralSeparator}${category}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
400
|
+
: `${baseKey}${pluralSeparator}${category}`,
|
|
401
|
+
}));
|
|
402
|
+
const anyVariantPresent = variants.some(({ pluralKey }) => resolveAndClassify(pluralKey) !== 'absent');
|
|
403
|
+
const bareState = resolveAndClassify(baseKey);
|
|
404
|
+
if (!anyVariantPresent && bareState !== 'absent' && !processedKeys.has(baseKey)) {
|
|
405
|
+
// Convention (a): only the bare key exists (typical under
|
|
406
|
+
// disablePlurals, or single-"other" languages). The runtime
|
|
407
|
+
// resolves count via the bare key, so it satisfies the family on
|
|
408
|
+
// its own — don't demand plural variants that were never written.
|
|
409
|
+
processedKeys.add(baseKey);
|
|
339
410
|
totalInNs++;
|
|
340
|
-
|
|
341
|
-
if (state === 'translated')
|
|
411
|
+
if (bareState === 'translated')
|
|
342
412
|
translatedInNs++;
|
|
343
|
-
else if (
|
|
413
|
+
else if (bareState === 'empty')
|
|
344
414
|
emptyInNs++;
|
|
345
415
|
else
|
|
346
416
|
absentInNs++;
|
|
347
|
-
keyDetails.push({ key:
|
|
417
|
+
keyDetails.push({ key: baseKey, state: bareState });
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
// Convention (b): plural variants exist (or the family is missing
|
|
421
|
+
// entirely). Evaluate each CLDR category — a missing variant is a
|
|
422
|
+
// hard absence only when the category is required for this locale;
|
|
423
|
+
// optional categories (e.g. French `_many`) downgrade to a soft note.
|
|
424
|
+
for (const { category, pluralKey } of variants) {
|
|
425
|
+
if (processedKeys.has(pluralKey))
|
|
426
|
+
continue;
|
|
427
|
+
processedKeys.add(pluralKey);
|
|
428
|
+
const state = resolveAndClassify(pluralKey);
|
|
429
|
+
// An optional category (e.g. French `_many`) is a soft note
|
|
430
|
+
// whenever it isn't translated — never a hard failure. See #270.
|
|
431
|
+
if (state !== 'translated' && optionalCategories.has(category)) {
|
|
432
|
+
optionalInNs++;
|
|
433
|
+
keyDetails.push({ key: pluralKey, state: 'optional' });
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
totalInNs++;
|
|
437
|
+
if (state === 'translated')
|
|
438
|
+
translatedInNs++;
|
|
439
|
+
else if (state === 'empty')
|
|
440
|
+
emptyInNs++;
|
|
441
|
+
else
|
|
442
|
+
absentInNs++;
|
|
443
|
+
keyDetails.push({ key: pluralKey, state });
|
|
444
|
+
}
|
|
348
445
|
}
|
|
349
446
|
}
|
|
350
447
|
}
|
|
@@ -380,10 +477,11 @@ async function generateStatusReport(config) {
|
|
|
380
477
|
absentInNs++;
|
|
381
478
|
keyDetails.push({ key: variantKey, state });
|
|
382
479
|
}
|
|
383
|
-
namespaces.set(ns, { totalKeys: totalInNs, translatedKeys: translatedInNs, emptyKeys: emptyInNs, absentKeys: absentInNs, keyDetails });
|
|
480
|
+
namespaces.set(ns, { totalKeys: totalInNs, translatedKeys: translatedInNs, emptyKeys: emptyInNs, absentKeys: absentInNs, optionalKeys: optionalInNs, keyDetails });
|
|
384
481
|
totalTranslatedForLocale += translatedInNs;
|
|
385
482
|
totalEmptyForLocale += emptyInNs;
|
|
386
483
|
totalAbsentForLocale += absentInNs;
|
|
484
|
+
totalOptionalForLocale += optionalInNs;
|
|
387
485
|
totalKeysForLocale += totalInNs;
|
|
388
486
|
}
|
|
389
487
|
const localeStatus = {
|
|
@@ -391,6 +489,7 @@ async function generateStatusReport(config) {
|
|
|
391
489
|
totalTranslated: totalTranslatedForLocale,
|
|
392
490
|
totalEmpty: totalEmptyForLocale,
|
|
393
491
|
totalAbsent: totalAbsentForLocale,
|
|
492
|
+
totalOptional: totalOptionalForLocale,
|
|
394
493
|
namespaces,
|
|
395
494
|
};
|
|
396
495
|
if (isPrimary) {
|
|
@@ -482,6 +581,9 @@ async function displayDetailedLocaleReport(report, config, locale, namespaceFilt
|
|
|
482
581
|
else if (state === 'empty') {
|
|
483
582
|
console.log(` ${node_util.styleText('yellow', '~')} ${key} ${node_util.styleText('yellow', '(untranslated)')}`);
|
|
484
583
|
}
|
|
584
|
+
else if (state === 'optional') {
|
|
585
|
+
console.log(` ${node_util.styleText('gray', '○')} ${key} ${node_util.styleText('gray', '(optional plural form)')}`);
|
|
586
|
+
}
|
|
485
587
|
else {
|
|
486
588
|
console.log(` ${node_util.styleText('red', '✗')} ${key} ${node_util.styleText('red', '(absent)')}`);
|
|
487
589
|
}
|
package/dist/esm/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ const program = new Command();
|
|
|
31
31
|
program
|
|
32
32
|
.name('i18next-cli')
|
|
33
33
|
.description('A unified, high-performance i18next CLI.')
|
|
34
|
-
.version('1.
|
|
34
|
+
.version('1.64.1'); // This string is replaced with the actual version at build time by rollup
|
|
35
35
|
// new: global config override option
|
|
36
36
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
|
|
37
37
|
program
|
|
@@ -392,8 +392,11 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
|
|
|
392
392
|
if (shouldFilterKey(key)) {
|
|
393
393
|
return false;
|
|
394
394
|
}
|
|
395
|
-
if (!hasCount) {
|
|
396
|
-
// Non-plural keys are always included
|
|
395
|
+
if (!hasCount || config.extract.disablePlurals) {
|
|
396
|
+
// Non-plural keys are always included. Under `disablePlurals`, count keys
|
|
397
|
+
// are emitted as plain keys (no plural expansion) but still carry
|
|
398
|
+
// `hasCount` so `status` can recognise them — they must bypass the
|
|
399
|
+
// plural-form filtering below and always be included, exactly as before.
|
|
397
400
|
return true;
|
|
398
401
|
}
|
|
399
402
|
// For plural keys, check if this specific plural form is needed for the target language
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { safePluralRules } from '../../utils/plural-rules.js';
|
|
2
2
|
import { parseNestedReferences } from '../../utils/nesting.js';
|
|
3
3
|
import { lineColumnFromOffset, isSimpleTemplateLiteral, getObjectPropValue, getObjectPropValueExpression } from './ast-utils.js';
|
|
4
|
+
import { matchesFunctionPattern } from '../utils/function-matcher.js';
|
|
4
5
|
|
|
5
6
|
// Helper to escape regex characters
|
|
6
7
|
const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -70,19 +71,11 @@ class CallExpressionHandler {
|
|
|
70
71
|
let isFunctionToParse = scopeInfo !== undefined; // A scoped variable (from useTranslation, etc.) is always parsed.
|
|
71
72
|
if (!isFunctionToParse) {
|
|
72
73
|
for (const pattern of configuredFunctions) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
// Handle exact match
|
|
82
|
-
if (pattern === functionName) {
|
|
83
|
-
isFunctionToParse = true;
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
74
|
+
// Supports exact ('t', 'i18next.t'), prefix wildcards ('*.t' -> 'i18n.t')
|
|
75
|
+
// and suffix wildcards ('tProps.*' -> 'tProps.label').
|
|
76
|
+
if (matchesFunctionPattern(functionName, pattern)) {
|
|
77
|
+
isFunctionToParse = true;
|
|
78
|
+
break;
|
|
86
79
|
}
|
|
87
80
|
}
|
|
88
81
|
}
|
|
@@ -342,12 +335,17 @@ class CallExpressionHandler {
|
|
|
342
335
|
// Check if plurals are disabled FIRST, before any plural optimization paths
|
|
343
336
|
if (this.config.extract.disablePlurals) {
|
|
344
337
|
// When plurals are disabled, treat count as a regular option (for interpolation only)
|
|
345
|
-
// Still handle context normally
|
|
338
|
+
// Still handle context normally. We keep `hasCount`/`isOrdinal` on the
|
|
339
|
+
// emitted key so `status` knows it is count-driven and can accept the
|
|
340
|
+
// file's plural variants (or the bare key) instead of demanding the
|
|
341
|
+
// literal base key. File generation ignores these flags under
|
|
342
|
+
// disablePlurals (see translation-manager), so output is unchanged.
|
|
343
|
+
const countMeta = hasCount ? { hasCount: true, isOrdinal: isOrdinalByKey } : {};
|
|
346
344
|
if (keysWithContext.length > 0) {
|
|
347
|
-
keysWithContext.forEach(this.pluginContext.addKey);
|
|
345
|
+
keysWithContext.forEach(k => this.pluginContext.addKey({ ...k, ...countMeta }));
|
|
348
346
|
}
|
|
349
347
|
else {
|
|
350
|
-
this.pluginContext.addKey({ key: finalKey, ns, defaultValue: dv, explicitDefault: explicitDefaultForBase });
|
|
348
|
+
this.pluginContext.addKey({ key: finalKey, ns, defaultValue: dv, explicitDefault: explicitDefaultForBase, ...countMeta });
|
|
351
349
|
}
|
|
352
350
|
continue; // This key is fully handled
|
|
353
351
|
}
|
|
@@ -122,14 +122,18 @@ function extractKeysFromComments(code, pluginContext, config, scopeResolver) {
|
|
|
122
122
|
ns = config.extract.defaultNS;
|
|
123
123
|
// 5. Handle context and count combinations based on disablePlurals setting
|
|
124
124
|
if (config.extract.disablePlurals) {
|
|
125
|
-
// When plurals are disabled, ignore count for key generation
|
|
125
|
+
// When plurals are disabled, ignore count for key generation. We still
|
|
126
|
+
// tag the key with `hasCount` (when a count was present) so `status` can
|
|
127
|
+
// recognise it as count-driven; file generation ignores the flag under
|
|
128
|
+
// disablePlurals (see translation-manager).
|
|
129
|
+
const countMeta = count ? { hasCount: true, isOrdinal } : {};
|
|
126
130
|
if (context) {
|
|
127
131
|
// Only generate context variants (no base key when context is static)
|
|
128
|
-
pluginContext.addKey({ key: `${key}_${context}`, ns, defaultValue: defaultValue ?? key });
|
|
132
|
+
pluginContext.addKey({ key: `${key}_${context}`, ns, defaultValue: defaultValue ?? key, ...countMeta });
|
|
129
133
|
}
|
|
130
134
|
else {
|
|
131
135
|
// Simple key (ignore count)
|
|
132
|
-
pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key });
|
|
136
|
+
pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key, ...countMeta });
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
139
|
else {
|
|
@@ -367,12 +367,16 @@ class JSXHandler {
|
|
|
367
367
|
else if (hasCount) {
|
|
368
368
|
// Check if plurals are disabled
|
|
369
369
|
if (this.config.extract.disablePlurals) {
|
|
370
|
-
// When plurals are disabled, just add the base keys (no plural forms)
|
|
370
|
+
// When plurals are disabled, just add the base keys (no plural forms).
|
|
371
|
+
// We keep `hasCount` so `status` recognises the key as count-driven and
|
|
372
|
+
// accepts the file's plural variants (or bare key); file generation
|
|
373
|
+
// ignores it under disablePlurals (see translation-manager).
|
|
371
374
|
extractedKeys.forEach(extractedKey => {
|
|
372
375
|
this.pluginContext.addKey({
|
|
373
376
|
key: extractedKey.key,
|
|
374
377
|
ns: extractedKey.ns,
|
|
375
378
|
defaultValue: extractedKey.defaultValue,
|
|
379
|
+
hasCount: true,
|
|
376
380
|
locations: extractedKey.locations
|
|
377
381
|
});
|
|
378
382
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks whether a (possibly dotted) function name matches a configured
|
|
3
|
+
* `functions` pattern.
|
|
4
|
+
*
|
|
5
|
+
* Supported pattern forms:
|
|
6
|
+
* - Exact match: `'t'` matches `t`, `'i18next.t'` matches `i18next.t`.
|
|
7
|
+
* - Prefix wildcard: `'*.t'` matches any call ending in `.t`
|
|
8
|
+
* (e.g. `i18n.t`, `this._i18n.t`).
|
|
9
|
+
* - Suffix wildcard: `'tProps.*'` matches any single-segment member call on the
|
|
10
|
+
* prefix (e.g. `tProps.label`, `tProps.title`) but not deeper nesting like
|
|
11
|
+
* `tProps.label.t`.
|
|
12
|
+
*
|
|
13
|
+
* @param functionName - The dotted callee name (e.g. `tProps.label`).
|
|
14
|
+
* @param pattern - A single configured pattern from `extract.functions`.
|
|
15
|
+
*/
|
|
16
|
+
function matchesFunctionPattern(functionName, pattern) {
|
|
17
|
+
if (pattern === functionName)
|
|
18
|
+
return true;
|
|
19
|
+
// Prefix wildcard, e.g. '*.t' -> matches any callee ending in '.t'
|
|
20
|
+
if (pattern.startsWith('*.')) {
|
|
21
|
+
return functionName.endsWith(pattern.slice(1));
|
|
22
|
+
}
|
|
23
|
+
// Suffix wildcard, e.g. 'tProps.*' -> matches 'tProps.<segment>'
|
|
24
|
+
if (pattern.endsWith('.*')) {
|
|
25
|
+
const prefix = pattern.slice(0, -1); // keep the trailing dot: 'tProps.'
|
|
26
|
+
if (!functionName.startsWith(prefix))
|
|
27
|
+
return false;
|
|
28
|
+
const rest = functionName.slice(prefix.length);
|
|
29
|
+
return rest.length > 0 && !rest.includes('.');
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { matchesFunctionPattern };
|
package/dist/esm/linter.js
CHANGED
|
@@ -8,6 +8,7 @@ import { ConsoleLogger } from './utils/logger.js';
|
|
|
8
8
|
import { createSpinnerLike } from './utils/wrap-ora.js';
|
|
9
9
|
import { acceptedTags, translatableAttributes, ignoredTags, ignoredAttributeLowerSet } from './utils/jsx-attributes.js';
|
|
10
10
|
import { findFirstTokenIndex, normalizeASTSpans, buildByteToCharMap, convertSpansToCharIndices, collectIgnoredLineRanges } from './extractor/parsers/ast-utils.js';
|
|
11
|
+
import { matchesFunctionPattern } from './extractor/utils/function-matcher.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Loads all translation values from the primary locale's JSON files and returns
|
|
@@ -139,28 +140,58 @@ function lintInterpolationParams(ast, code, config, translationValues) {
|
|
|
139
140
|
}
|
|
140
141
|
// Modularized CallExpression handler
|
|
141
142
|
function handleCallExpression(node, ancestors) {
|
|
143
|
+
// Build the full dotted callee name (e.g. 'i18n.t', 'tProps.label') as well as
|
|
144
|
+
// the bare last segment ('t', 'label') so both exact and wildcard patterns match.
|
|
142
145
|
let calleeName = '';
|
|
146
|
+
let fullCalleeName = '';
|
|
143
147
|
if (node.callee) {
|
|
144
|
-
if (node.callee.type === 'Identifier'
|
|
145
|
-
calleeName = node.callee.value;
|
|
148
|
+
if (node.callee.type === 'Identifier') {
|
|
149
|
+
calleeName = node.callee.value || node.callee.name || '';
|
|
150
|
+
fullCalleeName = calleeName;
|
|
146
151
|
}
|
|
147
|
-
else if (node.callee.type === '
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
else if (node.callee.type === 'MemberExpression') {
|
|
153
|
+
const parts = [];
|
|
154
|
+
let current = node.callee;
|
|
155
|
+
let computed = false;
|
|
156
|
+
while (current?.type === 'MemberExpression') {
|
|
157
|
+
if (current.property?.type === 'Identifier') {
|
|
158
|
+
parts.unshift(current.property.value || current.property.name);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
computed = true;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
current = current.object;
|
|
165
|
+
}
|
|
166
|
+
if (!computed) {
|
|
167
|
+
if (current?.type === 'ThisExpression') {
|
|
168
|
+
parts.unshift('this');
|
|
169
|
+
}
|
|
170
|
+
else if (current?.type === 'Identifier') {
|
|
171
|
+
parts.unshift(current.value || current.name);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
parts.length = 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (node.callee.property?.type === 'Identifier') {
|
|
178
|
+
calleeName = node.callee.property.value || node.callee.property.name;
|
|
179
|
+
}
|
|
180
|
+
fullCalleeName = parts.length ? parts.join('.') : calleeName;
|
|
152
181
|
}
|
|
153
182
|
}
|
|
154
183
|
const fnPatterns = config.extract.functions || ['t', '*.t'];
|
|
155
184
|
let matches = false;
|
|
156
185
|
for (const pattern of fnPatterns) {
|
|
157
|
-
if (pattern
|
|
158
|
-
|
|
159
|
-
|
|
186
|
+
if (matchesFunctionPattern(fullCalleeName, pattern)) {
|
|
187
|
+
matches = true;
|
|
188
|
+
break;
|
|
160
189
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
190
|
+
// Backwards-compatible: '*.X' also matches a call whose last segment is X
|
|
191
|
+
// (covers computed member expressions where the full chain is unavailable).
|
|
192
|
+
if (pattern.startsWith('*.') && calleeName === pattern.slice(2)) {
|
|
193
|
+
matches = true;
|
|
194
|
+
break;
|
|
164
195
|
}
|
|
165
196
|
}
|
|
166
197
|
if (matches) {
|
package/dist/esm/rename-key.js
CHANGED
|
@@ -295,6 +295,10 @@ function replaceKeyWithRegex(code, oldParts, newParts, config, namespaceKeyMap)
|
|
|
295
295
|
const suffix = fnPattern.slice(2);
|
|
296
296
|
return `\\b[\\w$]+\\.${escapeRegex(suffix)}`;
|
|
297
297
|
}
|
|
298
|
+
if (fnPattern.endsWith('.*')) {
|
|
299
|
+
const prefix = fnPattern.slice(0, -2);
|
|
300
|
+
return `\\b${escapeRegex(prefix)}\\.[\\w$]+`;
|
|
301
|
+
}
|
|
298
302
|
return `\\b${escapeRegex(fnPattern)}`;
|
|
299
303
|
};
|
|
300
304
|
// Helper: check whether the old key exists in a given namespace (from the prebuilt map)
|
package/dist/esm/status.js
CHANGED
|
@@ -20,6 +20,53 @@ function classifyValue(value) {
|
|
|
20
20
|
return 'empty';
|
|
21
21
|
return 'translated';
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Representative counts used to decide which CLDR plural categories a locale can
|
|
25
|
+
* actually reach in normal usage: small integers (the common case), a handful
|
|
26
|
+
* of larger integers, and common decimals (so categories that only fire for
|
|
27
|
+
* fractional display values — e.g. Polish/Russian `other` — stay required).
|
|
28
|
+
*
|
|
29
|
+
* Any category NOT produced by these counts is treated as "optional". The prime
|
|
30
|
+
* example is French `many`, which `Intl.PluralRules` only selects for values
|
|
31
|
+
* ≥ 1,000,000 — the i18next runtime can technically request it, but real apps
|
|
32
|
+
* almost never hit those values (and the runtime falls back to the base key
|
|
33
|
+
* when the variant is missing), so a missing `_many` should be a soft note
|
|
34
|
+
* rather than a hard "missing key" failure.
|
|
35
|
+
*/
|
|
36
|
+
const REPRESENTATIVE_COUNTS = (() => {
|
|
37
|
+
const counts = [];
|
|
38
|
+
for (let n = 0; n <= 20; n++)
|
|
39
|
+
counts.push(n);
|
|
40
|
+
counts.push(100, 101, 1000, 1001, 10000);
|
|
41
|
+
counts.push(0.5, 1.1, 1.5, 2.5, 3.5);
|
|
42
|
+
return counts;
|
|
43
|
+
})();
|
|
44
|
+
const optionalCategoriesCache = new Map();
|
|
45
|
+
/**
|
|
46
|
+
* Returns the CLDR plural categories for a locale that are NOT reachable by any
|
|
47
|
+
* representative count (see {@link REPRESENTATIVE_COUNTS}). These are reported
|
|
48
|
+
* by `status` as optional: a missing variant is a soft note instead of a hard
|
|
49
|
+
* absence, mirroring how the i18next runtime resolves such forms.
|
|
50
|
+
*/
|
|
51
|
+
function getOptionalPluralCategories(locale, isOrdinal) {
|
|
52
|
+
const type = isOrdinal ? 'ordinal' : 'cardinal';
|
|
53
|
+
const cacheKey = `${locale}|${type}`;
|
|
54
|
+
const cached = optionalCategoriesCache.get(cacheKey);
|
|
55
|
+
if (cached)
|
|
56
|
+
return cached;
|
|
57
|
+
let optional;
|
|
58
|
+
try {
|
|
59
|
+
const rules = safePluralRules(locale, { type });
|
|
60
|
+
const all = rules.resolvedOptions().pluralCategories;
|
|
61
|
+
const reachable = new Set(REPRESENTATIVE_COUNTS.map(n => rules.select(n)));
|
|
62
|
+
optional = new Set(all.filter(c => !reachable.has(c)));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
optional = new Set();
|
|
66
|
+
}
|
|
67
|
+
optionalCategoriesCache.set(cacheKey, optional);
|
|
68
|
+
return optional;
|
|
69
|
+
}
|
|
23
70
|
/**
|
|
24
71
|
* Runs a health check on the project's i18next translations and displays a status report.
|
|
25
72
|
*
|
|
@@ -216,6 +263,7 @@ async function generateStatusReport(config) {
|
|
|
216
263
|
let totalTranslatedForLocale = 0;
|
|
217
264
|
let totalEmptyForLocale = 0;
|
|
218
265
|
let totalAbsentForLocale = 0;
|
|
266
|
+
let totalOptionalForLocale = 0;
|
|
219
267
|
let totalKeysForLocale = 0;
|
|
220
268
|
const namespaces = new Map();
|
|
221
269
|
const mergedTranslations = mergeNamespaces
|
|
@@ -244,6 +292,7 @@ async function generateStatusReport(config) {
|
|
|
244
292
|
let translatedInNs = 0;
|
|
245
293
|
let emptyInNs = 0;
|
|
246
294
|
let absentInNs = 0;
|
|
295
|
+
let optionalInNs = 0;
|
|
247
296
|
let totalInNs = 0;
|
|
248
297
|
const keyDetails = [];
|
|
249
298
|
// Get the plural categories for THIS specific locale
|
|
@@ -308,37 +357,85 @@ async function generateStatusReport(config) {
|
|
|
308
357
|
// Only count this key if it's a plural form used by this locale
|
|
309
358
|
if (localePluralCategories.includes(category) && !processedKeys.has(baseKey)) {
|
|
310
359
|
processedKeys.add(baseKey);
|
|
311
|
-
totalInNs++;
|
|
312
360
|
const state = resolveAndClassify(baseKey);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
361
|
+
const optionalCategories = getOptionalPluralCategories(locale, isOrdinalVariant);
|
|
362
|
+
// An optional category (e.g. French `_many`) is a soft note whenever
|
|
363
|
+
// it isn't translated — whether absent or an empty placeholder that
|
|
364
|
+
// `extract` wrote. It is never a hard failure. See #270 and
|
|
365
|
+
// getOptionalPluralCategories.
|
|
366
|
+
if (state !== 'translated' && optionalCategories.has(category)) {
|
|
367
|
+
optionalInNs++;
|
|
368
|
+
keyDetails.push({ key: baseKey, state: 'optional' });
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
totalInNs++;
|
|
372
|
+
if (state === 'translated')
|
|
373
|
+
translatedInNs++;
|
|
374
|
+
else if (state === 'empty')
|
|
375
|
+
emptyInNs++;
|
|
376
|
+
else
|
|
377
|
+
absentInNs++;
|
|
378
|
+
keyDetails.push({ key: baseKey, state });
|
|
379
|
+
}
|
|
320
380
|
}
|
|
321
381
|
}
|
|
322
382
|
else {
|
|
323
|
-
// This is a base plural key without expanded variants
|
|
324
|
-
//
|
|
383
|
+
// This is a base plural key without expanded variants. Mirror the
|
|
384
|
+
// i18next runtime, where t(key, { count }) resolves `key + suffix`
|
|
385
|
+
// and falls back to the bare `key`. A family is therefore satisfied
|
|
386
|
+
// either by its plural variants OR by a bare key (the convention
|
|
387
|
+
// used when `disablePlurals` is enabled and no variants are written).
|
|
325
388
|
const localePluralCategories = getLocalePluralCategories(locale, isOrdinal || false);
|
|
326
|
-
|
|
327
|
-
|
|
389
|
+
const optionalCategories = getOptionalPluralCategories(locale, isOrdinal || false);
|
|
390
|
+
const variants = localePluralCategories.map(category => ({
|
|
391
|
+
category,
|
|
392
|
+
pluralKey: isOrdinal
|
|
328
393
|
? `${baseKey}${pluralSeparator}ordinal${pluralSeparator}${category}`
|
|
329
|
-
: `${baseKey}${pluralSeparator}${category}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
394
|
+
: `${baseKey}${pluralSeparator}${category}`,
|
|
395
|
+
}));
|
|
396
|
+
const anyVariantPresent = variants.some(({ pluralKey }) => resolveAndClassify(pluralKey) !== 'absent');
|
|
397
|
+
const bareState = resolveAndClassify(baseKey);
|
|
398
|
+
if (!anyVariantPresent && bareState !== 'absent' && !processedKeys.has(baseKey)) {
|
|
399
|
+
// Convention (a): only the bare key exists (typical under
|
|
400
|
+
// disablePlurals, or single-"other" languages). The runtime
|
|
401
|
+
// resolves count via the bare key, so it satisfies the family on
|
|
402
|
+
// its own — don't demand plural variants that were never written.
|
|
403
|
+
processedKeys.add(baseKey);
|
|
333
404
|
totalInNs++;
|
|
334
|
-
|
|
335
|
-
if (state === 'translated')
|
|
405
|
+
if (bareState === 'translated')
|
|
336
406
|
translatedInNs++;
|
|
337
|
-
else if (
|
|
407
|
+
else if (bareState === 'empty')
|
|
338
408
|
emptyInNs++;
|
|
339
409
|
else
|
|
340
410
|
absentInNs++;
|
|
341
|
-
keyDetails.push({ key:
|
|
411
|
+
keyDetails.push({ key: baseKey, state: bareState });
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
// Convention (b): plural variants exist (or the family is missing
|
|
415
|
+
// entirely). Evaluate each CLDR category — a missing variant is a
|
|
416
|
+
// hard absence only when the category is required for this locale;
|
|
417
|
+
// optional categories (e.g. French `_many`) downgrade to a soft note.
|
|
418
|
+
for (const { category, pluralKey } of variants) {
|
|
419
|
+
if (processedKeys.has(pluralKey))
|
|
420
|
+
continue;
|
|
421
|
+
processedKeys.add(pluralKey);
|
|
422
|
+
const state = resolveAndClassify(pluralKey);
|
|
423
|
+
// An optional category (e.g. French `_many`) is a soft note
|
|
424
|
+
// whenever it isn't translated — never a hard failure. See #270.
|
|
425
|
+
if (state !== 'translated' && optionalCategories.has(category)) {
|
|
426
|
+
optionalInNs++;
|
|
427
|
+
keyDetails.push({ key: pluralKey, state: 'optional' });
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
totalInNs++;
|
|
431
|
+
if (state === 'translated')
|
|
432
|
+
translatedInNs++;
|
|
433
|
+
else if (state === 'empty')
|
|
434
|
+
emptyInNs++;
|
|
435
|
+
else
|
|
436
|
+
absentInNs++;
|
|
437
|
+
keyDetails.push({ key: pluralKey, state });
|
|
438
|
+
}
|
|
342
439
|
}
|
|
343
440
|
}
|
|
344
441
|
}
|
|
@@ -374,10 +471,11 @@ async function generateStatusReport(config) {
|
|
|
374
471
|
absentInNs++;
|
|
375
472
|
keyDetails.push({ key: variantKey, state });
|
|
376
473
|
}
|
|
377
|
-
namespaces.set(ns, { totalKeys: totalInNs, translatedKeys: translatedInNs, emptyKeys: emptyInNs, absentKeys: absentInNs, keyDetails });
|
|
474
|
+
namespaces.set(ns, { totalKeys: totalInNs, translatedKeys: translatedInNs, emptyKeys: emptyInNs, absentKeys: absentInNs, optionalKeys: optionalInNs, keyDetails });
|
|
378
475
|
totalTranslatedForLocale += translatedInNs;
|
|
379
476
|
totalEmptyForLocale += emptyInNs;
|
|
380
477
|
totalAbsentForLocale += absentInNs;
|
|
478
|
+
totalOptionalForLocale += optionalInNs;
|
|
381
479
|
totalKeysForLocale += totalInNs;
|
|
382
480
|
}
|
|
383
481
|
const localeStatus = {
|
|
@@ -385,6 +483,7 @@ async function generateStatusReport(config) {
|
|
|
385
483
|
totalTranslated: totalTranslatedForLocale,
|
|
386
484
|
totalEmpty: totalEmptyForLocale,
|
|
387
485
|
totalAbsent: totalAbsentForLocale,
|
|
486
|
+
totalOptional: totalOptionalForLocale,
|
|
388
487
|
namespaces,
|
|
389
488
|
};
|
|
390
489
|
if (isPrimary) {
|
|
@@ -476,6 +575,9 @@ async function displayDetailedLocaleReport(report, config, locale, namespaceFilt
|
|
|
476
575
|
else if (state === 'empty') {
|
|
477
576
|
console.log(` ${styleText('yellow', '~')} ${key} ${styleText('yellow', '(untranslated)')}`);
|
|
478
577
|
}
|
|
578
|
+
else if (state === 'optional') {
|
|
579
|
+
console.log(` ${styleText('gray', '○')} ${key} ${styleText('gray', '(optional plural form)')}`);
|
|
580
|
+
}
|
|
479
581
|
else {
|
|
480
582
|
console.log(` ${styleText('red', '✗')} ${key} ${styleText('red', '(absent)')}`);
|
|
481
583
|
}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AA0jC9F;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,EAC5B,EACE,uBAA+B,EAC/B,OAAe,EACf,oBAA4B,EAC5B,MAA4B,EAC7B,GAAE;IACD,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAA;CACX,GACL,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAiK9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"call-expression-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/call-expression-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6C,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1G,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"call-expression-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/call-expression-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6C,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1G,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAc7D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,kBAAkB,CAAoB;IACvC,UAAU,cAAoB;IACrC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,iBAAiB,CAAsC;gBAG7D,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM,EAC5B,iBAAiB,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAA2B;IAW3E;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,IAAI;IA8ZxG;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,wBAAwB;IAyEhC;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IAsDpC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,OAAO,CAAC,sBAAsB;IA2B9B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,uBAAuB;IAgB/B;;;;;;;;OAQG;IACH,OAAO,CAAC,iCAAiC;IAwFzC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,gBAAgB;IAyMxB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comment-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/comment-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAOzE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,oBAAoB,EAC5B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC1F,IAAI,
|
|
1
|
+
{"version":3,"file":"comment-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/comment-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAOzE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,oBAAoB,EAC5B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC1F,IAAI,CAyJN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,UAAU,EAAqC,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAgB,MAAM,gBAAgB,CAAA;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAS7D,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;gBAGlC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM;IAS9B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,iCAAiC;IAgCzC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;;;;;;OAQG;IACH,gBAAgB,CAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"jsx-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,UAAU,EAAqC,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAgB,MAAM,gBAAgB,CAAA;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAS7D,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;gBAGlC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM;IAS9B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,iCAAiC;IAgCzC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;;;;;;OAQG;IACH,gBAAgB,CAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,GAAG,IAAI;IA+UjI;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,0BAA0B;IAkIlC;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;CAevB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks whether a (possibly dotted) function name matches a configured
|
|
3
|
+
* `functions` pattern.
|
|
4
|
+
*
|
|
5
|
+
* Supported pattern forms:
|
|
6
|
+
* - Exact match: `'t'` matches `t`, `'i18next.t'` matches `i18next.t`.
|
|
7
|
+
* - Prefix wildcard: `'*.t'` matches any call ending in `.t`
|
|
8
|
+
* (e.g. `i18n.t`, `this._i18n.t`).
|
|
9
|
+
* - Suffix wildcard: `'tProps.*'` matches any single-segment member call on the
|
|
10
|
+
* prefix (e.g. `tProps.label`, `tProps.title`) but not deeper nesting like
|
|
11
|
+
* `tProps.label.t`.
|
|
12
|
+
*
|
|
13
|
+
* @param functionName - The dotted callee name (e.g. `tProps.label`).
|
|
14
|
+
* @param pattern - A single configured pattern from `extract.functions`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function matchesFunctionPattern(functionName: string, pattern: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Checks whether a function name matches any of the configured patterns.
|
|
19
|
+
*
|
|
20
|
+
* @param functionName - The dotted callee name (e.g. `tProps.label`).
|
|
21
|
+
* @param patterns - The configured `extract.functions` patterns.
|
|
22
|
+
*/
|
|
23
|
+
export declare function matchesAnyFunctionPattern(functionName: string, patterns: string[]): boolean;
|
|
24
|
+
//# sourceMappingURL=function-matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"function-matcher.d.ts","sourceRoot":"","sources":["../../../src/extractor/utils/function-matcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAiBtF;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAK5F"}
|
package/types/linter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAO1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,SAAS,EAA6B,MAAM,YAAY,CAAA;AA8SpG,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE;QAAC;YACT,OAAO,EAAE,MAAM,CAAC;SACjB;KAAC,CAAC;IACH,IAAI,EAAE;QAAC;YACL,OAAO,EAAE,OAAO,CAAC;YACjB,OAAO,EAAE,MAAM,CAAC;YAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;SACpC;KAAC,CAAC;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACvB,CAAA;AAED,eAAO,MAAM,uBAAuB,EAAE,MAAM,EAAiD,CAAA;AAC7F,eAAO,MAAM,6BAA6B,EAAE,MAAM,EAAqD,CAAA;AAKvG,qBAAa,MAAO,SAAQ,YAAY,CAAC,cAAc,CAAC;IACtD,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,MAAM,CAAQ;gBAET,MAAM,EAAE,oBAAoB,EAAE,MAAM,GAAE,MAA4B;IAM/E,SAAS,CAAE,KAAK,EAAE,OAAO;IAanB,GAAG;;;;;;;IAkIT,OAAO,CAAC,uBAAuB;YAOjB,qBAAqB;IAWnC,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,0BAA0B;YAUpB,qBAAqB;YAgBrB,uBAAuB;CAgBtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB;;;;;;GAE5D;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,iBAkCnD"}
|
package/types/status.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAgB,MAAM,YAAY,CAAA;AAOpE;;GAEG;AACH,UAAU,aAAa;IACrB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAgB,MAAM,YAAY,CAAA;AAOpE;;GAEG;AACH,UAAU,aAAa;IACrB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAyHD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB,EAAE,OAAO,GAAE,aAAkB,iBA4BzF"}
|