i18next-cli 0.9.17 → 0.9.19
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/CHANGELOG.md +11 -1
- package/README.md +15 -0
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/core/translation-manager.js +1 -1
- package/dist/cjs/extractor/parsers/ast-visitors.js +1 -1
- package/dist/cjs/status.js +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/core/translation-manager.js +1 -1
- package/dist/esm/extractor/parsers/ast-visitors.js +1 -1
- package/dist/esm/status.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/extractor/core/translation-manager.ts +23 -35
- package/src/extractor/parsers/ast-visitors.ts +95 -18
- package/src/status.ts +43 -28
- package/src/types.ts +3 -0
- package/types/extractor/core/translation-manager.d.ts.map +1 -1
- package/types/extractor/parsers/ast-visitors.d.ts +31 -0
- package/types/extractor/parsers/ast-visitors.d.ts.map +1 -1
- package/types/status.d.ts.map +1 -1
- package/types/types.d.ts +2 -0
- package/types/types.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,10 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [1.0.0](https://github.com/i18next/i18next-cli/compare/v0.9.
|
|
8
|
+
## [1.0.0](https://github.com/i18next/i18next-cli/compare/v0.9.19...v1.0.0) - 2025-xx-yy
|
|
9
9
|
|
|
10
10
|
- not yet released
|
|
11
11
|
|
|
12
|
+
## [0.9.19](https://github.com/i18next/i18next-cli/compare/v0.9.18...v0.9.19) - 2025-09-30
|
|
13
|
+
|
|
14
|
+
- **Status Command:** Greatly improved the accuracy of the translation status report for plural keys. The command now calculates the total number of required keys for each language based on that specific language's pluralization rules (e.g., 2 forms for English, 6 for Arabic), rather than incorrectly using the primary language's rules for all locales.
|
|
15
|
+
- **Extractor:** Corrected the logic for ordinal plurals and default value fallbacks. The extractor now recognizes keys with an `_ordinal` suffix as ordinal plurals. The fallback hierarchy for all plural default values (e.g., `defaultValue_one`, `defaultValue_other`) now correctly matches i18next's behavior.
|
|
16
|
+
|
|
17
|
+
## [0.9.18](https://github.com/i18next/i18next-cli/compare/v0.9.17...v0.9.18) - 2025-09-30
|
|
18
|
+
|
|
19
|
+
- **Extractor:** Fixed a bug where translation keys were not found in custom functions that were part of an object (e.g., `i18n.t(...)`). The `functions` configuration option now correctly handles member expressions in addition to simple function names. [#10](https://github.com/i18next/i18next-cli/issues/10)
|
|
20
|
+
- **Extractor:** Fixed a critical bug where the `extract` command would incorrectly overwrite existing translations in secondary languages when using the `mergeNamespaces: true` option. The fix also resolves a related issue where unused keys were not being correctly pruned from the primary language file in the same scenario. The translation manager logic is now more robust for both merged and non-merged configurations. [#11](https://github.com/i18next/i18next-cli/issues/11)
|
|
21
|
+
|
|
12
22
|
## [0.9.17](https://github.com/i18next/i18next-cli/compare/v0.9.16...v0.9.17) - 2025-09-29
|
|
13
23
|
|
|
14
24
|
- **Extractor:** Fixed a bug where namespace and `keyPrefix` information from custom `useTranslationNames` hooks was ignored when the `t` function was assigned directly to a variable (e.g., `let t = myHook()`). The extractor now correctly handles this pattern in addition to destructuring assignments. [#9](https://github.com/i18next/i18next-cli/issues/9)
|
package/README.md
CHANGED
|
@@ -244,6 +244,8 @@ npx i18next-cli locize-sync [options]
|
|
|
244
244
|
|
|
245
245
|
The configuration file supports both TypeScript (`.ts`) and JavaScript (`.js`) formats. Use the `defineConfig` helper for type safety and IntelliSense.
|
|
246
246
|
|
|
247
|
+
> **💡 No Installation Required?** If you don't want to install `i18next-cli` as a dependency, you can skip the `defineConfig` helper and return a plain JavaScript object or JSON instead. The `defineConfig` function is purely for TypeScript support and doesn't affect functionality.
|
|
248
|
+
|
|
247
249
|
### Basic Configuration
|
|
248
250
|
|
|
249
251
|
```typescript
|
|
@@ -259,6 +261,19 @@ export default defineConfig({
|
|
|
259
261
|
});
|
|
260
262
|
```
|
|
261
263
|
|
|
264
|
+
**Alternative without local installation:**
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
// i18next.config.js
|
|
268
|
+
export default {
|
|
269
|
+
locales: ['en', 'de', 'fr'],
|
|
270
|
+
extract: {
|
|
271
|
+
input: ['src/**/*.{ts,tsx,js,jsx}'],
|
|
272
|
+
output: 'locales/{{language}}/{{namespace}}.json',
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
```
|
|
276
|
+
|
|
262
277
|
### Advanced Configuration
|
|
263
278
|
|
|
264
279
|
```typescript
|
package/dist/cjs/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.
|
|
2
|
+
"use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.19"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a);e.ci&&t&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let o=await i.loadConfig();if(!o){console.log(n.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),o=e}await g.runStatus(o,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const n=await i.ensureConfig(),a=()=>c.runTypesGenerator(n);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(n.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await l.runMigrator()}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let e=await i.loadConfig();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await d.runLinter(e)}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t=require("node:path"),e=require("../../utils/nested-object.js"),s=require("../../utils/file-utils.js");function a(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}exports.getTranslations=async function(n,
|
|
1
|
+
"use strict";var t=require("node:path"),e=require("../../utils/nested-object.js"),s=require("../../utils/file-utils.js");function a(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}exports.getTranslations=async function(r,n,o){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(t=>t!==o?.extract?.primaryLanguage);const c=o.extract.primaryLanguage,l=o.extract.defaultNS??"translation",u=o.extract.keySeparator??".",i=[...o.extract.preservePatterns||[]],p=o.extract.mergeNamespaces??!1,f=o.extract.indentation??2;for(const t of n)i.push(`${t}.*`);const g=i.map(a),d=new Map;for(const t of r.values()){const e=t.ns||l;d.has(e)||d.set(e,[]),d.get(e).push(t)}const x=[];for(const a of o.locales){const r=p?await s.loadTranslationFile(t.resolve(process.cwd(),s.getOutputPath(o.extract.output,a)))||{}:null,n={};for(const[l,i]of d.entries()){const d=s.getOutputPath(o.extract.output,a,p?void 0:l),y=t.resolve(process.cwd(),d),h=p?r?.[l]||{}:await s.loadTranslationFile(y)||{},N={},w=e.getNestedKeys(h,u);for(const t of w)if(g.some(e=>e.test(t))){const s=e.getNestedValue(h,t,u);e.setNestedValue(N,t,s,u)}const m=!1===o.extract.sort?i:[...i].sort((t,e)=>t.key.localeCompare(e.key));for(const{key:t,defaultValue:s}of m){const r=e.getNestedValue(h,t,u)??(a===c?s:o.extract.defaultValue??"");e.setNestedValue(N,t,r,u)}if(p)n[l]=N;else{const t=JSON.stringify(h,null,f),e=JSON.stringify(N,null,f);x.push({path:y,updated:e!==t,newTranslations:N,existingTranslations:h})}}if(p){const e=s.getOutputPath(o.extract.output,a),c=t.resolve(process.cwd(),e),l=JSON.stringify(r,null,f),u=JSON.stringify(n,null,f);x.push({path:c,updated:u!==l,newTranslations:n,existingTranslations:r||{}})}}return x};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("./jsx-parser.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const r=n.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,t,n){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){r="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const i=t.arguments?.[n.nsArg]?.expression;let s;"StringLiteral"===i?.type?s=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(s=i.elements[0].expression.value);const a=t.arguments?.[n.keyPrefixArg]?.expression;let o;if("ObjectExpression"===a?.type){const e=this.getObjectPropValue(a,"keyPrefix");o="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:s,keyPrefix:o})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,r=t.arguments,i=r[1]?.expression,s=r[2]?.expression,a="StringLiteral"===i?.type?i.value:void 0,o="StringLiteral"===s?.type?s.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const t=e.callee;if(
|
|
1
|
+
"use strict";var e=require("./jsx-parser.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const r=n.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,t,n){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){r="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const i=t.arguments?.[n.nsArg]?.expression;let s;"StringLiteral"===i?.type?s=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(s=i.elements[0].expression.value);const a=t.arguments?.[n.keyPrefixArg]?.expression;let o;if("ObjectExpression"===a?.type){const e=this.getObjectPropValue(a,"keyPrefix");o="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:s,keyPrefix:o})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,r=t.arguments,i=r[1]?.expression,s=r[2]?.expression,a="StringLiteral"===i?.type?i.value:void 0,o="StringLiteral"===s?.type?s.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const t=this.getFunctionName(e.callee);if(!t)return;const n=this.getVarFromScope(t);if(!((this.config.extract.functions||["t"]).includes(t)||void 0!==n)||0===e.arguments.length)return;const r=e.arguments[0].expression,i=[];if("StringLiteral"===r.type)i.push(r.value);else if("ArrowFunctionExpression"===r.type){const e=this.extractKeyFromSelector(r);e&&i.push(e)}else if("ArrayExpression"===r.type)for(const e of r.elements)"StringLiteral"===e?.expression.type&&i.push(e.expression.value);if(0===i.length)return;let s=!1;const a=this.config.extract.pluralSeparator??"_";for(let e=0;e<i.length;e++)i[e].endsWith(`${a}ordinal`)&&(s=!0,i[e]=i[e].slice(0,-8));let o,l;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?l=t:"StringLiteral"===t.type&&(o=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(l=t)}const p=l?this.getObjectPropValue(l,"defaultValue"):void 0,u="string"==typeof p?p:o;for(let e=0;e<i.length;e++){let t,r=i[e];if(l){const e=this.getObjectPropValue(l,"ns");"string"==typeof e&&(t=e)}!t&&n?.defaultNs&&(t=n.defaultNs);const a=this.config.extract.nsSeparator??":";if(!t&&a&&r.includes(a)){const e=r.split(a);t=e.shift(),r=e.join(a)}t||(t=this.config.extract.defaultNS);let o=r;if(n?.keyPrefix){const e=this.config.extract.keySeparator??".";o=`${n.keyPrefix}${e}${r}`}const p=e===i.length-1&&u||r;if(l){const e=this.getObjectProperty(l,"context");if("ConditionalExpression"===e?.value?.type){const n=this.resolvePossibleStringValues(e.value),r=this.config.extract.contextSeparator??"_";if(n.length>0){n.forEach(e=>{this.pluginContext.addKey({key:`${o}${r}${e}`,ns:t,defaultValue:p})}),this.pluginContext.addKey({key:o,ns:t,defaultValue:p});continue}}const n=this.getObjectPropValue(l,"context");if("string"==typeof n&&n){const e=this.config.extract.contextSeparator??"_";this.pluginContext.addKey({key:`${o}${e}${n}`,ns:t,defaultValue:p});continue}const r=void 0!==this.getObjectPropValue(l,"count"),i=!0===this.getObjectPropValue(l,"ordinal");if(r||s){this.handlePluralKeys(o,t,l,i||s);continue}!0===this.getObjectPropValue(l,"returnObjects")&&this.objectKeys.add(o)}this.pluginContext.addKey({key:o,ns:t,defaultValue:p})}}handlePluralKeys(e,t,n,r){try{const i=r?"ordinal":"cardinal",s=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,a=this.config.extract.pluralSeparator??"_",o=this.getObjectPropValue(n,"defaultValue"),l=this.getObjectPropValue(n,"defaultValue_other"),p=this.getObjectPropValue(n,"defaultValue_ordinal_other");for(const i of s){const s=r?`defaultValue_ordinal_${i}`:`defaultValue_${i}`,u=this.getObjectPropValue(n,s);let c;c="string"==typeof u?u:"one"===i&&"string"==typeof o?o:r&&"string"==typeof p?p:r||"string"!=typeof l?"string"==typeof o?o:e:l;const f=r?`${e}${a}ordinal${a}${i}`:`${e}${a}${i}`;this.pluginContext.addKey({key:f,ns:t,defaultValue:c,hasCount:!0,isOrdinal:r})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=this.getObjectPropValue(n,"defaultValue");this.pluginContext.addKey({key:e,ns:t,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,n){try{const r=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,i=this.config.extract.pluralSeparator??"_";for(const s of r)this.pluginContext.addKey({key:`${e}${i}${s}`,ns:n,defaultValue:t,hasCount:!0})}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:n,defaultValue:t})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e.extractFromTransComponent(t,this.config);if(n){if(!n.ns){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===e?.type&&"JSXExpressionContainer"===e.value?.type&&"Identifier"===e.value.expression.type){const t=e.value.expression.value,r=this.getVarFromScope(t);r?.defaultNs&&(n.ns=r.defaultNs)}}if(n.ns||(n.ns=this.config.extract.defaultNS),n.contextExpression){const e=this.resolvePossibleStringValues(n.contextExpression),t=this.config.extract.contextSeparator??"_";if(e.length>0){for(const r of e)this.pluginContext.addKey({key:`${n.key}${t}${r}`,ns:n.ns,defaultValue:n.defaultValue});this.pluginContext.addKey(n)}}else n.hasCount?this.handleSimplePluralKeys(n.key,n.defaultValue,n.ns):this.pluginContext.addKey(n)}}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}getObjectPropValue(e,t){const n=e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t));if("KeyValueProperty"===n?.type){const e=n.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const r=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)r.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;r.unshift(e.expression.value)}n=n.object}if(r.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return r.join(t)}return null}resolvePossibleStringValues(e){if("StringLiteral"===e.type)return[e.value];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent),...this.resolvePossibleStringValues(e.alternate)]}return"Identifier"===e.type&&e.value,[]}getObjectProperty(e,t){return e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t))}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}return"Identifier"!==n.type?null:(t.unshift(n.value),t.join("."))}return null}};
|
package/dist/cjs/status.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("chalk"),o=require("ora"),t=require("node:path"),a=require("./extractor/core/key-finder.js"),s=require("./utils/nested-object.js"),n=require("./utils/file-utils.js");function l(o,t,a){const s=a>0?Math.round(t/a*100):100,n=r(s);console.log(`${e.bold(o)}: ${n} ${s}% (${t}/${a})`)}function r(o){const t=Math.
|
|
1
|
+
"use strict";var e=require("chalk"),o=require("ora"),t=require("node:path"),a=require("./extractor/core/key-finder.js"),s=require("./utils/nested-object.js"),n=require("./utils/file-utils.js");function l(o,t,a){const s=a>0?Math.round(t/a*100):100,n=r(s);console.log(`${e.bold(o)}: ${n} ${s}% (${t}/${a})`)}function r(o){const t=Math.floor(o/100*20),a=20-t;return`[${e.green("".padStart(t,"■"))}${"".padStart(a,"□")}]`}function c(){console.log(e.yellow.bold("\n✨ Take your localization to the next level!")),console.log("Manage translations with your team in the cloud with locize => https://www.locize.com/docs/getting-started"),console.log(`Run ${e.cyan("npx i18next-cli locize-migrate")} to get started.`)}exports.runStatus=async function(i,u={}){i.extract.primaryLanguage||=i.locales[0]||"en",i.extract.secondaryLanguages||=i.locales.filter(e=>e!==i?.extract?.primaryLanguage);const d=o("Analyzing project localization status...\n").start();try{const o=await async function(e){e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(o=>o!==e?.extract?.primaryLanguage);const{allKeys:o}=await a.findKeys(e),{secondaryLanguages:l,keySeparator:r=".",defaultNS:c="translation",mergeNamespaces:i=!1,pluralSeparator:u="_"}=e.extract,d=new Map;for(const e of o.values()){const o=e.ns||c;d.has(o)||d.set(o,[]),d.get(o).push(e)}const y={totalBaseKeys:o.size,keysByNs:d,locales:new Map};for(const o of l){let a=0,l=0;const c=new Map,g=i?await n.loadTranslationFile(t.resolve(process.cwd(),n.getOutputPath(e.extract.output,o)))||{}:null;for(const[y,f]of d.entries()){const d=i?g?.[y]||{}:await n.loadTranslationFile(t.resolve(process.cwd(),n.getOutputPath(e.extract.output,o,y)))||{};let p=0,$=0;const m=[];for(const{key:e,hasCount:t,isOrdinal:a}of f)if(t){const t=a?"ordinal":"cardinal",n=new Intl.PluralRules(o,{type:t}).resolvedOptions().pluralCategories;for(const o of n){$++;const t=a?`${e}${u}ordinal${u}${o}`:`${e}${u}${o}`,n=!!s.getNestedValue(d,t,r??".");n&&p++,m.push({key:t,isTranslated:n})}}else{$++;const o=!!s.getNestedValue(d,e,r??".");o&&p++,m.push({key:e,isTranslated:o})}c.set(y,{totalKeys:$,translatedKeys:p,keyDetails:m}),a+=p,l+=$}y.locales.set(o,{totalKeys:l,totalTranslated:a,namespaces:c})}return y}(i);d.succeed("Analysis complete."),function(o,t,a){a.detail?function(o,t,a,s){if(a===t.extract.primaryLanguage)return void console.log(e.yellow(`Locale "${a}" is the primary language. All keys are considered present.`));if(!t.locales.includes(a))return void console.error(e.red(`Error: Locale "${a}" is not defined in your configuration.`));const n=o.locales.get(a);if(!n)return void console.error(e.red(`Error: Locale "${a}" is not a valid secondary language.`));console.log(e.bold(`\nKey Status for "${e.cyan(a)}":`));const r=Array.from(o.keysByNs.values()).flat().length;l("Overall",n.totalTranslated,n.totalKeys);const i=s?[s]:Array.from(n.namespaces.keys()).sort();for(const o of i){const t=n.namespaces.get(o);t&&(console.log(e.cyan.bold(`\nNamespace: ${o}`)),l("Namespace Progress",t.translatedKeys,t.totalKeys),t.keyDetails.forEach(({key:o,isTranslated:t})=>{const a=t?e.green("✓"):e.red("✗");console.log(` ${a} ${o}`)}))}const u=r-n.totalTranslated;u>0?console.log(e.yellow.bold(`\nSummary: Found ${u} missing translations for "${a}".`)):console.log(e.green.bold(`\nSummary: 🎉 All keys are translated for "${a}".`));c()}(o,t,a.detail,a.namespace):a.namespace?function(o,t,a){const s=o.keysByNs.get(a);if(!s)return void console.error(e.red(`Error: Namespace "${a}" was not found in your source code.`));console.log(e.cyan.bold(`\nStatus for Namespace: "${a}"`)),console.log("------------------------");for(const[e,t]of o.locales.entries()){const o=t.namespaces.get(a);if(o){const t=o.totalKeys>0?Math.round(o.translatedKeys/o.totalKeys*100):100,a=r(t);console.log(`- ${e}: ${a} ${t}% (${o.translatedKeys}/${o.totalKeys} keys)`)}}c()}(o,0,a.namespace):function(o,t){const{primaryLanguage:a}=t.extract;console.log(e.cyan.bold("\ni18next Project Status")),console.log("------------------------"),console.log(`🔑 Keys Found: ${e.bold(o.totalBaseKeys)}`),console.log(`📚 Namespaces Found: ${e.bold(o.keysByNs.size)}`),console.log(`🌍 Locales: ${e.bold(t.locales.join(", "))}`),console.log(`✅ Primary Language: ${e.bold(a)}`),console.log("\nTranslation Progress:");for(const[e,t]of o.locales.entries()){const o=t.totalKeys>0?Math.round(t.totalTranslated/t.totalKeys*100):100,a=r(o);console.log(`- ${e}: ${a} ${o}% (${t.totalTranslated}/${t.totalKeys} keys)`)}c()}(o,t)}(o,i,u)}catch(e){d.fail("Failed to generate status report."),console.error(e)}};
|
package/dist/esm/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as o}from"commander";import t from"chokidar";import{glob as e}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.
|
|
2
|
+
import{Command as o}from"commander";import t from"chokidar";import{glob as e}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.19"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async o=>{const a=await n(),c=async()=>{const t=await r(a);o.ci&&t&&(console.error(i.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(i.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${i.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),o.watch){console.log("\nWatching for changes...");t.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(o,t)=>{let e=await a();if(!e){console.log(i.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),e=o}await f(e,{detail:o,namespace:t.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async o=>{const i=await n(),a=()=>s(i);if(await a(),o.watch){console.log("\nWatching for changes...");t.watch(await e(i.types?.input||[]),{persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const o=await n();await l(o)}),w.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await m()}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let o=await a();if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),o=t}await d(o)}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async o=>{const t=await n();await g(t,o)}),w.command("locize-download").description("Download all translations from your locize project.").action(async o=>{const t=await n();await u(t,o)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async o=>{const t=await n();await y(t,o)}),w.parse(process.argv);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolve as t}from"node:path";import{getNestedKeys as e,getNestedValue as
|
|
1
|
+
import{resolve as t}from"node:path";import{getNestedKeys as e,getNestedValue as s,setNestedValue as a}from"../../utils/nested-object.js";import{loadTranslationFile as n,getOutputPath as o}from"../../utils/file-utils.js";function r(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}async function c(c,i,l){l.extract.primaryLanguage||=l.locales[0]||"en",l.extract.secondaryLanguages||=l.locales.filter(t=>t!==l?.extract?.primaryLanguage);const u=l.extract.primaryLanguage,p=l.extract.defaultNS??"translation",f=l.extract.keySeparator??".",g=[...l.extract.preservePatterns||[]],x=l.extract.mergeNamespaces??!1,d=l.extract.indentation??2;for(const t of i)g.push(`${t}.*`);const m=g.map(r),y=new Map;for(const t of c.values()){const e=t.ns||p;y.has(e)||y.set(e,[]),y.get(e).push(t)}const w=[];for(const r of l.locales){const c=x?await n(t(process.cwd(),o(l.extract.output,r)))||{}:null,i={};for(const[p,g]of y.entries()){const y=o(l.extract.output,r,x?void 0:p),h=t(process.cwd(),y),N=x?c?.[p]||{}:await n(h)||{},S={},$=e(N,f);for(const t of $)if(m.some(e=>e.test(t))){const e=s(N,t,f);a(S,t,e,f)}const k=!1===l.extract.sort?g:[...g].sort((t,e)=>t.key.localeCompare(e.key));for(const{key:t,defaultValue:e}of k){const n=s(N,t,f)??(r===u?e:l.extract.defaultValue??"");a(S,t,n,f)}if(x)i[p]=S;else{const t=JSON.stringify(N,null,d),e=JSON.stringify(S,null,d);w.push({path:h,updated:e!==t,newTranslations:S,existingTranslations:N})}}if(x){const e=o(l.extract.output,r),s=t(process.cwd(),e),a=JSON.stringify(c,null,d),n=JSON.stringify(i,null,d);w.push({path:s,updated:n!==a,newTranslations:i,existingTranslations:c||{}})}}return w}export{c as getTranslations};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{extractFromTransComponent as e}from"./jsx-parser.js";class t{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const
|
|
1
|
+
import{extractFromTransComponent as e}from"./jsx-parser.js";class t{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const r=n.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,t,n){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){r="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const i=t.arguments?.[n.nsArg]?.expression;let s;"StringLiteral"===i?.type?s=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(s=i.elements[0].expression.value);const a=t.arguments?.[n.keyPrefixArg]?.expression;let o;if("ObjectExpression"===a?.type){const e=this.getObjectPropValue(a,"keyPrefix");o="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:s,keyPrefix:o})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,r=t.arguments,i=r[1]?.expression,s=r[2]?.expression,a="StringLiteral"===i?.type?i.value:void 0,o="StringLiteral"===s?.type?s.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const t=this.getFunctionName(e.callee);if(!t)return;const n=this.getVarFromScope(t);if(!((this.config.extract.functions||["t"]).includes(t)||void 0!==n)||0===e.arguments.length)return;const r=e.arguments[0].expression,i=[];if("StringLiteral"===r.type)i.push(r.value);else if("ArrowFunctionExpression"===r.type){const e=this.extractKeyFromSelector(r);e&&i.push(e)}else if("ArrayExpression"===r.type)for(const e of r.elements)"StringLiteral"===e?.expression.type&&i.push(e.expression.value);if(0===i.length)return;let s=!1;const a=this.config.extract.pluralSeparator??"_";for(let e=0;e<i.length;e++)i[e].endsWith(`${a}ordinal`)&&(s=!0,i[e]=i[e].slice(0,-8));let o,l;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?l=t:"StringLiteral"===t.type&&(o=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(l=t)}const p=l?this.getObjectPropValue(l,"defaultValue"):void 0,u="string"==typeof p?p:o;for(let e=0;e<i.length;e++){let t,r=i[e];if(l){const e=this.getObjectPropValue(l,"ns");"string"==typeof e&&(t=e)}!t&&n?.defaultNs&&(t=n.defaultNs);const a=this.config.extract.nsSeparator??":";if(!t&&a&&r.includes(a)){const e=r.split(a);t=e.shift(),r=e.join(a)}t||(t=this.config.extract.defaultNS);let o=r;if(n?.keyPrefix){const e=this.config.extract.keySeparator??".";o=`${n.keyPrefix}${e}${r}`}const p=e===i.length-1&&u||r;if(l){const e=this.getObjectProperty(l,"context");if("ConditionalExpression"===e?.value?.type){const n=this.resolvePossibleStringValues(e.value),r=this.config.extract.contextSeparator??"_";if(n.length>0){n.forEach(e=>{this.pluginContext.addKey({key:`${o}${r}${e}`,ns:t,defaultValue:p})}),this.pluginContext.addKey({key:o,ns:t,defaultValue:p});continue}}const n=this.getObjectPropValue(l,"context");if("string"==typeof n&&n){const e=this.config.extract.contextSeparator??"_";this.pluginContext.addKey({key:`${o}${e}${n}`,ns:t,defaultValue:p});continue}const r=void 0!==this.getObjectPropValue(l,"count"),i=!0===this.getObjectPropValue(l,"ordinal");if(r||s){this.handlePluralKeys(o,t,l,i||s);continue}!0===this.getObjectPropValue(l,"returnObjects")&&this.objectKeys.add(o)}this.pluginContext.addKey({key:o,ns:t,defaultValue:p})}}handlePluralKeys(e,t,n,r){try{const i=r?"ordinal":"cardinal",s=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,a=this.config.extract.pluralSeparator??"_",o=this.getObjectPropValue(n,"defaultValue"),l=this.getObjectPropValue(n,"defaultValue_other"),p=this.getObjectPropValue(n,"defaultValue_ordinal_other");for(const i of s){const s=r?`defaultValue_ordinal_${i}`:`defaultValue_${i}`,u=this.getObjectPropValue(n,s);let c;c="string"==typeof u?u:"one"===i&&"string"==typeof o?o:r&&"string"==typeof p?p:r||"string"!=typeof l?"string"==typeof o?o:e:l;const f=r?`${e}${a}ordinal${a}${i}`:`${e}${a}${i}`;this.pluginContext.addKey({key:f,ns:t,defaultValue:c,hasCount:!0,isOrdinal:r})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=this.getObjectPropValue(n,"defaultValue");this.pluginContext.addKey({key:e,ns:t,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,n){try{const r=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,i=this.config.extract.pluralSeparator??"_";for(const s of r)this.pluginContext.addKey({key:`${e}${i}${s}`,ns:n,defaultValue:t,hasCount:!0})}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:n,defaultValue:t})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e(t,this.config);if(n){if(!n.ns){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===e?.type&&"JSXExpressionContainer"===e.value?.type&&"Identifier"===e.value.expression.type){const t=e.value.expression.value,r=this.getVarFromScope(t);r?.defaultNs&&(n.ns=r.defaultNs)}}if(n.ns||(n.ns=this.config.extract.defaultNS),n.contextExpression){const e=this.resolvePossibleStringValues(n.contextExpression),t=this.config.extract.contextSeparator??"_";if(e.length>0){for(const r of e)this.pluginContext.addKey({key:`${n.key}${t}${r}`,ns:n.ns,defaultValue:n.defaultValue});this.pluginContext.addKey(n)}}else n.hasCount?this.handleSimplePluralKeys(n.key,n.defaultValue,n.ns):this.pluginContext.addKey(n)}}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}getObjectPropValue(e,t){const n=e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t));if("KeyValueProperty"===n?.type){const e=n.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const r=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)r.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;r.unshift(e.expression.value)}n=n.object}if(r.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return r.join(t)}return null}resolvePossibleStringValues(e){if("StringLiteral"===e.type)return[e.value];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent),...this.resolvePossibleStringValues(e.alternate)]}return"Identifier"===e.type&&e.value,[]}getObjectProperty(e,t){return e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t))}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}return"Identifier"!==n.type?null:(t.unshift(n.value),t.join("."))}return null}}export{t as ASTVisitors};
|
package/dist/esm/status.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import o from"chalk";import e from"ora";import{resolve as t}from"node:path";import{findKeys as a}from"./extractor/core/key-finder.js";import{getNestedValue as s}from"./utils/nested-object.js";import{loadTranslationFile as n,getOutputPath as l}from"./utils/file-utils.js";async function r(r,d={}){r.extract.primaryLanguage||=r.locales[0]||"en",r.extract.secondaryLanguages||=r.locales.filter(o=>o!==r?.extract?.primaryLanguage);const
|
|
1
|
+
import o from"chalk";import e from"ora";import{resolve as t}from"node:path";import{findKeys as a}from"./extractor/core/key-finder.js";import{getNestedValue as s}from"./utils/nested-object.js";import{loadTranslationFile as n,getOutputPath as l}from"./utils/file-utils.js";async function r(r,d={}){r.extract.primaryLanguage||=r.locales[0]||"en",r.extract.secondaryLanguages||=r.locales.filter(o=>o!==r?.extract?.primaryLanguage);const u=e("Analyzing project localization status...\n").start();try{const e=await async function(o){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(e=>e!==o?.extract?.primaryLanguage);const{allKeys:e}=await a(o),{secondaryLanguages:r,keySeparator:c=".",defaultNS:i="translation",mergeNamespaces:y=!1,pluralSeparator:d="_"}=o.extract,u=new Map;for(const o of e.values()){const e=o.ns||i;u.has(e)||u.set(e,[]),u.get(e).push(o)}const g={totalBaseKeys:e.size,keysByNs:u,locales:new Map};for(const e of r){let a=0,r=0;const i=new Map,f=y?await n(t(process.cwd(),l(o.extract.output,e)))||{}:null;for(const[g,p]of u.entries()){const u=y?f?.[g]||{}:await n(t(process.cwd(),l(o.extract.output,e,g)))||{};let m=0,$=0;const h=[];for(const{key:o,hasCount:t,isOrdinal:a}of p)if(t){const t=a?"ordinal":"cardinal",n=new Intl.PluralRules(e,{type:t}).resolvedOptions().pluralCategories;for(const e of n){$++;const t=a?`${o}${d}ordinal${d}${e}`:`${o}${d}${e}`,n=!!s(u,t,c??".");n&&m++,h.push({key:t,isTranslated:n})}}else{$++;const e=!!s(u,o,c??".");e&&m++,h.push({key:o,isTranslated:e})}i.set(g,{totalKeys:$,translatedKeys:m,keyDetails:h}),a+=m,r+=$}g.locales.set(e,{totalKeys:r,totalTranslated:a,namespaces:i})}return g}(r);u.succeed("Analysis complete."),function(e,t,a){a.detail?function(e,t,a,s){if(a===t.extract.primaryLanguage)return void console.log(o.yellow(`Locale "${a}" is the primary language. All keys are considered present.`));if(!t.locales.includes(a))return void console.error(o.red(`Error: Locale "${a}" is not defined in your configuration.`));const n=e.locales.get(a);if(!n)return void console.error(o.red(`Error: Locale "${a}" is not a valid secondary language.`));console.log(o.bold(`\nKey Status for "${o.cyan(a)}":`));const l=Array.from(e.keysByNs.values()).flat().length;c("Overall",n.totalTranslated,n.totalKeys);const r=s?[s]:Array.from(n.namespaces.keys()).sort();for(const e of r){const t=n.namespaces.get(e);t&&(console.log(o.cyan.bold(`\nNamespace: ${e}`)),c("Namespace Progress",t.translatedKeys,t.totalKeys),t.keyDetails.forEach(({key:e,isTranslated:t})=>{const a=t?o.green("✓"):o.red("✗");console.log(` ${a} ${e}`)}))}const i=l-n.totalTranslated;i>0?console.log(o.yellow.bold(`\nSummary: Found ${i} missing translations for "${a}".`)):console.log(o.green.bold(`\nSummary: 🎉 All keys are translated for "${a}".`));y()}(e,t,a.detail,a.namespace):a.namespace?function(e,t,a){const s=e.keysByNs.get(a);if(!s)return void console.error(o.red(`Error: Namespace "${a}" was not found in your source code.`));console.log(o.cyan.bold(`\nStatus for Namespace: "${a}"`)),console.log("------------------------");for(const[o,t]of e.locales.entries()){const e=t.namespaces.get(a);if(e){const t=e.totalKeys>0?Math.round(e.translatedKeys/e.totalKeys*100):100,a=i(t);console.log(`- ${o}: ${a} ${t}% (${e.translatedKeys}/${e.totalKeys} keys)`)}}y()}(e,0,a.namespace):function(e,t){const{primaryLanguage:a}=t.extract;console.log(o.cyan.bold("\ni18next Project Status")),console.log("------------------------"),console.log(`🔑 Keys Found: ${o.bold(e.totalBaseKeys)}`),console.log(`📚 Namespaces Found: ${o.bold(e.keysByNs.size)}`),console.log(`🌍 Locales: ${o.bold(t.locales.join(", "))}`),console.log(`✅ Primary Language: ${o.bold(a)}`),console.log("\nTranslation Progress:");for(const[o,t]of e.locales.entries()){const e=t.totalKeys>0?Math.round(t.totalTranslated/t.totalKeys*100):100,a=i(e);console.log(`- ${o}: ${a} ${e}% (${t.totalTranslated}/${t.totalKeys} keys)`)}y()}(e,t)}(e,r,d)}catch(o){u.fail("Failed to generate status report."),console.error(o)}}function c(e,t,a){const s=a>0?Math.round(t/a*100):100,n=i(s);console.log(`${o.bold(e)}: ${n} ${s}% (${t}/${a})`)}function i(e){const t=Math.floor(e/100*20),a=20-t;return`[${o.green("".padStart(t,"■"))}${"".padStart(a,"□")}]`}function y(){console.log(o.yellow.bold("\n✨ Take your localization to the next level!")),console.log("Manage translations with your team in the cloud with locize => https://www.locize.com/docs/getting-started"),console.log(`Run ${o.cyan("npx i18next-cli locize-migrate")} to get started.`)}export{r as runStatus};
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -47,40 +47,46 @@ export async function getTranslations (
|
|
|
47
47
|
objectKeys: Set<string>,
|
|
48
48
|
config: I18nextToolkitConfig
|
|
49
49
|
): Promise<TranslationResult[]> {
|
|
50
|
+
config.extract.primaryLanguage ||= config.locales[0] || 'en'
|
|
51
|
+
config.extract.secondaryLanguages ||= config.locales.filter((l: string) => l !== config?.extract?.primaryLanguage)
|
|
52
|
+
const primaryLanguage = config.extract.primaryLanguage
|
|
50
53
|
const defaultNS = config.extract.defaultNS ?? 'translation'
|
|
51
54
|
const keySeparator = config.extract.keySeparator ?? '.'
|
|
52
55
|
const patternsToPreserve = [...(config.extract.preservePatterns || [])]
|
|
53
56
|
const mergeNamespaces = config.extract.mergeNamespaces ?? false
|
|
57
|
+
const indentation = config.extract.indentation ?? 2
|
|
58
|
+
|
|
54
59
|
for (const key of objectKeys) {
|
|
55
60
|
// Convert the object key to a glob pattern to preserve all its children
|
|
56
61
|
patternsToPreserve.push(`${key}.*`)
|
|
57
62
|
}
|
|
58
63
|
const preservePatterns = patternsToPreserve.map(globToRegex)
|
|
59
|
-
config.extract.primaryLanguage ||= config.locales[0] || 'en'
|
|
60
|
-
config.extract.secondaryLanguages ||= config.locales.filter((l: string) => l !== config?.extract?.primaryLanguage)
|
|
61
|
-
const primaryLanguage = config.extract.primaryLanguage
|
|
62
64
|
|
|
63
65
|
// Group keys by namespace
|
|
64
66
|
const keysByNS = new Map<string, ExtractedKey[]>()
|
|
65
67
|
for (const key of keys.values()) {
|
|
66
68
|
const ns = key.ns || defaultNS
|
|
67
|
-
if (!keysByNS.has(ns))
|
|
68
|
-
keysByNS.set(ns, [])
|
|
69
|
-
}
|
|
69
|
+
if (!keysByNS.has(ns)) keysByNS.set(ns, [])
|
|
70
70
|
keysByNS.get(ns)!.push(key)
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const results: TranslationResult[] = []
|
|
74
74
|
|
|
75
75
|
for (const locale of config.locales) {
|
|
76
|
-
const
|
|
77
|
-
|
|
76
|
+
const existingMergedFile = mergeNamespaces
|
|
77
|
+
? await loadTranslationFile(resolve(process.cwd(), getOutputPath(config.extract.output, locale))) || {}
|
|
78
|
+
: null
|
|
79
|
+
|
|
80
|
+
const newMergedTranslations: Record<string, any> = {}
|
|
78
81
|
|
|
79
82
|
for (const [ns, nsKeys] of keysByNS.entries()) {
|
|
80
83
|
const outputPath = getOutputPath(config.extract.output, locale, mergeNamespaces ? undefined : ns)
|
|
81
84
|
const fullPath = resolve(process.cwd(), outputPath)
|
|
82
85
|
|
|
83
|
-
const existingTranslations =
|
|
86
|
+
const existingTranslations = mergeNamespaces
|
|
87
|
+
? existingMergedFile?.[ns] || {}
|
|
88
|
+
: await loadTranslationFile(fullPath) || {}
|
|
89
|
+
|
|
84
90
|
const newTranslations: Record<string, any> = {}
|
|
85
91
|
|
|
86
92
|
const existingKeys = getNestedKeys(existingTranslations, keySeparator)
|
|
@@ -91,10 +97,7 @@ export async function getTranslations (
|
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
99
|
|
|
94
|
-
const sortedKeys = (config.extract.sort === false)
|
|
95
|
-
? nsKeys
|
|
96
|
-
: [...nsKeys].sort((a, b) => a.key.localeCompare(b.key))
|
|
97
|
-
|
|
100
|
+
const sortedKeys = (config.extract.sort === false) ? nsKeys : [...nsKeys].sort((a, b) => a.key.localeCompare(b.key))
|
|
98
101
|
for (const { key, defaultValue } of sortedKeys) {
|
|
99
102
|
const existingValue = getNestedValue(existingTranslations, key, keySeparator)
|
|
100
103
|
const valueToSet = existingValue ?? (locale === primaryLanguage ? defaultValue : (config.extract.defaultValue ?? ''))
|
|
@@ -102,35 +105,20 @@ export async function getTranslations (
|
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
if (mergeNamespaces) {
|
|
105
|
-
|
|
106
|
-
if (Object.keys(existingTranslations).length > 0) {
|
|
107
|
-
mergedExisting[ns] = existingTranslations
|
|
108
|
-
}
|
|
108
|
+
newMergedTranslations[ns] = newTranslations
|
|
109
109
|
} else {
|
|
110
|
-
const oldContent =
|
|
111
|
-
const newContent = JSON.stringify(newTranslations, null,
|
|
112
|
-
|
|
113
|
-
results.push({
|
|
114
|
-
path: fullPath,
|
|
115
|
-
updated: newContent !== oldContent,
|
|
116
|
-
newTranslations,
|
|
117
|
-
existingTranslations,
|
|
118
|
-
})
|
|
110
|
+
const oldContent = JSON.stringify(existingTranslations, null, indentation)
|
|
111
|
+
const newContent = JSON.stringify(newTranslations, null, indentation)
|
|
112
|
+
results.push({ path: fullPath, updated: newContent !== oldContent, newTranslations, existingTranslations })
|
|
119
113
|
}
|
|
120
114
|
}
|
|
121
115
|
|
|
122
116
|
if (mergeNamespaces) {
|
|
123
117
|
const outputPath = getOutputPath(config.extract.output, locale)
|
|
124
118
|
const fullPath = resolve(process.cwd(), outputPath)
|
|
125
|
-
const oldContent =
|
|
126
|
-
const newContent = JSON.stringify(
|
|
127
|
-
|
|
128
|
-
results.push({
|
|
129
|
-
path: fullPath,
|
|
130
|
-
updated: newContent !== oldContent,
|
|
131
|
-
newTranslations: mergedTranslations,
|
|
132
|
-
existingTranslations: mergedExisting,
|
|
133
|
-
})
|
|
119
|
+
const oldContent = JSON.stringify(existingMergedFile, null, indentation)
|
|
120
|
+
const newContent = JSON.stringify(newMergedTranslations, null, indentation)
|
|
121
|
+
results.push({ path: fullPath, updated: newContent !== oldContent, newTranslations: newMergedTranslations, existingTranslations: existingMergedFile || {} })
|
|
134
122
|
}
|
|
135
123
|
}
|
|
136
124
|
|
|
@@ -365,11 +365,12 @@ export class ASTVisitors {
|
|
|
365
365
|
* @private
|
|
366
366
|
*/
|
|
367
367
|
private handleCallExpression (node: CallExpression): void {
|
|
368
|
-
const
|
|
369
|
-
if (
|
|
368
|
+
const functionName = this.getFunctionName(node.callee)
|
|
369
|
+
if (!functionName) return
|
|
370
370
|
|
|
371
|
-
|
|
372
|
-
const
|
|
371
|
+
// The scope lookup will only work for simple identifiers, which is okay for this fix.
|
|
372
|
+
const scopeInfo = this.getVarFromScope(functionName)
|
|
373
|
+
const isFunctionToParse = (this.config.extract.functions || ['t']).includes(functionName) || scopeInfo !== undefined
|
|
373
374
|
if (!isFunctionToParse || node.arguments.length === 0) return
|
|
374
375
|
|
|
375
376
|
const firstArg = node.arguments[0].expression
|
|
@@ -391,6 +392,17 @@ export class ASTVisitors {
|
|
|
391
392
|
|
|
392
393
|
if (keysToProcess.length === 0) return
|
|
393
394
|
|
|
395
|
+
let isOrdinalByKey = false
|
|
396
|
+
const pluralSeparator = this.config.extract.pluralSeparator ?? '_'
|
|
397
|
+
|
|
398
|
+
for (let i = 0; i < keysToProcess.length; i++) {
|
|
399
|
+
if (keysToProcess[i].endsWith(`${pluralSeparator}ordinal`)) {
|
|
400
|
+
isOrdinalByKey = true
|
|
401
|
+
// Normalize the key by stripping the suffix
|
|
402
|
+
keysToProcess[i] = keysToProcess[i].slice(0, -8)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
394
406
|
let defaultValue: string | undefined
|
|
395
407
|
let options: ObjectExpression | undefined
|
|
396
408
|
|
|
@@ -468,8 +480,11 @@ export class ASTVisitors {
|
|
|
468
480
|
}
|
|
469
481
|
|
|
470
482
|
// 3. Handle Plurals
|
|
471
|
-
|
|
472
|
-
|
|
483
|
+
const hasCount = this.getObjectPropValue(options, 'count') !== undefined
|
|
484
|
+
const isOrdinalByOption = this.getObjectPropValue(options, 'ordinal') === true
|
|
485
|
+
if (hasCount || isOrdinalByKey) {
|
|
486
|
+
// Pass the combined ordinal flag to the handler
|
|
487
|
+
this.handlePluralKeys(finalKey, ns, options, isOrdinalByOption || isOrdinalByKey)
|
|
473
488
|
continue
|
|
474
489
|
}
|
|
475
490
|
|
|
@@ -495,43 +510,50 @@ export class ASTVisitors {
|
|
|
495
510
|
* @param key - Base key name for pluralization
|
|
496
511
|
* @param ns - Namespace for the keys
|
|
497
512
|
* @param options - object expression options
|
|
513
|
+
* @param isOrdinal - isOrdinal flag
|
|
498
514
|
*
|
|
499
515
|
* @private
|
|
500
516
|
*/
|
|
501
|
-
private handlePluralKeys (key: string, ns: string | undefined, options: ObjectExpression): void {
|
|
517
|
+
private handlePluralKeys (key: string, ns: string | undefined, options: ObjectExpression, isOrdinal: boolean): void {
|
|
502
518
|
try {
|
|
503
|
-
const isOrdinal = this.getObjectPropValue(options, 'ordinal') === true
|
|
504
519
|
const type = isOrdinal ? 'ordinal' : 'cardinal'
|
|
505
520
|
|
|
506
521
|
const pluralCategories = new Intl.PluralRules(this.config.extract?.primaryLanguage, { type }).resolvedOptions().pluralCategories
|
|
507
522
|
const pluralSeparator = this.config.extract.pluralSeparator ?? '_'
|
|
508
523
|
|
|
524
|
+
// Get all possible default values once at the start
|
|
509
525
|
const defaultValue = this.getObjectPropValue(options, 'defaultValue')
|
|
510
|
-
const
|
|
526
|
+
const otherDefault = this.getObjectPropValue(options, 'defaultValue_other')
|
|
527
|
+
const ordinalOtherDefault = this.getObjectPropValue(options, 'defaultValue_ordinal_other')
|
|
511
528
|
|
|
512
529
|
for (const category of pluralCategories) {
|
|
513
|
-
//
|
|
530
|
+
// 1. Look for the most specific default value (e.g., defaultValue_ordinal_one)
|
|
514
531
|
const specificDefaultKey = isOrdinal ? `defaultValue_ordinal_${category}` : `defaultValue_${category}`
|
|
515
532
|
const specificDefault = this.getObjectPropValue(options, specificDefaultKey)
|
|
516
533
|
|
|
534
|
+
// 2. Determine the final default value using a clear fallback chain
|
|
517
535
|
let finalDefaultValue: string | undefined
|
|
518
|
-
|
|
519
536
|
if (typeof specificDefault === 'string') {
|
|
537
|
+
// 1. Use the most specific default if it exists (e.g., defaultValue_one)
|
|
520
538
|
finalDefaultValue = specificDefault
|
|
521
539
|
} else if (category === 'one' && typeof defaultValue === 'string') {
|
|
522
|
-
// 'one'
|
|
540
|
+
// 2. SPECIAL CASE: The 'one' category falls back to the main 'defaultValue' prop
|
|
523
541
|
finalDefaultValue = defaultValue
|
|
524
|
-
} else if (typeof
|
|
525
|
-
//
|
|
526
|
-
finalDefaultValue =
|
|
542
|
+
} else if (isOrdinal && typeof ordinalOtherDefault === 'string') {
|
|
543
|
+
// 3a. Other ordinal categories fall back to 'defaultValue_ordinal_other'
|
|
544
|
+
finalDefaultValue = ordinalOtherDefault
|
|
545
|
+
} else if (!isOrdinal && typeof otherDefault === 'string') {
|
|
546
|
+
// 3b. Other cardinal categories fall back to 'defaultValue_other'
|
|
547
|
+
finalDefaultValue = otherDefault
|
|
527
548
|
} else if (typeof defaultValue === 'string') {
|
|
528
|
-
// If
|
|
549
|
+
// 4. If no '_other' is found, all categories can fall back to the main 'defaultValue'
|
|
529
550
|
finalDefaultValue = defaultValue
|
|
530
551
|
} else {
|
|
531
|
-
|
|
552
|
+
// 5. Final fallback to the base key itself
|
|
553
|
+
finalDefaultValue = key
|
|
532
554
|
}
|
|
533
555
|
|
|
534
|
-
// Construct the final key
|
|
556
|
+
// 3. Construct the final plural key
|
|
535
557
|
const finalKey = isOrdinal
|
|
536
558
|
? `${key}${pluralSeparator}ordinal${pluralSeparator}${category}`
|
|
537
559
|
: `${key}${pluralSeparator}${category}`
|
|
@@ -541,6 +563,7 @@ export class ASTVisitors {
|
|
|
541
563
|
ns,
|
|
542
564
|
defaultValue: finalDefaultValue,
|
|
543
565
|
hasCount: true,
|
|
566
|
+
isOrdinal
|
|
544
567
|
})
|
|
545
568
|
}
|
|
546
569
|
} catch (e) {
|
|
@@ -858,4 +881,58 @@ export class ASTVisitors {
|
|
|
858
881
|
}
|
|
859
882
|
return undefined
|
|
860
883
|
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Serializes a callee node (Identifier or MemberExpression) into a string.
|
|
887
|
+
*
|
|
888
|
+
* Produces a dotted name for simple callees that can be used for scope lookups
|
|
889
|
+
* or configuration matching.
|
|
890
|
+
*
|
|
891
|
+
* Supported inputs:
|
|
892
|
+
* - Identifier: returns the identifier name (e.g., `t` -> "t")
|
|
893
|
+
* - MemberExpression with Identifier parts: returns a dotted path of identifiers
|
|
894
|
+
* (e.g., `i18n.t` -> "i18n.t", `i18n.getFixedT` -> "i18n.getFixedT")
|
|
895
|
+
*
|
|
896
|
+
* Behavior notes:
|
|
897
|
+
* - Computed properties are not supported and cause this function to return null
|
|
898
|
+
* (e.g., `i18n['t']` -> null).
|
|
899
|
+
* - The base of a MemberExpression must be a simple Identifier. More complex bases
|
|
900
|
+
* (other expressions, `this`, etc.) will result in null.
|
|
901
|
+
* - This function does not attempt to resolve or evaluate expressions — it only
|
|
902
|
+
* serializes static identifier/member chains.
|
|
903
|
+
*
|
|
904
|
+
* Examples:
|
|
905
|
+
* - Identifier callee: { type: 'Identifier', value: 't' } -> "t"
|
|
906
|
+
* - Member callee: { type: 'MemberExpression', object: { type: 'Identifier', value: 'i18n' }, property: { type: 'Identifier', value: 't' } } -> "i18n.t"
|
|
907
|
+
*
|
|
908
|
+
* @param callee - The CallExpression callee node to serialize
|
|
909
|
+
* @returns A dotted string name for supported callees, or null when the callee
|
|
910
|
+
* is a computed/unsupported expression.
|
|
911
|
+
*
|
|
912
|
+
* @private
|
|
913
|
+
*/
|
|
914
|
+
private getFunctionName (callee: CallExpression['callee']): string | null {
|
|
915
|
+
if (callee.type === 'Identifier') {
|
|
916
|
+
return callee.value
|
|
917
|
+
}
|
|
918
|
+
if (callee.type === 'MemberExpression') {
|
|
919
|
+
const parts: string[] = []
|
|
920
|
+
let current: any = callee
|
|
921
|
+
while (current.type === 'MemberExpression') {
|
|
922
|
+
if (current.property.type === 'Identifier') {
|
|
923
|
+
parts.unshift(current.property.value)
|
|
924
|
+
} else {
|
|
925
|
+
return null // Cannot handle computed properties like i18n['t']
|
|
926
|
+
}
|
|
927
|
+
current = current.object
|
|
928
|
+
}
|
|
929
|
+
if (current.type === 'Identifier') {
|
|
930
|
+
parts.unshift(current.value)
|
|
931
|
+
} else {
|
|
932
|
+
return null // Base of the expression is not a simple identifier
|
|
933
|
+
}
|
|
934
|
+
return parts.join('.')
|
|
935
|
+
}
|
|
936
|
+
return null
|
|
937
|
+
}
|
|
861
938
|
}
|
package/src/status.ts
CHANGED
|
@@ -21,11 +21,13 @@ interface StatusOptions {
|
|
|
21
21
|
*/
|
|
22
22
|
interface StatusReport {
|
|
23
23
|
/** Total number of extracted keys across all namespaces */
|
|
24
|
-
|
|
24
|
+
totalBaseKeys: number;
|
|
25
25
|
/** Map of namespace names to their extracted keys */
|
|
26
26
|
keysByNs: Map<string, ExtractedKey[]>;
|
|
27
27
|
/** Map of locale codes to their translation status data */
|
|
28
28
|
locales: Map<string, {
|
|
29
|
+
/** Total number of extracted keys per locale */
|
|
30
|
+
totalKeys: number;
|
|
29
31
|
/** Total number of translated keys for this locale */
|
|
30
32
|
totalTranslated: number;
|
|
31
33
|
/** Map of namespace names to their translation details for this locale */
|
|
@@ -87,12 +89,7 @@ async function generateStatusReport (config: I18nextToolkitConfig): Promise<Stat
|
|
|
87
89
|
config.extract.secondaryLanguages ||= config.locales.filter((l: string) => l !== config?.extract?.primaryLanguage)
|
|
88
90
|
|
|
89
91
|
const { allKeys: allExtractedKeys } = await findKeys(config)
|
|
90
|
-
const {
|
|
91
|
-
secondaryLanguages,
|
|
92
|
-
keySeparator = '.',
|
|
93
|
-
defaultNS = 'translation',
|
|
94
|
-
mergeNamespaces = false,
|
|
95
|
-
} = config.extract
|
|
92
|
+
const { secondaryLanguages, keySeparator = '.', defaultNS = 'translation', mergeNamespaces = false, pluralSeparator = '_' } = config.extract
|
|
96
93
|
|
|
97
94
|
const keysByNs = new Map<string, ExtractedKey[]>()
|
|
98
95
|
for (const key of allExtractedKeys.values()) {
|
|
@@ -102,13 +99,14 @@ async function generateStatusReport (config: I18nextToolkitConfig): Promise<Stat
|
|
|
102
99
|
}
|
|
103
100
|
|
|
104
101
|
const report: StatusReport = {
|
|
105
|
-
|
|
102
|
+
totalBaseKeys: allExtractedKeys.size,
|
|
106
103
|
keysByNs,
|
|
107
104
|
locales: new Map(),
|
|
108
105
|
}
|
|
109
106
|
|
|
110
107
|
for (const locale of secondaryLanguages) {
|
|
111
108
|
let totalTranslatedForLocale = 0
|
|
109
|
+
let totalKeysForLocale = 0
|
|
112
110
|
const namespaces = new Map<string, any>()
|
|
113
111
|
|
|
114
112
|
const mergedTranslations = mergeNamespaces
|
|
@@ -121,24 +119,41 @@ async function generateStatusReport (config: I18nextToolkitConfig): Promise<Stat
|
|
|
121
119
|
: await loadTranslationFile(resolve(process.cwd(), getOutputPath(config.extract.output, locale, ns))) || {}
|
|
122
120
|
|
|
123
121
|
let translatedInNs = 0
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
122
|
+
let totalInNs = 0
|
|
123
|
+
const keyDetails: Array<{ key: string; isTranslated: boolean }> = []
|
|
124
|
+
|
|
125
|
+
// This is the new, language-aware logic loop
|
|
126
|
+
for (const { key: baseKey, hasCount, isOrdinal } of keysInNs) {
|
|
127
|
+
if (hasCount) {
|
|
128
|
+
const type = isOrdinal ? 'ordinal' : 'cardinal'
|
|
129
|
+
// It's a plural key: expand it based on the current locale's rules
|
|
130
|
+
const pluralCategories = new Intl.PluralRules(locale, { type }).resolvedOptions().pluralCategories
|
|
131
|
+
for (const category of pluralCategories) {
|
|
132
|
+
totalInNs++
|
|
133
|
+
const pluralKey = isOrdinal
|
|
134
|
+
? `${baseKey}${pluralSeparator}ordinal${pluralSeparator}${category}`
|
|
135
|
+
: `${baseKey}${pluralSeparator}${category}`
|
|
136
|
+
const value = getNestedValue(translationsForNs, pluralKey, keySeparator ?? '.')
|
|
137
|
+
const isTranslated = !!value
|
|
138
|
+
if (isTranslated) translatedInNs++
|
|
139
|
+
keyDetails.push({ key: pluralKey, isTranslated })
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
// It's a simple key
|
|
143
|
+
totalInNs++
|
|
144
|
+
const value = getNestedValue(translationsForNs, baseKey, keySeparator ?? '.')
|
|
145
|
+
const isTranslated = !!value
|
|
146
|
+
if (isTranslated) translatedInNs++
|
|
147
|
+
keyDetails.push({ key: baseKey, isTranslated })
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
namespaces.set(ns, { totalKeys: totalInNs, translatedKeys: translatedInNs, keyDetails })
|
|
137
152
|
totalTranslatedForLocale += translatedInNs
|
|
153
|
+
totalKeysForLocale += totalInNs
|
|
138
154
|
}
|
|
139
|
-
report.locales.set(locale, { totalTranslated: totalTranslatedForLocale, namespaces })
|
|
155
|
+
report.locales.set(locale, { totalKeys: totalKeysForLocale, totalTranslated: totalTranslatedForLocale, namespaces })
|
|
140
156
|
}
|
|
141
|
-
|
|
142
157
|
return report
|
|
143
158
|
}
|
|
144
159
|
|
|
@@ -198,7 +213,7 @@ function displayDetailedLocaleReport (report: StatusReport, config: I18nextToolk
|
|
|
198
213
|
console.log(chalk.bold(`\nKey Status for "${chalk.cyan(locale)}":`))
|
|
199
214
|
|
|
200
215
|
const totalKeysForLocale = Array.from(report.keysByNs.values()).flat().length
|
|
201
|
-
printProgressBar('Overall', localeData.totalTranslated,
|
|
216
|
+
printProgressBar('Overall', localeData.totalTranslated, localeData.totalKeys)
|
|
202
217
|
|
|
203
218
|
const namespacesToDisplay = namespaceFilter ? [namespaceFilter] : Array.from(localeData.namespaces.keys()).sort()
|
|
204
219
|
|
|
@@ -273,16 +288,16 @@ function displayOverallSummaryReport (report: StatusReport, config: I18nextToolk
|
|
|
273
288
|
|
|
274
289
|
console.log(chalk.cyan.bold('\ni18next Project Status'))
|
|
275
290
|
console.log('------------------------')
|
|
276
|
-
console.log(`🔑 Keys Found: ${chalk.bold(report.
|
|
291
|
+
console.log(`🔑 Keys Found: ${chalk.bold(report.totalBaseKeys)}`)
|
|
277
292
|
console.log(`📚 Namespaces Found: ${chalk.bold(report.keysByNs.size)}`)
|
|
278
293
|
console.log(`🌍 Locales: ${chalk.bold(config.locales.join(', '))}`)
|
|
279
294
|
console.log(`✅ Primary Language: ${chalk.bold(primaryLanguage)}`)
|
|
280
295
|
console.log('\nTranslation Progress:')
|
|
281
296
|
|
|
282
297
|
for (const [locale, localeData] of report.locales.entries()) {
|
|
283
|
-
const percentage =
|
|
298
|
+
const percentage = localeData.totalKeys > 0 ? Math.round((localeData.totalTranslated / localeData.totalKeys) * 100) : 100
|
|
284
299
|
const bar = generateProgressBarText(percentage)
|
|
285
|
-
console.log(`- ${locale}: ${bar} ${percentage}% (${localeData.totalTranslated}/${
|
|
300
|
+
console.log(`- ${locale}: ${bar} ${percentage}% (${localeData.totalTranslated}/${localeData.totalKeys} keys)`)
|
|
286
301
|
}
|
|
287
302
|
|
|
288
303
|
printLocizeFunnel()
|
|
@@ -312,7 +327,7 @@ function printProgressBar (label: string, current: number, total: number) {
|
|
|
312
327
|
*/
|
|
313
328
|
function generateProgressBarText (percentage: number): string {
|
|
314
329
|
const totalBars = 20
|
|
315
|
-
const filledBars = Math.
|
|
330
|
+
const filledBars = Math.floor((percentage / 100) * totalBars)
|
|
316
331
|
const emptyBars = totalBars - filledBars
|
|
317
332
|
return `[${chalk.green(''.padStart(filledBars, '■'))}${''.padStart(emptyBars, '□')}]`
|
|
318
333
|
}
|
package/src/types.ts
CHANGED
|
@@ -251,6 +251,9 @@ export interface ExtractedKey {
|
|
|
251
251
|
/** Whether this key is used with pluralization (count parameter) */
|
|
252
252
|
hasCount?: boolean;
|
|
253
253
|
|
|
254
|
+
/** Whether this key is used with ordinal pluralization */
|
|
255
|
+
isOrdinal?: boolean;
|
|
256
|
+
|
|
254
257
|
/** hold the raw context expression from the AST */
|
|
255
258
|
contextExpression?: Expression;
|
|
256
259
|
}
|
|
@@ -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,aAAa,CAAA;AAgBnF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,iBAAiB,EAAE,CAAC,
|
|
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,aAAa,CAAA;AAgBnF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA6E9B"}
|
|
@@ -162,6 +162,7 @@ export declare class ASTVisitors {
|
|
|
162
162
|
* @param key - Base key name for pluralization
|
|
163
163
|
* @param ns - Namespace for the keys
|
|
164
164
|
* @param options - object expression options
|
|
165
|
+
* @param isOrdinal - isOrdinal flag
|
|
165
166
|
*
|
|
166
167
|
* @private
|
|
167
168
|
*/
|
|
@@ -271,5 +272,35 @@ export declare class ASTVisitors {
|
|
|
271
272
|
* @returns The resolved UseTranslationHookConfig when a match is found, otherwise undefined
|
|
272
273
|
*/
|
|
273
274
|
private getUseTranslationConfig;
|
|
275
|
+
/**
|
|
276
|
+
* Serializes a callee node (Identifier or MemberExpression) into a string.
|
|
277
|
+
*
|
|
278
|
+
* Produces a dotted name for simple callees that can be used for scope lookups
|
|
279
|
+
* or configuration matching.
|
|
280
|
+
*
|
|
281
|
+
* Supported inputs:
|
|
282
|
+
* - Identifier: returns the identifier name (e.g., `t` -> "t")
|
|
283
|
+
* - MemberExpression with Identifier parts: returns a dotted path of identifiers
|
|
284
|
+
* (e.g., `i18n.t` -> "i18n.t", `i18n.getFixedT` -> "i18n.getFixedT")
|
|
285
|
+
*
|
|
286
|
+
* Behavior notes:
|
|
287
|
+
* - Computed properties are not supported and cause this function to return null
|
|
288
|
+
* (e.g., `i18n['t']` -> null).
|
|
289
|
+
* - The base of a MemberExpression must be a simple Identifier. More complex bases
|
|
290
|
+
* (other expressions, `this`, etc.) will result in null.
|
|
291
|
+
* - This function does not attempt to resolve or evaluate expressions — it only
|
|
292
|
+
* serializes static identifier/member chains.
|
|
293
|
+
*
|
|
294
|
+
* Examples:
|
|
295
|
+
* - Identifier callee: { type: 'Identifier', value: 't' } -> "t"
|
|
296
|
+
* - Member callee: { type: 'MemberExpression', object: { type: 'Identifier', value: 'i18n' }, property: { type: 'Identifier', value: 't' } } -> "i18n.t"
|
|
297
|
+
*
|
|
298
|
+
* @param callee - The CallExpression callee node to serialize
|
|
299
|
+
* @returns A dotted string name for supported callees, or null when the callee
|
|
300
|
+
* is a computed/unsupported expression.
|
|
301
|
+
*
|
|
302
|
+
* @private
|
|
303
|
+
*/
|
|
304
|
+
private getFunctionName;
|
|
274
305
|
}
|
|
275
306
|
//# sourceMappingURL=ast-visitors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA+G,MAAM,WAAW,CAAA;AACpJ,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAoB9E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,UAAU,CAAoC;IAE/C,UAAU,cAAoB;IAErC;;;;;;OAMG;gBAED,MAAM,EAAE,oBAAoB,EAC5B,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM;IAOhB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IAoDZ;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IASvB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,wBAAwB;IAmChC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,8BAA8B;IAwDtC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;
|
|
1
|
+
{"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA+G,MAAM,WAAW,CAAA;AACpJ,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAoB9E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,UAAU,CAAoC;IAE/C,UAAU,cAAoB;IAErC;;;;;;OAMG;gBAED,MAAM,EAAE,oBAAoB,EAC5B,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM;IAOhB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IAoDZ;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IASvB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,wBAAwB;IAmChC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,8BAA8B;IAwDtC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IAwI5B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,gBAAgB;IA4DxB;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAkB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IAuDxB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,kBAAkB;IA6B1B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,2BAA2B;IAmBnC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,OAAO,CAAC,eAAe;CAwBxB"}
|
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,SAAS,CAAA;AAGjE;;GAEG;AACH,UAAU,aAAa;IACrB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAgB,MAAM,SAAS,CAAA;AAGjE;;GAEG;AACH,UAAU,aAAa;IACrB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4BD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB,EAAE,OAAO,GAAE,aAAkB,iBAYzF"}
|
package/types/types.d.ts
CHANGED
|
@@ -210,6 +210,8 @@ export interface ExtractedKey {
|
|
|
210
210
|
ns?: string;
|
|
211
211
|
/** Whether this key is used with pluralization (count parameter) */
|
|
212
212
|
hasCount?: boolean;
|
|
213
|
+
/** Whether this key is used with ordinal pluralization */
|
|
214
|
+
isOrdinal?: boolean;
|
|
213
215
|
/** hold the raw context expression from the AST */
|
|
214
216
|
contextExpression?: Expression;
|
|
215
217
|
}
|
package/types/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|