i18next-cli 1.49.4 → 1.49.6
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/dist/cjs/cli.js +1 -1
- package/dist/cjs/syncer.js +71 -0
- package/dist/cjs/types-generator.js +3 -3
- package/dist/esm/cli.js +1 -1
- package/dist/esm/syncer.js +71 -0
- package/dist/esm/types-generator.js +3 -3
- package/package.json +31 -31
- package/types/syncer.d.ts.map +1 -1
package/dist/cjs/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ const program = new commander.Command();
|
|
|
31
31
|
program
|
|
32
32
|
.name('i18next-cli')
|
|
33
33
|
.description('A unified, high-performance i18next CLI.')
|
|
34
|
-
.version('1.49.
|
|
34
|
+
.version('1.49.6'); // 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
|
package/dist/cjs/syncer.js
CHANGED
|
@@ -79,12 +79,66 @@ async function runSyncer(config, options = {}) {
|
|
|
79
79
|
continue;
|
|
80
80
|
}
|
|
81
81
|
const primaryKeys = nestedObject.getNestedKeys(primaryTranslations, keySeparator ?? '.');
|
|
82
|
+
const primaryKeySet = new Set(primaryKeys);
|
|
82
83
|
// 3. For each secondary language, sync the current namespace
|
|
83
84
|
for (const lang of secondaryLanguages) {
|
|
84
85
|
const secondaryPath = fileUtils.getOutputPath(output, lang, ns);
|
|
85
86
|
const fullSecondaryPath = node_path.resolve(process.cwd(), secondaryPath);
|
|
86
87
|
const existingSecondaryTranslations = await fileUtils.loadTranslationFile(fullSecondaryPath) || {};
|
|
87
88
|
const newSecondaryTranslations = {};
|
|
89
|
+
// Determine CLDR plural categories for this specific secondary locale so
|
|
90
|
+
// we can recognise locale-specific plural suffixes (e.g. `_many` for fr/es)
|
|
91
|
+
// that are not present in the primary language and must not be discarded.
|
|
92
|
+
const sep = config.extract.pluralSeparator ?? '_';
|
|
93
|
+
const localeCardinalCategories = (() => {
|
|
94
|
+
try {
|
|
95
|
+
return new Set(new Intl.PluralRules(lang, { type: 'cardinal' }).resolvedOptions().pluralCategories);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return new Set(['one', 'other']);
|
|
99
|
+
}
|
|
100
|
+
})();
|
|
101
|
+
const localeOrdinalCategories = (() => {
|
|
102
|
+
try {
|
|
103
|
+
return new Set(new Intl.PluralRules(lang, { type: 'ordinal' }).resolvedOptions().pluralCategories);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return new Set(['one', 'other', 'two', 'few']);
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
/**
|
|
110
|
+
* Returns true when `key` is a plural variant that is:
|
|
111
|
+
* 1. Valid for this locale's CLDR rules (cardinal or ordinal), AND
|
|
112
|
+
* 2. Derived from a base key that exists in the primary locale.
|
|
113
|
+
*
|
|
114
|
+
* This handles both cardinal (`title_many`) and ordinal
|
|
115
|
+
* (`place_ordinal_few`) suffixes so neither gets erased during sync.
|
|
116
|
+
*/
|
|
117
|
+
const isLocaleSpecificPluralExtension = (key) => {
|
|
118
|
+
// Cardinal: key ends with `{sep}{category}` and base key is in primary
|
|
119
|
+
for (const cat of localeCardinalCategories) {
|
|
120
|
+
const suffix = `${sep}${cat}`;
|
|
121
|
+
if (key.endsWith(suffix)) {
|
|
122
|
+
const base = key.slice(0, -suffix.length);
|
|
123
|
+
// The base itself, or any primary key that starts with `{base}{sep}`,
|
|
124
|
+
// confirms this is a plural family rooted in the primary locale.
|
|
125
|
+
if (primaryKeySet.has(base) || primaryKeys.some(pk => pk.startsWith(`${base}${sep}`))) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Ordinal: key ends with `{sep}ordinal{sep}{category}`
|
|
131
|
+
for (const cat of localeOrdinalCategories) {
|
|
132
|
+
const suffix = `${sep}ordinal${sep}${cat}`;
|
|
133
|
+
if (key.endsWith(suffix)) {
|
|
134
|
+
const base = key.slice(0, -suffix.length);
|
|
135
|
+
if (primaryKeySet.has(base) || primaryKeys.some(pk => pk.startsWith(`${base}${sep}`))) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
};
|
|
88
142
|
for (const key of primaryKeys) {
|
|
89
143
|
const primaryValue = nestedObject.getNestedValue(primaryTranslations, key, keySeparator ?? '.');
|
|
90
144
|
const existingValue = nestedObject.getNestedValue(existingSecondaryTranslations, key, keySeparator ?? '.');
|
|
@@ -92,6 +146,23 @@ async function runSyncer(config, options = {}) {
|
|
|
92
146
|
const valueToSet = existingValue ?? defaultValue.resolveDefaultValue(defaultValue$1, key, ns, lang, primaryValue);
|
|
93
147
|
nestedObject.setNestedValue(newSecondaryTranslations, key, valueToSet, keySeparator ?? '.');
|
|
94
148
|
}
|
|
149
|
+
// Preserve locale-specific plural forms that exist in the secondary file
|
|
150
|
+
// but are absent from the primary locale (e.g. `_many` for French/Spanish
|
|
151
|
+
// when the primary language is English, which has no `_many` category).
|
|
152
|
+
// Without this pass the syncer would silently drop those keys, causing
|
|
153
|
+
// `status` to immediately report them as missing.
|
|
154
|
+
const existingSecondaryKeys = nestedObject.getNestedKeys(existingSecondaryTranslations, keySeparator ?? '.');
|
|
155
|
+
for (const key of existingSecondaryKeys) {
|
|
156
|
+
if (!primaryKeySet.has(key) && isLocaleSpecificPluralExtension(key)) {
|
|
157
|
+
const existingValue = nestedObject.getNestedValue(existingSecondaryTranslations, key, keySeparator ?? '.');
|
|
158
|
+
// Only preserve non-empty values; an empty string was likely a
|
|
159
|
+
// placeholder left by a previous (buggy) sync run and should not
|
|
160
|
+
// be perpetuated.
|
|
161
|
+
if (existingValue !== '' && existingValue != null) {
|
|
162
|
+
nestedObject.setNestedValue(newSecondaryTranslations, key, existingValue, keySeparator ?? '.');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
95
166
|
// Use JSON.stringify for a reliable object comparison, regardless of format
|
|
96
167
|
const oldContent = JSON.stringify(existingSecondaryTranslations);
|
|
97
168
|
const newContent = JSON.stringify(newSecondaryTranslations);
|
|
@@ -27,10 +27,10 @@ async function loadFile(file) {
|
|
|
27
27
|
type: 'commonjs'
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
|
-
const exports = {};
|
|
31
|
-
const module = { exports };
|
|
30
|
+
const exports$1 = {};
|
|
31
|
+
const module = { exports: exports$1 };
|
|
32
32
|
const context = vm.createContext({
|
|
33
|
-
exports,
|
|
33
|
+
exports: exports$1,
|
|
34
34
|
module,
|
|
35
35
|
require: (id) => require(id),
|
|
36
36
|
console,
|
package/dist/esm/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ const program = new Command();
|
|
|
29
29
|
program
|
|
30
30
|
.name('i18next-cli')
|
|
31
31
|
.description('A unified, high-performance i18next CLI.')
|
|
32
|
-
.version('1.49.
|
|
32
|
+
.version('1.49.6'); // This string is replaced with the actual version at build time by rollup
|
|
33
33
|
// new: global config override option
|
|
34
34
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
|
|
35
35
|
program
|
package/dist/esm/syncer.js
CHANGED
|
@@ -77,12 +77,66 @@ async function runSyncer(config, options = {}) {
|
|
|
77
77
|
continue;
|
|
78
78
|
}
|
|
79
79
|
const primaryKeys = getNestedKeys(primaryTranslations, keySeparator ?? '.');
|
|
80
|
+
const primaryKeySet = new Set(primaryKeys);
|
|
80
81
|
// 3. For each secondary language, sync the current namespace
|
|
81
82
|
for (const lang of secondaryLanguages) {
|
|
82
83
|
const secondaryPath = getOutputPath(output, lang, ns);
|
|
83
84
|
const fullSecondaryPath = resolve(process.cwd(), secondaryPath);
|
|
84
85
|
const existingSecondaryTranslations = await loadTranslationFile(fullSecondaryPath) || {};
|
|
85
86
|
const newSecondaryTranslations = {};
|
|
87
|
+
// Determine CLDR plural categories for this specific secondary locale so
|
|
88
|
+
// we can recognise locale-specific plural suffixes (e.g. `_many` for fr/es)
|
|
89
|
+
// that are not present in the primary language and must not be discarded.
|
|
90
|
+
const sep = config.extract.pluralSeparator ?? '_';
|
|
91
|
+
const localeCardinalCategories = (() => {
|
|
92
|
+
try {
|
|
93
|
+
return new Set(new Intl.PluralRules(lang, { type: 'cardinal' }).resolvedOptions().pluralCategories);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return new Set(['one', 'other']);
|
|
97
|
+
}
|
|
98
|
+
})();
|
|
99
|
+
const localeOrdinalCategories = (() => {
|
|
100
|
+
try {
|
|
101
|
+
return new Set(new Intl.PluralRules(lang, { type: 'ordinal' }).resolvedOptions().pluralCategories);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return new Set(['one', 'other', 'two', 'few']);
|
|
105
|
+
}
|
|
106
|
+
})();
|
|
107
|
+
/**
|
|
108
|
+
* Returns true when `key` is a plural variant that is:
|
|
109
|
+
* 1. Valid for this locale's CLDR rules (cardinal or ordinal), AND
|
|
110
|
+
* 2. Derived from a base key that exists in the primary locale.
|
|
111
|
+
*
|
|
112
|
+
* This handles both cardinal (`title_many`) and ordinal
|
|
113
|
+
* (`place_ordinal_few`) suffixes so neither gets erased during sync.
|
|
114
|
+
*/
|
|
115
|
+
const isLocaleSpecificPluralExtension = (key) => {
|
|
116
|
+
// Cardinal: key ends with `{sep}{category}` and base key is in primary
|
|
117
|
+
for (const cat of localeCardinalCategories) {
|
|
118
|
+
const suffix = `${sep}${cat}`;
|
|
119
|
+
if (key.endsWith(suffix)) {
|
|
120
|
+
const base = key.slice(0, -suffix.length);
|
|
121
|
+
// The base itself, or any primary key that starts with `{base}{sep}`,
|
|
122
|
+
// confirms this is a plural family rooted in the primary locale.
|
|
123
|
+
if (primaryKeySet.has(base) || primaryKeys.some(pk => pk.startsWith(`${base}${sep}`))) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Ordinal: key ends with `{sep}ordinal{sep}{category}`
|
|
129
|
+
for (const cat of localeOrdinalCategories) {
|
|
130
|
+
const suffix = `${sep}ordinal${sep}${cat}`;
|
|
131
|
+
if (key.endsWith(suffix)) {
|
|
132
|
+
const base = key.slice(0, -suffix.length);
|
|
133
|
+
if (primaryKeySet.has(base) || primaryKeys.some(pk => pk.startsWith(`${base}${sep}`))) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
};
|
|
86
140
|
for (const key of primaryKeys) {
|
|
87
141
|
const primaryValue = getNestedValue(primaryTranslations, key, keySeparator ?? '.');
|
|
88
142
|
const existingValue = getNestedValue(existingSecondaryTranslations, key, keySeparator ?? '.');
|
|
@@ -90,6 +144,23 @@ async function runSyncer(config, options = {}) {
|
|
|
90
144
|
const valueToSet = existingValue ?? resolveDefaultValue(defaultValue, key, ns, lang, primaryValue);
|
|
91
145
|
setNestedValue(newSecondaryTranslations, key, valueToSet, keySeparator ?? '.');
|
|
92
146
|
}
|
|
147
|
+
// Preserve locale-specific plural forms that exist in the secondary file
|
|
148
|
+
// but are absent from the primary locale (e.g. `_many` for French/Spanish
|
|
149
|
+
// when the primary language is English, which has no `_many` category).
|
|
150
|
+
// Without this pass the syncer would silently drop those keys, causing
|
|
151
|
+
// `status` to immediately report them as missing.
|
|
152
|
+
const existingSecondaryKeys = getNestedKeys(existingSecondaryTranslations, keySeparator ?? '.');
|
|
153
|
+
for (const key of existingSecondaryKeys) {
|
|
154
|
+
if (!primaryKeySet.has(key) && isLocaleSpecificPluralExtension(key)) {
|
|
155
|
+
const existingValue = getNestedValue(existingSecondaryTranslations, key, keySeparator ?? '.');
|
|
156
|
+
// Only preserve non-empty values; an empty string was likely a
|
|
157
|
+
// placeholder left by a previous (buggy) sync run and should not
|
|
158
|
+
// be perpetuated.
|
|
159
|
+
if (existingValue !== '' && existingValue != null) {
|
|
160
|
+
setNestedValue(newSecondaryTranslations, key, existingValue, keySeparator ?? '.');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
93
164
|
// Use JSON.stringify for a reliable object comparison, regardless of format
|
|
94
165
|
const oldContent = JSON.stringify(existingSecondaryTranslations);
|
|
95
166
|
const newContent = JSON.stringify(newSecondaryTranslations);
|
|
@@ -25,10 +25,10 @@ async function loadFile(file) {
|
|
|
25
25
|
type: 'commonjs'
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
|
-
const exports = {};
|
|
29
|
-
const module = { exports };
|
|
28
|
+
const exports$1 = {};
|
|
29
|
+
const module = { exports: exports$1 };
|
|
30
30
|
const context = vm.createContext({
|
|
31
|
-
exports,
|
|
31
|
+
exports: exports$1,
|
|
32
32
|
module,
|
|
33
33
|
require: (id) => require(id),
|
|
34
34
|
console,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "i18next-cli",
|
|
3
|
-
"version": "1.49.
|
|
3
|
+
"version": "1.49.6",
|
|
4
4
|
"description": "A unified, high-performance i18next CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -52,39 +52,39 @@
|
|
|
52
52
|
"url": "https://github.com/i18next/i18next-cli/issues"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@rollup/plugin-replace": "6.0.3",
|
|
56
|
-
"@rollup/plugin-terser": "0.
|
|
57
|
-
"@types/inquirer": "9.0.9",
|
|
58
|
-
"@types/node": "25.
|
|
59
|
-
"@types/react": "19.2.14",
|
|
60
|
-
"@typescript-eslint/parser": "^8.
|
|
61
|
-
"@vitest/coverage-v8": "4.0.18",
|
|
62
|
-
"eslint": "9.39.2",
|
|
55
|
+
"@rollup/plugin-replace": "^6.0.3",
|
|
56
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
57
|
+
"@types/inquirer": "^9.0.9",
|
|
58
|
+
"@types/node": "^25.4.0",
|
|
59
|
+
"@types/react": "^19.2.14",
|
|
60
|
+
"@typescript-eslint/parser": "^8.57.0",
|
|
61
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
62
|
+
"eslint": "^9.39.2",
|
|
63
63
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
64
|
-
"eslint-plugin-import": "2.32.0",
|
|
65
|
-
"memfs": "4.56.11",
|
|
66
|
-
"neostandard": "0.13.0",
|
|
67
|
-
"rollup-plugin-typescript2": "0.36.0",
|
|
68
|
-
"typescript": "5.9.3",
|
|
69
|
-
"unplugin-swc": "1.5.9",
|
|
70
|
-
"vitest": "4.0.18"
|
|
64
|
+
"eslint-plugin-import": "^2.32.0",
|
|
65
|
+
"memfs": "^4.56.11",
|
|
66
|
+
"neostandard": "^0.13.0",
|
|
67
|
+
"rollup-plugin-typescript2": "^0.36.0",
|
|
68
|
+
"typescript": "^5.9.3",
|
|
69
|
+
"unplugin-swc": "^1.5.9",
|
|
70
|
+
"vitest": "^4.0.18"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@croct/json5-parser": "0.2.2",
|
|
74
|
-
"@swc/core": "1.15.18",
|
|
75
|
-
"chokidar": "5.0.0",
|
|
76
|
-
"commander": "14.0.3",
|
|
77
|
-
"execa": "9.6.1",
|
|
78
|
-
"glob": "13.0.6",
|
|
79
|
-
"i18next-resources-for-ts": "2.0.
|
|
80
|
-
"inquirer": "13.3.0",
|
|
81
|
-
"jiti": "2.6.1",
|
|
82
|
-
"jsonc-parser": "3.3.1",
|
|
83
|
-
"magic-string": "0.30.21",
|
|
84
|
-
"minimatch": "10.2.4",
|
|
85
|
-
"ora": "9.3.0",
|
|
73
|
+
"@croct/json5-parser": "^0.2.2",
|
|
74
|
+
"@swc/core": "^1.15.18",
|
|
75
|
+
"chokidar": "^5.0.0",
|
|
76
|
+
"commander": "^14.0.3",
|
|
77
|
+
"execa": "^9.6.1",
|
|
78
|
+
"glob": "^13.0.6",
|
|
79
|
+
"i18next-resources-for-ts": "^2.0.1",
|
|
80
|
+
"inquirer": "^13.3.0",
|
|
81
|
+
"jiti": "^2.6.1",
|
|
82
|
+
"jsonc-parser": "^3.3.1",
|
|
83
|
+
"magic-string": "^0.30.21",
|
|
84
|
+
"minimatch": "^10.2.4",
|
|
85
|
+
"ora": "^9.3.0",
|
|
86
86
|
"react": "^19.2.4",
|
|
87
|
-
"react-i18next": "^16.5.
|
|
88
|
-
"yaml": "2.8.2"
|
|
87
|
+
"react-i18next": "^16.5.6",
|
|
88
|
+
"yaml": "^2.8.2"
|
|
89
89
|
}
|
|
90
90
|
}
|
package/types/syncer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../src/syncer.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAM9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,
|
|
1
|
+
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../src/syncer.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAM9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,iBA0KnD"}
|