i18next-cli 1.46.4 → 1.47.0
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 +198 -3
- package/dist/cjs/cli.js +50 -1
- package/dist/cjs/extractor/core/extractor.js +27 -18
- package/dist/cjs/extractor/core/translation-manager.js +10 -3
- package/dist/cjs/extractor/parsers/call-expression-handler.js +9 -1
- package/dist/cjs/extractor/parsers/jsx-handler.js +10 -1
- package/dist/cjs/index.js +5 -0
- package/dist/cjs/init.js +68 -12
- package/dist/cjs/instrumenter/core/instrumenter.js +1633 -0
- package/dist/cjs/instrumenter/core/key-generator.js +71 -0
- package/dist/cjs/instrumenter/core/string-detector.js +290 -0
- package/dist/cjs/instrumenter/core/transformer.js +339 -0
- package/dist/cjs/linter.js +6 -7
- package/dist/cjs/utils/jsx-attributes.js +131 -0
- package/dist/esm/cli.js +50 -1
- package/dist/esm/extractor/core/extractor.js +27 -18
- package/dist/esm/extractor/core/translation-manager.js +10 -3
- package/dist/esm/extractor/parsers/call-expression-handler.js +9 -1
- package/dist/esm/extractor/parsers/jsx-handler.js +10 -1
- package/dist/esm/index.js +3 -0
- package/dist/esm/init.js +68 -12
- package/dist/esm/instrumenter/core/instrumenter.js +1630 -0
- package/dist/esm/instrumenter/core/key-generator.js +68 -0
- package/dist/esm/instrumenter/core/string-detector.js +288 -0
- package/dist/esm/instrumenter/core/transformer.js +336 -0
- package/dist/esm/linter.js +6 -7
- package/dist/esm/utils/jsx-attributes.js +121 -0
- package/package.json +2 -1
- package/types/cli.d.ts.map +1 -1
- package/types/extractor/core/extractor.d.ts.map +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/jsx-handler.d.ts.map +1 -1
- package/types/index.d.ts +2 -1
- package/types/index.d.ts.map +1 -1
- package/types/init.d.ts.map +1 -1
- package/types/instrumenter/core/instrumenter.d.ts +16 -0
- package/types/instrumenter/core/instrumenter.d.ts.map +1 -0
- package/types/instrumenter/core/key-generator.d.ts +30 -0
- package/types/instrumenter/core/key-generator.d.ts.map +1 -0
- package/types/instrumenter/core/string-detector.d.ts +27 -0
- package/types/instrumenter/core/string-detector.d.ts.map +1 -0
- package/types/instrumenter/core/transformer.d.ts +31 -0
- package/types/instrumenter/core/transformer.d.ts.map +1 -0
- package/types/instrumenter/index.d.ts +6 -0
- package/types/instrumenter/index.d.ts.map +1 -0
- package/types/linter.d.ts.map +1 -1
- package/types/types.d.ts +285 -1
- package/types/types.d.ts.map +1 -1
- package/types/utils/jsx-attributes.d.ts +68 -0
- package/types/utils/jsx-attributes.d.ts.map +1 -0
|
@@ -336,7 +336,7 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
|
|
|
336
336
|
return false;
|
|
337
337
|
};
|
|
338
338
|
// Filter nsKeys to only include keys relevant to this language
|
|
339
|
-
const filteredKeys = nsKeys.filter(({ key, hasCount, isOrdinal }) => {
|
|
339
|
+
const filteredKeys = nsKeys.filter(({ key, hasCount, isOrdinal, explicitDefault }) => {
|
|
340
340
|
// FIRST: Check if key matches preservePatterns and should be excluded
|
|
341
341
|
if (shouldFilterKey(key)) {
|
|
342
342
|
return false;
|
|
@@ -362,14 +362,21 @@ function buildNewTranslationsForNs(nsKeys, existingTranslations, config, locale,
|
|
|
362
362
|
return true;
|
|
363
363
|
// Otherwise fall through and check the explicit suffix as before.
|
|
364
364
|
}
|
|
365
|
+
// i18next supports a special _zero form that is NOT part of CLDR plural
|
|
366
|
+
// rules. When the key was explicitly extracted (e.g. from a t() call with
|
|
367
|
+
// `defaultValue_zero`), always include it regardless of the target
|
|
368
|
+
// language's Intl.PluralRules categories.
|
|
369
|
+
// See: https://www.i18next.com/translation-function/plurals#special-zero
|
|
370
|
+
const lastPart = keyParts[keyParts.length - 1];
|
|
371
|
+
if (lastPart === 'zero' && explicitDefault) {
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
365
374
|
if (isOrdinal && keyParts.includes('ordinal')) {
|
|
366
375
|
// For ordinal plurals: key_context_ordinal_category or key_ordinal_category
|
|
367
|
-
const lastPart = keyParts[keyParts.length - 1];
|
|
368
376
|
return targetLanguagePluralCategories.has(`ordinal_${lastPart}`);
|
|
369
377
|
}
|
|
370
378
|
else if (hasCount) {
|
|
371
379
|
// For cardinal plurals: key_context_category or key_category
|
|
372
|
-
const lastPart = keyParts[keyParts.length - 1];
|
|
373
380
|
return targetLanguagePluralCategories.has(lastPart);
|
|
374
381
|
}
|
|
375
382
|
return true;
|
|
@@ -703,8 +703,16 @@ class CallExpressionHandler {
|
|
|
703
703
|
categories.forEach(cat => allPluralCategories.add(cat));
|
|
704
704
|
}
|
|
705
705
|
}
|
|
706
|
-
const pluralCategories = Array.from(allPluralCategories).sort();
|
|
707
706
|
const pluralSeparator = this.config.extract.pluralSeparator ?? '_';
|
|
707
|
+
// i18next supports a special _zero form (not part of CLDR plural rules).
|
|
708
|
+
// When defaultValue_zero is present in the options, include 'zero' in the
|
|
709
|
+
// categories so that key_zero is generated with the correct default value.
|
|
710
|
+
// See: https://www.i18next.com/translation-function/plurals#special-zero
|
|
711
|
+
const zeroDefault = getObjectPropValue(options, `defaultValue${pluralSeparator}zero`);
|
|
712
|
+
if (typeof zeroDefault === 'string' && !allPluralCategories.has('zero')) {
|
|
713
|
+
allPluralCategories.add('zero');
|
|
714
|
+
}
|
|
715
|
+
const pluralCategories = Array.from(allPluralCategories).sort();
|
|
708
716
|
// Get all possible default values once at the start
|
|
709
717
|
const defaultValue = getObjectPropValue(options, 'defaultValue');
|
|
710
718
|
const otherDefault = getObjectPropValue(options, `defaultValue${pluralSeparator}other`);
|
|
@@ -364,8 +364,17 @@ class JSXHandler {
|
|
|
364
364
|
categories.forEach(cat => allPluralCategories.add(cat));
|
|
365
365
|
}
|
|
366
366
|
}
|
|
367
|
-
const pluralCategories = Array.from(allPluralCategories).sort();
|
|
368
367
|
const pluralSeparator = this.config.extract.pluralSeparator ?? '_';
|
|
368
|
+
// i18next supports a special _zero form (not part of CLDR plural rules).
|
|
369
|
+
// When defaultValue_zero is present in tOptions, include 'zero' in the
|
|
370
|
+
// categories so that key_zero is generated with the correct default value.
|
|
371
|
+
if (optionsNode) {
|
|
372
|
+
const zeroDefault = getObjectPropValue(optionsNode, `defaultValue${pluralSeparator}zero`);
|
|
373
|
+
if (typeof zeroDefault === 'string' && !allPluralCategories.has('zero')) {
|
|
374
|
+
allPluralCategories.add('zero');
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const pluralCategories = Array.from(allPluralCategories).sort();
|
|
369
378
|
// Get plural-specific default values from tOptions if available
|
|
370
379
|
let otherDefault;
|
|
371
380
|
let ordinalOtherDefault;
|
package/dist/esm/index.js
CHANGED
|
@@ -8,3 +8,6 @@ export { runSyncer } from './syncer.js';
|
|
|
8
8
|
export { runStatus } from './status.js';
|
|
9
9
|
export { runTypesGenerator } from './types-generator.js';
|
|
10
10
|
export { runRenameKey } from './rename-key.js';
|
|
11
|
+
export { runInstrumenter, writeExtractedKeys } from './instrumenter/core/instrumenter.js';
|
|
12
|
+
import './utils/jsx-attributes.js';
|
|
13
|
+
import 'magic-string';
|
package/dist/esm/init.js
CHANGED
|
@@ -30,6 +30,39 @@ async function isEsmProject() {
|
|
|
30
30
|
return true; // Default to ESM if package.json is not found or readable
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Checks whether i18next-cli is listed as a local dependency of the current project.
|
|
35
|
+
* When running via `npx` without a local install, `defineConfig` would not be available
|
|
36
|
+
* at runtime, so the generated config should fall back to a plain object export.
|
|
37
|
+
*
|
|
38
|
+
* @returns Promise resolving to true if i18next-cli is in dependencies or devDependencies
|
|
39
|
+
*/
|
|
40
|
+
async function isCliLocallyInstalled() {
|
|
41
|
+
try {
|
|
42
|
+
const packageJsonPath = resolve(process.cwd(), 'package.json');
|
|
43
|
+
const content = await readFile(packageJsonPath, 'utf-8');
|
|
44
|
+
const packageJson = JSON.parse(content);
|
|
45
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
46
|
+
return !!deps['i18next-cli'];
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Checks whether the project uses TypeScript by looking for a tsconfig.json.
|
|
54
|
+
*
|
|
55
|
+
* @returns Promise resolving to true if tsconfig.json exists in the project root
|
|
56
|
+
*/
|
|
57
|
+
async function isTypeScriptProject() {
|
|
58
|
+
try {
|
|
59
|
+
await readFile(resolve(process.cwd(), 'tsconfig.json'));
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
33
66
|
/**
|
|
34
67
|
* Interactive setup wizard for creating a new i18next-cli configuration file.
|
|
35
68
|
*
|
|
@@ -74,12 +107,17 @@ async function runInit() {
|
|
|
74
107
|
if (detectedConfig && typeof detectedConfig.extract?.output === 'function') {
|
|
75
108
|
delete detectedConfig.extract.output;
|
|
76
109
|
}
|
|
110
|
+
// Detect whether the project uses TypeScript to set the preferred default
|
|
111
|
+
const projectUsesTs = await isTypeScriptProject();
|
|
112
|
+
const tsChoice = 'TypeScript (i18next.config.ts)';
|
|
113
|
+
const jsChoice = 'JavaScript (i18next.config.js)';
|
|
114
|
+
const fileTypeChoices = projectUsesTs ? [tsChoice, jsChoice] : [jsChoice, tsChoice];
|
|
77
115
|
const answers = await inquirer.prompt([
|
|
78
116
|
{
|
|
79
117
|
type: 'select',
|
|
80
118
|
name: 'fileType',
|
|
81
119
|
message: 'What kind of configuration file do you want?',
|
|
82
|
-
choices:
|
|
120
|
+
choices: fileTypeChoices,
|
|
83
121
|
},
|
|
84
122
|
{
|
|
85
123
|
type: 'input',
|
|
@@ -146,23 +184,41 @@ async function runInit() {
|
|
|
146
184
|
// Fallback
|
|
147
185
|
return JSON.stringify(value);
|
|
148
186
|
}
|
|
187
|
+
const isLocallyInstalled = await isCliLocallyInstalled();
|
|
149
188
|
let fileContent = '';
|
|
150
|
-
if (
|
|
151
|
-
|
|
189
|
+
if (isLocallyInstalled) {
|
|
190
|
+
// i18next-cli is a local dependency — use defineConfig for type-safety
|
|
191
|
+
if (isTypeScript) {
|
|
192
|
+
fileContent = `import { defineConfig } from 'i18next-cli'
|
|
152
193
|
|
|
153
|
-
export default defineConfig(${toJs(configObject)})
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
194
|
+
export default defineConfig(${toJs(configObject)})`;
|
|
195
|
+
}
|
|
196
|
+
else if (isEsm) {
|
|
197
|
+
fileContent = `import { defineConfig } from 'i18next-cli'
|
|
157
198
|
|
|
158
199
|
/** @type {import('i18next-cli').I18nextToolkitConfig} */
|
|
159
|
-
export default defineConfig(${toJs(configObject)})
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
200
|
+
export default defineConfig(${toJs(configObject)})`;
|
|
201
|
+
}
|
|
202
|
+
else { // CJS
|
|
203
|
+
fileContent = `const { defineConfig } = require('i18next-cli')
|
|
163
204
|
|
|
164
205
|
/** @type {import('i18next-cli').I18nextToolkitConfig} */
|
|
165
|
-
module.exports = defineConfig(${toJs(configObject)})
|
|
206
|
+
module.exports = defineConfig(${toJs(configObject)})`;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
// i18next-cli is not locally installed (e.g. npx) — plain config object
|
|
211
|
+
if (isTypeScript) {
|
|
212
|
+
fileContent = `export default ${toJs(configObject)}`;
|
|
213
|
+
}
|
|
214
|
+
else if (isEsm) {
|
|
215
|
+
fileContent = `/** @type {import('i18next-cli').I18nextToolkitConfig} */
|
|
216
|
+
export default ${toJs(configObject)}`;
|
|
217
|
+
}
|
|
218
|
+
else { // CJS
|
|
219
|
+
fileContent = `/** @type {import('i18next-cli').I18nextToolkitConfig} */
|
|
220
|
+
module.exports = ${toJs(configObject)}`;
|
|
221
|
+
}
|
|
166
222
|
}
|
|
167
223
|
const outputPath = resolve(process.cwd(), fileName);
|
|
168
224
|
await writeFile(outputPath, fileContent.trim());
|