i18next-cli 1.24.9 → 1.24.10
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 +4 -0
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/parsers/scope-manager.js +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/parsers/scope-manager.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/extractor/parsers/scope-manager.ts +59 -57
- package/types/extractor/parsers/scope-manager.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,10 @@ 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.24.10](https://github.com/i18next/i18next-cli/compare/v1.24.9...v1.24.10) - 2025-11-16
|
|
9
|
+
|
|
10
|
+
- improve handling of namespace/keyPrefix argument parsing to address [#112](https://github.com/i18next/i18next-cli/issues/112)
|
|
11
|
+
|
|
8
12
|
## [1.24.9](https://github.com/i18next/i18next-cli/compare/v1.24.8...v1.24.9) - 2025-11-16
|
|
9
13
|
|
|
10
14
|
- fix: a namespace prefix passed as an argument (e.g. to useTranslation or custom hooks) could be treated as part of the translation key, producing unexpected nested namespace objects in generated JSON files. [#112](https://github.com/i18next/i18next-cli/issues/112)
|
package/dist/cjs/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var e=require("commander"),o=require("chokidar"),t=require("glob"),n=require("minimatch"),i=require("chalk"),r=require("./config.js"),a=require("./heuristic-config.js"),c=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var s=require("./types-generator.js"),l=require("./syncer.js"),u=require("./migrator.js"),d=require("./init.js"),g=require("./linter.js"),f=require("./status.js"),p=require("./locize.js"),m=require("./rename-key.js");const y=new e.Command;y.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.24.
|
|
2
|
+
"use strict";var e=require("commander"),o=require("chokidar"),t=require("glob"),n=require("minimatch"),i=require("chalk"),r=require("./config.js"),a=require("./heuristic-config.js"),c=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var s=require("./types-generator.js"),l=require("./syncer.js"),u=require("./migrator.js"),d=require("./init.js"),g=require("./linter.js"),f=require("./status.js"),p=require("./locize.js"),m=require("./rename-key.js");const y=new e.Command;y.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.24.10"),y.option("-c, --config <path>","Path to i18next-cli config file (overrides detection)"),y.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.").option("--dry-run","Run the extractor without writing any files to disk.").option("--sync-primary","Sync primary language values with default values from code.").action(async e=>{try{const t=y.opts().config,i=await r.ensureConfig(t),a=async()=>{const o=await c.runExtractor(i,{isWatchMode:!!e.watch,isDryRun:!!e.dryRun,syncPrimaryWithDefaults:!!e.syncPrimary});return e.ci&&!o?(console.log("✅ No files were updated."),process.exit(0)):e.ci&&o&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1)),o};if(await a(),e.watch){console.log("\nWatching for changes...");const e=await x(i.extract.input),t=h(i.extract.ignore),r=w(i.extract.output),c=[...t,...r].filter(Boolean),s=e.filter(e=>!c.some(o=>n.minimatch(e,o,{dot:!0})));o.watch(s,{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}catch(e){console.error("Error running extractor:",e),process.exit(1)}}),y.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,o)=>{const t=y.opts().config;let n=await r.loadConfig(t);if(!n){console.log(i.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(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!")),n=e}await f.runStatus(n,{detail:e,namespace:o.namespace})}),y.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 t=y.opts().config,i=await r.ensureConfig(t),a=()=>s.runTypesGenerator(i);if(await a(),e.watch){console.log("\nWatching for changes...");const e=await x(i.types?.input||[]),t=[...h(i.extract?.ignore)].filter(Boolean),r=e.filter(e=>!t.some(o=>n.minimatch(e,o,{dot:!0})));o.watch(r,{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),y.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=y.opts().config,o=await r.ensureConfig(e);await l.runSyncer(o)}),y.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await u.runMigrator(e)}),y.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(d.runInit),y.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const t=y.opts().config,c=async()=>{let e=await r.loadConfig(t);if(!e){console.log(i.blue("No config file found. Attempting to detect project structure..."));const o=await a.detectConfig();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 g.runLinterCli(e)};if(await c(),e.watch){console.log("\nWatching for changes...");const e=await r.loadConfig(t);if(e?.extract?.input){const t=await x(e.extract.input),i=[...h(e.extract.ignore),...w(e.extract.output)].filter(Boolean),r=t.filter(e=>!i.some(o=>n.minimatch(e,o,{dot:!0})));o.watch(r,{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}}),y.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 o=y.opts().config,t=await r.ensureConfig(o);await p.runLocizeSync(t,e)}),y.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const o=y.opts().config,t=await r.ensureConfig(o);await p.runLocizeDownload(t,e)}),y.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const o=y.opts().config,t=await r.ensureConfig(o);await p.runLocizeMigrate(t,e)}),y.command("rename-key <oldKey> <newKey>").description("Rename a translation key across all source files and translation files.").option("--dry-run","Preview changes without modifying files").action(async(e,o,t)=>{try{const n=y.opts().config,a=await r.ensureConfig(n),c=await m.runRenameKey(a,e,o,t);c.success||(c.conflicts&&(console.error(i.red("\n❌ Conflicts detected:")),c.conflicts.forEach(e=>console.error(` - ${e}`))),c.error&&console.error(i.red(`\n❌ ${c.error}`)),process.exit(1));0===c.sourceFiles.reduce((e,o)=>e+o.changes,0)&&console.log(i.yellow(`\n⚠️ No usages found for "${e}"`))}catch(e){console.error(i.red("Error renaming key:"),e),process.exit(1)}}),y.parse(process.argv);const h=e=>Array.isArray(e)?e:e?[e]:[],w=e=>e&&"string"==typeof e?[e.replace(/\{\{[^}]+\}\}/g,"*")]:[],x=async(e=[])=>{const o=h(e),n=await Promise.all(o.map(e=>t.glob(e||"",{nodir:!0})));return Array.from(new Set(n.flat()))};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("./ast-utils.js");exports.ScopeManager=class{scopeStack=[];config;scope=new Map;simpleConstants=new Map;constructor(e){this.config=e}reset(){this.scopeStack=[],this.scope=new Map,this.simpleConstants.clear()}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):this.scope.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)}const t=this.scope.get(e);if(t)return t}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const i of t){if("string"==typeof i&&i===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof i&&i.name===e)return{name:i.name,nsArg:i.nsArg??0,keyPrefixArg:i.keyPrefixArg??1}}}resolveSimpleStringIdentifier(e){return this.simpleConstants.get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;"Identifier"===e.id.type&&"StringLiteral"===t.type&&this.simpleConstants.set(e.id.value,t.value);const i="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!i)return;const r=i.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return this.handleUseTranslationDeclarator(e,i,t),void this.handleUseTranslationForComments(e,i,t)}if("Identifier"===r.type){if(this.getVarFromScope(r.value))return void this.handleGetFixedTFromVariableDeclarator(e,i,r.value)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,i)}handleUseTranslationForComments(
|
|
1
|
+
"use strict";var e=require("./ast-utils.js");exports.ScopeManager=class{scopeStack=[];config;scope=new Map;simpleConstants=new Map;constructor(e){this.config=e}reset(){this.scopeStack=[],this.scope=new Map,this.simpleConstants.clear()}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):this.scope.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)}const t=this.scope.get(e);if(t)return t}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const i of t){if("string"==typeof i&&i===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof i&&i.name===e)return{name:i.name,nsArg:i.nsArg??0,keyPrefixArg:i.keyPrefixArg??1}}}resolveSimpleStringIdentifier(e){return this.simpleConstants.get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;"Identifier"===e.id.type&&"StringLiteral"===t.type&&this.simpleConstants.set(e.id.value,t.value);const i="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!i)return;const r=i.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return this.handleUseTranslationDeclarator(e,i,t),void this.handleUseTranslationForComments(e,i,t)}if("Identifier"===r.type){if(this.getVarFromScope(r.value))return void this.handleGetFixedTFromVariableDeclarator(e,i,r.value)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,i)}handleUseTranslationForComments(t,i,r){let s;if("Identifier"===t.id.type&&(s=t.id.value),"ArrayPattern"===t.id.type){const e=t.id.elements[0];"Identifier"===e?.type&&(s=e.value)}if("ObjectPattern"===t.id.type)for(const e of t.id.properties){if("AssignmentPatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)){s=e.key.value;break}if("KeyValuePatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)&&"Identifier"===e.value.type){s=e.value.value;break}}if(!s)return;const n=r.nsArg??0,a=r.keyPrefixArg??1;let o,l;const p=i.arguments?.[0]?.expression,y=i.arguments?.[1]?.expression,u=i.arguments?.[2]?.expression;var f;let c;if("useTranslation"===r.name&&"StringLiteral"===p?.type&&"StringLiteral"===y?.type&&(f=p.value,/^[a-z]{2,3}([-_][A-Za-z0-9-]+)?$/i.test(f)))o=y.value,c=u;else{if(-1!==n){const e=i.arguments?.[n]?.expression;"StringLiteral"===e?.type?o=e.value:"ArrayExpression"===e?.type&&"StringLiteral"===e.elements[0]?.expression?.type&&(o=e.elements[0].expression.value)}c=-1===a?void 0:i.arguments?.[a]?.expression}if("ObjectExpression"===c?.type){const t=e.getObjectPropValue(c,"keyPrefix");l="string"==typeof t?t:void 0}else if("StringLiteral"===c?.type)l=c.value;else if("Identifier"===c?.type)l=this.resolveSimpleStringIdentifier(c.value);else if("TemplateLiteral"===c?.type){const e=c;0===(e.expressions||[]).length&&(l=e.quasis?.[0]?.cooked??void 0)}(o||l)&&this.scope.set(s,{defaultNs:o,keyPrefix:l})}handleUseTranslationDeclarator(t,i,r){let s;if("Identifier"===t.id.type&&(s=t.id.value),"ArrayPattern"===t.id.type){const e=t.id.elements[0];"Identifier"===e?.type&&(s=e.value)}if("ObjectPattern"===t.id.type)for(const e of t.id.properties){if("AssignmentPatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)){s=e.key.value;break}if("KeyValuePatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)&&"Identifier"===e.value.type){s=e.value.value;break}}if(!s)return;const n=r.nsArg??0,a=r.keyPrefixArg??1;let o,l;const p=i.arguments?.[0]?.expression,y=i.arguments?.[1]?.expression,u=i.arguments?.[2]?.expression;var f;let c;if("useTranslation"===r.name&&"StringLiteral"===p?.type&&"StringLiteral"===y?.type&&(f=p.value,/^[a-z]{2,3}([-_][A-Za-z0-9-]+)?$/i.test(f)))o=y.value,c=u;else{if(-1!==n){const e=i.arguments?.[n]?.expression;"StringLiteral"===e?.type?o=e.value:"ArrayExpression"===e?.type&&"StringLiteral"===e.elements[0]?.expression?.type&&(o=e.elements[0].expression.value)}c=-1===a?void 0:i.arguments?.[a]?.expression}if("ObjectExpression"===c?.type){const t=e.getObjectPropValue(c,"keyPrefix");l="string"==typeof t?t:void 0}else if("StringLiteral"===c?.type)l=c.value;else if("Identifier"===c?.type)l=this.resolveSimpleStringIdentifier(c.value);else if("TemplateLiteral"===c?.type){const e=c;0===(e.expressions||[]).length&&(l=e.quasis?.[0]?.cooked??void 0)}this.setVarInScope(s,{defaultNs:o,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const i=e.id.value,r=t.arguments,s=r[1]?.expression,n=r[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===n?.type?n.value:void 0;(a||o)&&this.setVarInScope(i,{defaultNs:a,keyPrefix:o})}handleGetFixedTFromVariableDeclarator(e,t,i){if("Identifier"!==e.id.type)return;const r=e.id.value,s=this.getVarFromScope(i);if(!s)return;const n=t.arguments,a=n[1]?.expression,o=n[2]?.expression,l="StringLiteral"===a?.type?a.value:void 0,p="StringLiteral"===o?.type?o.value:void 0,y=l??s.defaultNs,u=p??s.keyPrefix;(y||u)&&this.setVarInScope(r,{defaultNs:y,keyPrefix:u})}};
|
package/dist/esm/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as o}from"commander";import e from"chokidar";import{glob as t}from"glob";import{minimatch as n}from"minimatch";import i from"chalk";import{ensureConfig as a,loadConfig as r}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as s}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as l}from"./types-generator.js";import{runSyncer as p}from"./syncer.js";import{runMigrator as f}from"./migrator.js";import{runInit as m}from"./init.js";import{runLinterCli as d}from"./linter.js";import{runStatus as g}from"./status.js";import{runLocizeSync as u,runLocizeDownload as y,runLocizeMigrate as h}from"./locize.js";import{runRenameKey as w}from"./rename-key.js";const x=new o;x.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.24.
|
|
2
|
+
import{Command as o}from"commander";import e from"chokidar";import{glob as t}from"glob";import{minimatch as n}from"minimatch";import i from"chalk";import{ensureConfig as a,loadConfig as r}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as s}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as l}from"./types-generator.js";import{runSyncer as p}from"./syncer.js";import{runMigrator as f}from"./migrator.js";import{runInit as m}from"./init.js";import{runLinterCli as d}from"./linter.js";import{runStatus as g}from"./status.js";import{runLocizeSync as u,runLocizeDownload as y,runLocizeMigrate as h}from"./locize.js";import{runRenameKey as w}from"./rename-key.js";const x=new o;x.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.24.10"),x.option("-c, --config <path>","Path to i18next-cli config file (overrides detection)"),x.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.").option("--dry-run","Run the extractor without writing any files to disk.").option("--sync-primary","Sync primary language values with default values from code.").action(async o=>{try{const t=x.opts().config,i=await a(t),r=async()=>{const e=await s(i,{isWatchMode:!!o.watch,isDryRun:!!o.dryRun,syncPrimaryWithDefaults:!!o.syncPrimary});return o.ci&&!e?(console.log("✅ No files were updated."),process.exit(0)):o.ci&&e&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1)),e};if(await r(),o.watch){console.log("\nWatching for changes...");const o=await z(i.extract.input),t=j(i.extract.ignore),a=k(i.extract.output),c=[...t,...a].filter(Boolean),s=o.filter(o=>!c.some(e=>n(o,e,{dot:!0})));e.watch(s,{ignored:/node_modules/,persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),r()})}}catch(o){console.error("Error running extractor:",o),process.exit(1)}}),x.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,e)=>{const t=x.opts().config;let n=await r(t);if(!n){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!")),n=o}await g(n,{detail:o,namespace:e.namespace})}),x.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 t=x.opts().config,i=await a(t),r=()=>l(i);if(await r(),o.watch){console.log("\nWatching for changes...");const o=await z(i.types?.input||[]),t=[...j(i.extract?.ignore)].filter(Boolean),a=o.filter(o=>!t.some(e=>n(o,e,{dot:!0})));e.watch(a,{persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),r()})}}),x.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const o=x.opts().config,e=await a(o);await p(e)}),x.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async o=>{await f(o)}),x.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(m),x.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async o=>{const t=x.opts().config,a=async()=>{let o=await r(t);if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const e=await c();e||(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!")),o=e}await d(o)};if(await a(),o.watch){console.log("\nWatching for changes...");const o=await r(t);if(o?.extract?.input){const t=await z(o.extract.input),i=[...j(o.extract.ignore),...k(o.extract.output)].filter(Boolean),r=t.filter(o=>!i.some(e=>n(o,e,{dot:!0})));e.watch(r,{ignored:/node_modules/,persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),a()})}}}),x.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 e=x.opts().config,t=await a(e);await u(t,o)}),x.command("locize-download").description("Download all translations from your locize project.").action(async o=>{const e=x.opts().config,t=await a(e);await y(t,o)}),x.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async o=>{const e=x.opts().config,t=await a(e);await h(t,o)}),x.command("rename-key <oldKey> <newKey>").description("Rename a translation key across all source files and translation files.").option("--dry-run","Preview changes without modifying files").action(async(o,e,t)=>{try{const n=x.opts().config,r=await a(n),c=await w(r,o,e,t);c.success||(c.conflicts&&(console.error(i.red("\n❌ Conflicts detected:")),c.conflicts.forEach(o=>console.error(` - ${o}`))),c.error&&console.error(i.red(`\n❌ ${c.error}`)),process.exit(1));0===c.sourceFiles.reduce((o,e)=>o+e.changes,0)&&console.log(i.yellow(`\n⚠️ No usages found for "${o}"`))}catch(o){console.error(i.red("Error renaming key:"),o),process.exit(1)}}),x.parse(process.argv);const j=o=>Array.isArray(o)?o:o?[o]:[],k=o=>o&&"string"==typeof o?[o.replace(/\{\{[^}]+\}\}/g,"*")]:[],z=async(o=[])=>{const e=j(o),n=await Promise.all(e.map(o=>t(o||"",{nodir:!0})));return Array.from(new Set(n.flat()))};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getObjectPropValue as e}from"./ast-utils.js";class t{scopeStack=[];config;scope=new Map;simpleConstants=new Map;constructor(e){this.config=e}reset(){this.scopeStack=[],this.scope=new Map,this.simpleConstants.clear()}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):this.scope.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)}const t=this.scope.get(e);if(t)return t}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const i of t){if("string"==typeof i&&i===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof i&&i.name===e)return{name:i.name,nsArg:i.nsArg??0,keyPrefixArg:i.keyPrefixArg??1}}}resolveSimpleStringIdentifier(e){return this.simpleConstants.get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;"Identifier"===e.id.type&&"StringLiteral"===t.type&&this.simpleConstants.set(e.id.value,t.value);const i="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!i)return;const r=i.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return this.handleUseTranslationDeclarator(e,i,t),void this.handleUseTranslationForComments(e,i,t)}if("Identifier"===r.type){if(this.getVarFromScope(r.value))return void this.handleGetFixedTFromVariableDeclarator(e,i,r.value)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,i)}handleUseTranslationForComments(
|
|
1
|
+
import{getObjectPropValue as e}from"./ast-utils.js";class t{scopeStack=[];config;scope=new Map;simpleConstants=new Map;constructor(e){this.config=e}reset(){this.scopeStack=[],this.scope=new Map,this.simpleConstants.clear()}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):this.scope.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)}const t=this.scope.get(e);if(t)return t}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const i of t){if("string"==typeof i&&i===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof i&&i.name===e)return{name:i.name,nsArg:i.nsArg??0,keyPrefixArg:i.keyPrefixArg??1}}}resolveSimpleStringIdentifier(e){return this.simpleConstants.get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;"Identifier"===e.id.type&&"StringLiteral"===t.type&&this.simpleConstants.set(e.id.value,t.value);const i="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!i)return;const r=i.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return this.handleUseTranslationDeclarator(e,i,t),void this.handleUseTranslationForComments(e,i,t)}if("Identifier"===r.type){if(this.getVarFromScope(r.value))return void this.handleGetFixedTFromVariableDeclarator(e,i,r.value)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,i)}handleUseTranslationForComments(t,i,r){let s;if("Identifier"===t.id.type&&(s=t.id.value),"ArrayPattern"===t.id.type){const e=t.id.elements[0];"Identifier"===e?.type&&(s=e.value)}if("ObjectPattern"===t.id.type)for(const e of t.id.properties){if("AssignmentPatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)){s=e.key.value;break}if("KeyValuePatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)&&"Identifier"===e.value.type){s=e.value.value;break}}if(!s)return;const n=r.nsArg??0,a=r.keyPrefixArg??1;let o,l;const p=i.arguments?.[0]?.expression,y=i.arguments?.[1]?.expression,f=i.arguments?.[2]?.expression;var u;let c;if("useTranslation"===r.name&&"StringLiteral"===p?.type&&"StringLiteral"===y?.type&&(u=p.value,/^[a-z]{2,3}([-_][A-Za-z0-9-]+)?$/i.test(u)))o=y.value,c=f;else{if(-1!==n){const e=i.arguments?.[n]?.expression;"StringLiteral"===e?.type?o=e.value:"ArrayExpression"===e?.type&&"StringLiteral"===e.elements[0]?.expression?.type&&(o=e.elements[0].expression.value)}c=-1===a?void 0:i.arguments?.[a]?.expression}if("ObjectExpression"===c?.type){const t=e(c,"keyPrefix");l="string"==typeof t?t:void 0}else if("StringLiteral"===c?.type)l=c.value;else if("Identifier"===c?.type)l=this.resolveSimpleStringIdentifier(c.value);else if("TemplateLiteral"===c?.type){const e=c;0===(e.expressions||[]).length&&(l=e.quasis?.[0]?.cooked??void 0)}(o||l)&&this.scope.set(s,{defaultNs:o,keyPrefix:l})}handleUseTranslationDeclarator(t,i,r){let s;if("Identifier"===t.id.type&&(s=t.id.value),"ArrayPattern"===t.id.type){const e=t.id.elements[0];"Identifier"===e?.type&&(s=e.value)}if("ObjectPattern"===t.id.type)for(const e of t.id.properties){if("AssignmentPatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)){s=e.key.value;break}if("KeyValuePatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)&&"Identifier"===e.value.type){s=e.value.value;break}}if(!s)return;const n=r.nsArg??0,a=r.keyPrefixArg??1;let o,l;const p=i.arguments?.[0]?.expression,y=i.arguments?.[1]?.expression,f=i.arguments?.[2]?.expression;var u;let c;if("useTranslation"===r.name&&"StringLiteral"===p?.type&&"StringLiteral"===y?.type&&(u=p.value,/^[a-z]{2,3}([-_][A-Za-z0-9-]+)?$/i.test(u)))o=y.value,c=f;else{if(-1!==n){const e=i.arguments?.[n]?.expression;"StringLiteral"===e?.type?o=e.value:"ArrayExpression"===e?.type&&"StringLiteral"===e.elements[0]?.expression?.type&&(o=e.elements[0].expression.value)}c=-1===a?void 0:i.arguments?.[a]?.expression}if("ObjectExpression"===c?.type){const t=e(c,"keyPrefix");l="string"==typeof t?t:void 0}else if("StringLiteral"===c?.type)l=c.value;else if("Identifier"===c?.type)l=this.resolveSimpleStringIdentifier(c.value);else if("TemplateLiteral"===c?.type){const e=c;0===(e.expressions||[]).length&&(l=e.quasis?.[0]?.cooked??void 0)}this.setVarInScope(s,{defaultNs:o,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const i=e.id.value,r=t.arguments,s=r[1]?.expression,n=r[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===n?.type?n.value:void 0;(a||o)&&this.setVarInScope(i,{defaultNs:a,keyPrefix:o})}handleGetFixedTFromVariableDeclarator(e,t,i){if("Identifier"!==e.id.type)return;const r=e.id.value,s=this.getVarFromScope(i);if(!s)return;const n=t.arguments,a=n[1]?.expression,o=n[2]?.expression,l="StringLiteral"===a?.type?a.value:void 0,p="StringLiteral"===o?.type?o.value:void 0,y=l??s.defaultNs,f=p??s.keyPrefix;(y||f)&&this.setVarInScope(r,{defaultNs:y,keyPrefix:f})}}export{t as ScopeManager};
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -23,7 +23,7 @@ const program = new Command()
|
|
|
23
23
|
program
|
|
24
24
|
.name('i18next-cli')
|
|
25
25
|
.description('A unified, high-performance i18next CLI.')
|
|
26
|
-
.version('1.24.
|
|
26
|
+
.version('1.24.10')
|
|
27
27
|
|
|
28
28
|
// new: global config override option
|
|
29
29
|
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)')
|
|
@@ -222,49 +222,48 @@ export class ScopeManager {
|
|
|
222
222
|
// If we couldn't find a `t` function being declared, exit
|
|
223
223
|
if (!variableName) return
|
|
224
224
|
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
const thirdArg = callExpr.arguments?.[2]?.expression
|
|
225
|
+
// Position-driven extraction: respect hookConfig positions (nsArg/keyPrefixArg).
|
|
226
|
+
// nsArg === -1 means "no namespace arg"; keyPrefixArg === -1 means "no keyPrefix arg".
|
|
227
|
+
const nsArgIndex = hookConfig.nsArg ?? 0
|
|
228
|
+
const kpArgIndex = hookConfig.keyPrefixArg ?? 1
|
|
230
229
|
|
|
231
230
|
let defaultNs: string | undefined
|
|
232
231
|
let keyPrefix: string | undefined
|
|
233
232
|
|
|
234
|
-
//
|
|
233
|
+
// Early detection of react-i18next common form: useTranslation(lng, ns)
|
|
234
|
+
// Only apply for the built-in hook name to avoid interfering with custom hooks.
|
|
235
|
+
const first = callExpr.arguments?.[0]?.expression
|
|
236
|
+
const second = callExpr.arguments?.[1]?.expression
|
|
237
|
+
const third = callExpr.arguments?.[2]?.expression
|
|
235
238
|
const looksLikeLanguage = (s: string) => /^[a-z]{2,3}([-_][A-Za-z0-9-]+)?$/i.test(s)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
239
|
+
const isBuiltInLngNsForm = hookConfig.name === 'useTranslation' &&
|
|
240
|
+
first?.type === 'StringLiteral' &&
|
|
241
|
+
second?.type === 'StringLiteral' &&
|
|
242
|
+
looksLikeLanguage(first.value)
|
|
243
|
+
|
|
244
|
+
let kpArg
|
|
245
|
+
if (isBuiltInLngNsForm) {
|
|
246
|
+
// treat as useTranslation(lng, ns, [options])
|
|
247
|
+
defaultNs = second.value
|
|
248
|
+
// prefer third arg as keyPrefix (may be undefined)
|
|
249
|
+
kpArg = third
|
|
250
|
+
} else {
|
|
251
|
+
// Position-driven extraction: respect hookConfig positions (nsArg/keyPrefixArg).
|
|
252
|
+
if (nsArgIndex !== -1) {
|
|
253
|
+
const nsNode = callExpr.arguments?.[nsArgIndex]?.expression
|
|
254
|
+
if (nsNode?.type === 'StringLiteral') {
|
|
255
|
+
defaultNs = nsNode.value
|
|
256
|
+
} else if (nsNode?.type === 'ArrayExpression' && nsNode.elements[0]?.expression?.type === 'StringLiteral') {
|
|
257
|
+
defaultNs = nsNode.elements[0].expression.value
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
kpArg = kpArgIndex === -1 ? undefined : callExpr.arguments?.[kpArgIndex]?.expression
|
|
251
261
|
}
|
|
252
262
|
|
|
253
|
-
// Parse keyPrefix: accept either { keyPrefix: 'x' } or a plain string arg or simple identifier/template literal
|
|
254
|
-
// When the call is useTranslation(lng, ns) prefer the 3rd arg for keyPrefix; otherwise use configured keyPrefixArg.
|
|
255
|
-
const possibleKeyPrefixArg = isLngNs ? thirdArg : callExpr.arguments?.[hookConfig.keyPrefixArg ?? 1]?.expression
|
|
256
|
-
const kpArg = possibleKeyPrefixArg
|
|
257
263
|
if (kpArg?.type === 'ObjectExpression') {
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
prop.key.type === 'Identifier' &&
|
|
261
|
-
prop.key.value === 'keyPrefix'
|
|
262
|
-
)
|
|
263
|
-
if (keyPrefixProp?.type === 'KeyValueProperty' && keyPrefixProp.value.type === 'StringLiteral') {
|
|
264
|
-
keyPrefix = keyPrefixProp.value.value
|
|
265
|
-
}
|
|
264
|
+
const kp = getObjectPropValue(kpArg, 'keyPrefix')
|
|
265
|
+
keyPrefix = typeof kp === 'string' ? kp : undefined
|
|
266
266
|
} else if (kpArg?.type === 'StringLiteral') {
|
|
267
|
-
// allow keyPrefix as direct string argument
|
|
268
267
|
keyPrefix = kpArg.value
|
|
269
268
|
} else if (kpArg?.type === 'Identifier') {
|
|
270
269
|
keyPrefix = this.resolveSimpleStringIdentifier(kpArg.value)
|
|
@@ -329,35 +328,38 @@ export class ScopeManager {
|
|
|
329
328
|
// If we couldn't find a `t` function being declared, exit
|
|
330
329
|
if (!variableName) return
|
|
331
330
|
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
const
|
|
331
|
+
// Position-driven extraction: respect hookConfig positions (nsArg/keyPrefixArg).
|
|
332
|
+
const nsArgIndex = hookConfig.nsArg ?? 0
|
|
333
|
+
const kpArgIndex = hookConfig.keyPrefixArg ?? 1
|
|
335
334
|
|
|
336
335
|
let defaultNs: string | undefined
|
|
337
336
|
let keyPrefix: string | undefined
|
|
338
|
-
// Heuristic to detect language-like strings (e.g. "en", "pt-BR")
|
|
339
|
-
const looksLikeLanguage = (s: string) => /^[a-z]{2,3}([-_][A-Za-z0-9-]+)?$/i.test(s)
|
|
340
337
|
|
|
341
|
-
//
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
338
|
+
// Early detect useTranslation(lng, ns) for built-in hook name only
|
|
339
|
+
const first = callExpr.arguments?.[0]?.expression
|
|
340
|
+
const second = callExpr.arguments?.[1]?.expression
|
|
341
|
+
const third = callExpr.arguments?.[2]?.expression
|
|
342
|
+
const looksLikeLanguage = (s: string) => /^[a-z]{2,3}([-_][A-Za-z0-9-]+)?$/i.test(s)
|
|
343
|
+
const isBuiltInLngNsForm = hookConfig.name === 'useTranslation' &&
|
|
344
|
+
first?.type === 'StringLiteral' &&
|
|
345
|
+
second?.type === 'StringLiteral' &&
|
|
346
|
+
looksLikeLanguage(first.value)
|
|
347
|
+
|
|
348
|
+
let kpArg
|
|
349
|
+
if (isBuiltInLngNsForm) {
|
|
350
|
+
defaultNs = second.value
|
|
351
|
+
kpArg = third
|
|
352
|
+
} else {
|
|
353
|
+
if (nsArgIndex !== -1) {
|
|
354
|
+
const nsNode = callExpr.arguments?.[nsArgIndex]?.expression
|
|
355
|
+
if (nsNode?.type === 'StringLiteral') defaultNs = nsNode.value
|
|
356
|
+
else if (nsNode?.type === 'ArrayExpression' && nsNode.elements[0]?.expression?.type === 'StringLiteral') {
|
|
357
|
+
defaultNs = nsNode.elements[0].expression.value
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
kpArg = kpArgIndex === -1 ? undefined : callExpr.arguments?.[kpArgIndex]?.expression
|
|
355
361
|
}
|
|
356
362
|
|
|
357
|
-
// Determine keyPrefix: when using useTranslation(lng, ns) prefer the 3rd arg
|
|
358
|
-
// otherwise use the configured keyPrefixArg position.
|
|
359
|
-
const optionsArg = isLngNs ? thirdArg : callExpr.arguments?.[hookConfig.keyPrefixArg ?? 1]?.expression
|
|
360
|
-
const kpArg = optionsArg
|
|
361
363
|
if (kpArg?.type === 'ObjectExpression') {
|
|
362
364
|
const kp = getObjectPropValue(kpArg, 'keyPrefix')
|
|
363
365
|
keyPrefix = typeof kp === 'string' ? kp : undefined
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/scope-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAmC,MAAM,WAAW,CAAA;AACpF,OAAO,KAAK,EAAE,SAAS,EAA4B,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAG5F,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,KAAK,CAAqE;IAGlF,OAAO,CAAC,eAAe,CAAiC;gBAE3C,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC;IAI1D;;;;;;OAMG;IACI,KAAK,IAAK,IAAI;IAMrB;;;OAGG;IACH,UAAU,IAAK,IAAI;IAInB;;;OAGG;IACH,SAAS,IAAK,IAAI;IAIlB;;;;;;OAMG;IACH,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAUnD;;;;;;OAMG;IACH,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkBrD,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAIrC;;;;;;;;;;OAUG;IACH,wBAAwB,CAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAwDzD;;;;;;;;OAQG;IACH,OAAO,CAAC,+BAA+B;
|
|
1
|
+
{"version":3,"file":"scope-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/scope-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAmC,MAAM,WAAW,CAAA;AACpF,OAAO,KAAK,EAAE,SAAS,EAA4B,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAG5F,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,KAAK,CAAqE;IAGlF,OAAO,CAAC,eAAe,CAAiC;gBAE3C,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC;IAI1D;;;;;;OAMG;IACI,KAAK,IAAK,IAAI;IAMrB;;;OAGG;IACH,UAAU,IAAK,IAAI;IAInB;;;OAGG;IACH,SAAS,IAAK,IAAI;IAIlB;;;;;;OAMG;IACH,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAUnD;;;;;;OAMG;IACH,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkBrD,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAIrC;;;;;;;;;;OAUG;IACH,wBAAwB,CAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAwDzD;;;;;;;;OAQG;IACH,OAAO,CAAC,+BAA+B;IA4FvC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,8BAA8B;IAoFtC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;OASG;IACH,OAAO,CAAC,qCAAqC;CAuB9C"}
|