i18next-cli 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ 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.6.1](https://github.com/i18next/i18next-cli/compare/v1.6.0...v1.6.1) - 2025-10-05
9
+
10
+ - **Extractor (Comments):** Fixed namespace scope resolution for `t()` calls in comments. Commented translation calls like `// t("Private")` now correctly inherit the namespace from the surrounding `useTranslation('access')` scope instead of defaulting to the default namespace, matching i18next-parser behavior. This ensures consistency between commented and actual translation calls within the same component scope. [#44](https://github.com/i18next/i18next-cli/issues/44)
11
+ - **Extractor:** Loosened context value parsing to handle edge cases where empty strings or dynamic expressions in context options could cause extraction failures. The parser now gracefully handles various context value types and expressions. [#48](https://github.com/i18next/i18next-cli/pull/48)
12
+ - **Plugin System:** Fixed plugin execution timing by running `onVisitNode` hooks inline during AST traversal instead of after it. This ensures plugins have access to scope information (like `getVarFromScope`) when processing nodes, enabling more sophisticated custom extraction logic. Plugins can now properly access variable scope context during the main AST walking phase. [#47](https://github.com/i18next/i18next-cli/pull/47)
13
+
8
14
  ## [1.6.0](https://github.com/i18next/i18next-cli/compare/v1.5.11...v1.6.0) - 2025-10-05
9
15
 
10
16
  ### Added
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"),n=require("glob"),o=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("1.6.0"),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.").option("--dry-run","Run the extractor without writing any files to disk.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a,{isWatchMode:e.watch,isDryRun:e.dryRun});e.ci&&t&&(console.error(o.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(o.yellow("šŸ’” Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${o.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await n.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 n=await i.loadConfig();if(!n){console.log(o.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),n=e}await g.runStatus(n,{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 o=await i.ensureConfig(),a=()=>c.runTypesGenerator(o);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(o.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 [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await l.runMigrator(e)}),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.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const r=async()=>{let e=await i.loadConfig();if(!e){console.log(o.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),e=t}await d.runLinter(e)};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await i.loadConfig();if(e?.extract?.input){t.watch(await n.glob(e.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}}),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);
2
+ "use strict";var e=require("commander"),t=require("chokidar"),n=require("glob"),o=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("1.6.1"),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.").option("--dry-run","Run the extractor without writing any files to disk.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a,{isWatchMode:e.watch,isDryRun:e.dryRun});e.ci&&t&&(console.error(o.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(o.yellow("šŸ’” Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${o.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await n.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 n=await i.loadConfig();if(!n){console.log(o.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),n=e}await g.runStatus(n,{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 o=await i.ensureConfig(),a=()=>c.runTypesGenerator(o);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(o.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 [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await l.runMigrator(e)}),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.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const r=async()=>{let e=await i.loadConfig();if(!e){console.log(o.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),e=t}await d.runLinter(e)};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await i.loadConfig();if(e?.extract?.input){t.watch(await n.glob(e.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}}),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 e=require("ora"),t=require("chalk"),r=require("@swc/core"),a=require("node:fs/promises"),n=require("node:path"),o=require("./key-finder.js"),i=require("./translation-manager.js"),s=require("../../utils/validation.js"),c=require("../plugin-manager.js"),l=require("../parsers/comment-parser.js"),u=require("../../utils/logger.js"),g=require("../../utils/file-utils.js"),f=require("../../utils/funnel-msg-tracker.js");function d(e,t,r,a=new u.ConsoleLogger){if(e&&"object"==typeof e){for(const n of t)try{n.onVisitNode?.(e,r)}catch(e){a.warn(`Plugin ${n.name} onVisitNode failed:`,e)}for(const n of Object.keys(e)){const o=e[n];if(Array.isArray(o))for(const e of o)e&&"object"==typeof e&&d(e,t,r,a);else o&&"object"==typeof o&&d(o,t,r,a)}}}exports.extract=async function(e){e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(t=>t!==e?.extract?.primaryLanguage),e.extract.functions||=["t","*.t"],e.extract.transComponents||=["Trans"];const{allKeys:t,objectKeys:r}=await o.findKeys(e);return i.getTranslations(t,r,e)},exports.processFile=async function(e,t,n,o,i=new u.ConsoleLogger){try{let s=await a.readFile(e,"utf-8");for(const r of t.plugins||[])try{const t=await(r.onLoad?.(s,e));void 0!==t&&(s=t)}catch(e){i.warn(`Plugin ${r.name} onLoad failed:`,e)}const u=await r.parse(s,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0}),g=c.createPluginContext(n,t,i);g.getVarFromScope=o.getVarFromScope.bind(o),l.extractKeysFromComments(s,g,t,o.getVarFromScope.bind(o)),o.visit(u),(t.plugins||[]).length>0&&d(u,t.plugins||[],g,i)}catch(t){throw new s.ExtractorError("Failed to process file",e,t)}},exports.runExtractor=async function(r,{isWatchMode:c=!1,isDryRun:l=!1}={},d=new u.ConsoleLogger){r.extract.primaryLanguage||=r.locales[0]||"en",r.extract.secondaryLanguages||=r.locales.filter(e=>e!==r?.extract?.primaryLanguage),r.extract.functions||=["t","*.t"],r.extract.transComponents||=["Trans"],s.validateExtractorConfig(r);const p=e("Running i18next key extractor...\n").start();try{const{allKeys:e,objectKeys:s}=await o.findKeys(r,d);p.text=`Found ${e.size} unique keys. Updating translation files...`;const c=await i.getTranslations(e,s,r);let u=!1;for(const e of c)if(e.updated&&(u=!0,!l)){const o=g.serializeTranslationFile(e.newTranslations,r.extract.outputFormat,r.extract.indentation);await a.mkdir(n.dirname(e.path),{recursive:!0}),await a.writeFile(e.path,o),d.info(t.green(`Updated: ${e.path}`))}if((r.plugins||[]).length>0){p.text="Running post-extraction plugins...";for(const e of r.plugins||[])await(e.afterSync?.(c,r))}return p.succeed(t.bold("Extraction complete!")),u&&await async function(){if(!await f.shouldShowFunnel("extract"))return;return console.log(t.yellow.bold("\nšŸ’” Tip: Tired of running the extractor manually?")),console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,'),console.log(" where keys are created and translated automatically as you code."),console.log(` Learn more: ${t.cyan("https://www.locize.com/blog/i18next-savemissing-ai-automation")}`),console.log(` Watch the video: ${t.cyan("https://youtu.be/joPsZghT3wM")}`),f.recordFunnelShown("extract")}(),u}catch(e){throw p.fail(t.red("Extraction failed.")),e}};
1
+ "use strict";var e=require("ora"),t=require("chalk"),a=require("@swc/core"),r=require("node:fs/promises"),n=require("node:path"),o=require("./key-finder.js"),s=require("./translation-manager.js"),i=require("../../utils/validation.js"),c=require("../parsers/comment-parser.js"),l=require("../../utils/logger.js"),u=require("../../utils/file-utils.js"),g=require("../../utils/funnel-msg-tracker.js");exports.extract=async function(e){e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(t=>t!==e?.extract?.primaryLanguage),e.extract.functions||=["t","*.t"],e.extract.transComponents||=["Trans"];const{allKeys:t,objectKeys:a}=await o.findKeys(e);return s.getTranslations(t,a,e)},exports.processFile=async function(e,t,n,o,s,u=new l.ConsoleLogger){try{let i=await r.readFile(e,"utf-8");for(const a of t)try{const t=await(a.onLoad?.(i,e));void 0!==t&&(i=t)}catch(e){u.warn(`Plugin ${a.name} onLoad failed:`,e)}const l=await a.parse(i,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0});o.getVarFromScope=n.getVarFromScope.bind(n),n.visit(l),c.extractKeysFromComments(i,o,s,n.getVarFromScope.bind(n))}catch(t){throw new i.ExtractorError("Failed to process file",e,t)}},exports.runExtractor=async function(a,{isWatchMode:c=!1,isDryRun:d=!1}={},p=new l.ConsoleLogger){a.extract.primaryLanguage||=a.locales[0]||"en",a.extract.secondaryLanguages||=a.locales.filter(e=>e!==a?.extract?.primaryLanguage),a.extract.functions||=["t","*.t"],a.extract.transComponents||=["Trans"],i.validateExtractorConfig(a);const y=e("Running i18next key extractor...\n").start();try{const{allKeys:e,objectKeys:i}=await o.findKeys(a,p);y.text=`Found ${e.size} unique keys. Updating translation files...`;const c=await s.getTranslations(e,i,a);let l=!1;for(const e of c)if(e.updated&&(l=!0,!d)){const o=u.serializeTranslationFile(e.newTranslations,a.extract.outputFormat,a.extract.indentation);await r.mkdir(n.dirname(e.path),{recursive:!0}),await r.writeFile(e.path,o),p.info(t.green(`Updated: ${e.path}`))}if((a.plugins||[]).length>0){y.text="Running post-extraction plugins...";for(const e of a.plugins||[])await(e.afterSync?.(c,a))}return y.succeed(t.bold("Extraction complete!")),l&&await async function(){if(!await g.shouldShowFunnel("extract"))return;return console.log(t.yellow.bold("\nšŸ’” Tip: Tired of running the extractor manually?")),console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,'),console.log(" where keys are created and translated automatically as you code."),console.log(` Learn more: ${t.cyan("https://www.locize.com/blog/i18next-savemissing-ai-automation")}`),console.log(` Watch the video: ${t.cyan("https://youtu.be/joPsZghT3wM")}`),g.recordFunnelShown("extract")}(),l}catch(e){throw y.fail(t.red("Extraction failed.")),e}};
@@ -1 +1 @@
1
- "use strict";var e=require("glob"),r=require("./extractor.js"),t=require("../../utils/logger.js"),i=require("../plugin-manager.js"),o=require("../parsers/ast-visitors.js");exports.findKeys=async function(n,s=new t.ConsoleLogger){const a=await async function(r){const t=["node_modules/**"],i=Array.isArray(r.extract.ignore)?r.extract.ignore:r.extract.ignore?[r.extract.ignore]:[];return await e.glob(r.extract.input,{ignore:[...t,...i],cwd:process.cwd()})}(n),c=new Map,g=i.createPluginContext(c,n,s),u=new o.ASTVisitors(n,g,s);g.getVarFromScope=u.getVarFromScope.bind(u),await i.initializePlugins(n.plugins||[]);for(const e of a)await r.processFile(e,n,c,u,s);for(const e of n.plugins||[])await(e.onEnd?.(c));return{allKeys:c,objectKeys:u.objectKeys}};
1
+ "use strict";var e=require("glob"),r=require("./extractor.js"),o=require("../../utils/logger.js"),t=require("../plugin-manager.js"),i=require("../parsers/ast-visitors.js");exports.findKeys=async function(n,s=new o.ConsoleLogger){const{plugins:a,...c}=n,g=a||[],u=await async function(r){const o=["node_modules/**"],t=Array.isArray(r.extract.ignore)?r.extract.ignore:r.extract.ignore?[r.extract.ignore]:[];return await e.glob(r.extract.input,{ignore:[...o,...t],cwd:process.cwd()})}(n),l=new Map,d=t.createPluginContext(l,g,c,s),f={onBeforeVisitNode:e=>{for(const r of g)try{r.onVisitNode?.(e,d)}catch(e){s.warn(`Plugin ${r.name} onVisitNode failed:`,e)}}},w=new i.ASTVisitors(c,d,s,f);d.getVarFromScope=w.getVarFromScope.bind(w),await t.initializePlugins(g);for(const e of u)await r.processFile(e,g,w,d,c,s);for(const e of g)await(e.onEnd?.(l));return{allKeys:l,objectKeys:w.objectKeys}};
@@ -1 +1 @@
1
- "use strict";var e=require("./jsx-parser.js"),t=require("./ast-utils.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&&"object"==typeof n&&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,n,r){let s;if("Identifier"===e.id.type&&(s=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(s=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){s="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){s=t.value.value;break}}if(!s)return;const i=n.arguments?.[r.nsArg]?.expression;let a;"StringLiteral"===i?.type?a=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(a=i.elements[0].expression.value);const o=n.arguments?.[r.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t.getObjectPropValue(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(s,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,r=t.arguments,s=r[1]?.expression,i=r[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===i?.type?i.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const n=this.getFunctionName(e.callee);if(!n)return;const r=this.getVarFromScope(n),s=this.config.extract.functions||["t","*.t"];let i=void 0!==r;if(!i)for(const e of s)if(e.startsWith("*.")){if(n.endsWith(e.substring(1))){i=!0;break}}else if(e===n){i=!0;break}if(!i||0===e.arguments.length)return;const{keysToProcess:a,isSelectorAPI:o}=this.handleCallExpressionArgument(e,0);if(0===a.length)return;let l=!1;const u=this.config.extract.pluralSeparator??"_";for(let e=0;e<a.length;e++)a[e].endsWith(`${u}ordinal`)&&(l=!0,a[e]=a[e].slice(0,-8));let p,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(p=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const f=c?t.getObjectPropValue(c,"defaultValue"):void 0,g="string"==typeof f?f:p;for(let e=0;e<a.length;e++){let n,s=a[e];if(c){const e=t.getObjectPropValue(c,"ns");"string"==typeof e&&(n=e)}const i=this.config.extract.nsSeparator??":";if(!n&&i&&s.includes(i)){const e=s.split(i);n=e.shift(),s=e.join(i)}!n&&r?.defaultNs&&(n=r.defaultNs),n||(n=this.config.extract.defaultNS);let u=s;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";u=`${r.keyPrefix}${e}${s}`}const p=e===a.length-1&&g||s;if(c){const e=t.getObjectProperty(c,"context"),r=[];if("ConditionalExpression"===e?.value?.type){const t=this.resolvePossibleStringValues(e.value),s=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{r.push({key:`${u}${s}${e}`,ns:n,defaultValue:p})}),r.push({key:u,ns:n,defaultValue:p}))}else if("StringLiteral"===e?.value?.type){const t=e.value.value,s=this.config.extract.contextSeparator??"_";r.push({key:`${u}${s}${t}`,ns:n,defaultValue:p})}const s=void 0!==t.getObjectPropValue(c,"count"),i=!0===t.getObjectPropValue(c,"ordinal");if(s||l){if(r.length>0)for(const{key:e,ns:t}of r)this.handlePluralKeys(e,t,c,i||l);else this.handlePluralKeys(u,n,c,i||l);continue}if(r.length>0){r.forEach(this.pluginContext.addKey);continue}!0===t.getObjectPropValue(c,"returnObjects")&&this.objectKeys.add(u)}o&&this.objectKeys.add(u),this.pluginContext.addKey({key:u,ns:n,defaultValue:p})}}handleCallExpressionArgument(e,t){const n=e.arguments[t].expression,r=[];let s=!1;if("ArrowFunctionExpression"===n.type){const e=this.extractKeyFromSelector(n);e&&(r.push(e),s=!0)}else if("ArrayExpression"===n.type)for(const e of n.elements)e?.expression&&r.push(...this.resolvePossibleStringValues(e.expression));else r.push(...this.resolvePossibleStringValues(n));return{keysToProcess:r.filter(e=>!!e),isSelectorAPI:s}}handlePluralKeys(e,n,r,s){try{const i=s?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t.getObjectPropValue(r,"defaultValue"),u=t.getObjectPropValue(r,`defaultValue${o}other`),p=t.getObjectPropValue(r,`defaultValue${o}ordinal${o}other`);for(const i of a){const a=s?`defaultValue${o}ordinal${o}${i}`:`defaultValue${o}${i}`,c=t.getObjectPropValue(r,a);let f;f="string"==typeof c?c:"one"===i&&"string"==typeof l?l:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof l?l:e:u;const g=s?`${e}${o}ordinal${o}${i}`:`${e}${o}${i}`;this.pluginContext.addKey({key:g,ns:n,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(s){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=t.getObjectPropValue(r,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,n){try{const r=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,s=this.config.extract.pluralSeparator??"_";for(const i of r)this.pluginContext.addKey({key:`${e}${s}${i}`,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),r=[];if(n){if(n.keyExpression){const e=this.resolvePossibleStringValues(n.keyExpression);r.push(...e)}else r.push(n.serializedChildren);let e;const{contextExpression:s,optionsNode:i,defaultValue:a,hasCount:o,isOrdinal:l,serializedChildren:u}=n;if(n.ns){const{ns:t}=n;e=r.map(e=>({key:e,ns:t,defaultValue:a||u,hasCount:o,isOrdinal:l}))}else{e=r.map(e=>{const t=this.config.extract.nsSeparator??":";let n;if(t&&e.includes(t)){let r;[n,...r]=e.split(t),e=r.join(t)}return{key:e,ns:n,defaultValue:a||u,hasCount:o,isOrdinal:l}});const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===n?.type&&"JSXExpressionContainer"===n.value?.type&&"Identifier"===n.value.expression.type){const t=n.value.expression.value,r=this.getVarFromScope(t);r?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=r.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),s&&o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!n,a=this.resolvePossibleStringValues(s),o=this.config.extract.contextSeparator??"_";if(a.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i));for(const t of a)for(const n of e){const e=`${n.key}${o}${t}`;this.generatePluralKeysForTrans(e,n.defaultValue,n.ns,r,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i))}else if(s){const t=this.resolvePossibleStringValues(s),n=this.config.extract.contextSeparator??"_";if(t.length>0){for(const r of t)for(const{key:t,ns:s,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${n}${r}`,ns:s,defaultValue:i});"StringLiteral"!==s.type&&e.forEach(this.pluginContext.addKey)}}else if(o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!n;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,n,r,s,i){try{const a=s?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t.getObjectPropValue(i,`defaultValue${l}other`),p=t.getObjectPropValue(i,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=s?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`,c=i?t.getObjectPropValue(i,o):void 0;let f;f="string"==typeof c?c:"one"===a&&"string"==typeof n?n:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof n?n:e:u;const g=s?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:g,ns:r,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:r,defaultValue: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(".")}}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,t=!1){if("StringLiteral"===e.type)return e.value||t?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent,t),...this.resolvePossibleStringValues(e.alternate,t)]}return"Identifier"===e.type&&"undefined"===e.value?[]:"TemplateLiteral"===e.type?this.resolvePossibleStringValuesFromTemplateString(e):"NumericLiteral"===e.type||"BooleanLiteral"===e.type?[`${e.value}`]:[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...n]=e.quasis;return e.expressions.reduce((e,t,r)=>e.flatMap(e=>{const s=n[r]?.cooked??"";return this.resolvePossibleStringValues(t,!0).map(t=>`${e}${t}${s}`)}),[t.cooked??""])}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}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}};
1
+ "use strict";var e=require("./jsx-parser.js"),t=require("./ast-utils.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];hooks;objectKeys=new Set;scope=new Map;constructor(e,t,r,n){this.pluginContext=t,this.config=e,this.logger=r,this.hooks={onBeforeVisitNode:n?.onBeforeVisitNode,onAfterVisitNode:n?.onAfterVisitNode}}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),this.hooks.onBeforeVisitNode?.(e),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}this.hooks.onAfterVisitNode?.(e);for(const t in e){if("span"===t)continue;const r=e[t];if(Array.isArray(r))for(const e of r)e&&"object"==typeof e&&this.walk(e);else r&&"object"==typeof r&&this.walk(r)}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)}const t=this.scope.get(e);if(t)return t}handleVariableDeclarator(e){const t=e.init;if(!t)return;const r="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!r)return;const n=r.callee;if("Identifier"===n.type){const t=this.getUseTranslationConfig(n.value);if(t)return this.handleUseTranslationDeclarator(e,r,t),void this.handleUseTranslationForComments(e,r,t)}"MemberExpression"===n.type&&"Identifier"===n.property.type&&"getFixedT"===n.property.value&&this.handleGetFixedTDeclarator(e,r)}handleUseTranslationForComments(e,t,r){let n;if("Identifier"===e.id.type&&(n=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(n=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){n="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){n=t.value.value;break}}if(!n)return;const s=t.arguments?.[r.nsArg]?.expression,i=t.arguments?.[r.keyPrefixArg]?.expression;let a,o;if("StringLiteral"===s?.type?a=s.value:"ArrayExpression"===s?.type&&"StringLiteral"===s.elements[0]?.expression.type&&(a=s.elements[0].expression.value),"ObjectExpression"===i?.type){const e=i.properties.find(e=>"KeyValueProperty"===e.type&&"Identifier"===e.key.type&&"keyPrefix"===e.key.value);"KeyValueProperty"===e?.type&&"StringLiteral"===e.value.type&&(o=e.value.value)}(a||o)&&this.scope.set(n,{defaultNs:a,keyPrefix:o})}handleUseTranslationDeclarator(e,r,n){let s;if("Identifier"===e.id.type&&(s=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(s=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){s="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){s=t.value.value;break}}if(!s)return;const i=r.arguments?.[n.nsArg]?.expression;let a;"StringLiteral"===i?.type?a=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(a=i.elements[0].expression.value);const o=r.arguments?.[n.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t.getObjectPropValue(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(s,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const r=e.id.value,n=t.arguments,s=n[1]?.expression,i=n[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===i?.type?i.value:void 0;(a||o)&&this.setVarInScope(r,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const r=this.getFunctionName(e.callee);if(!r)return;const n=this.getVarFromScope(r),s=this.config.extract.functions||["t","*.t"];let i=void 0!==n;if(!i)for(const e of s)if(e.startsWith("*.")){if(r.endsWith(e.substring(1))){i=!0;break}}else if(e===r){i=!0;break}if(!i||0===e.arguments.length)return;const{keysToProcess:a,isSelectorAPI:o}=this.handleCallExpressionArgument(e,0);if(0===a.length)return;let l=!1;const u=this.config.extract.pluralSeparator??"_";for(let e=0;e<a.length;e++)a[e].endsWith(`${u}ordinal`)&&(l=!0,a[e]=a[e].slice(0,-8));let p,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(p=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const f=c?t.getObjectPropValue(c,"defaultValue"):void 0,y="string"==typeof f?f:p;for(let e=0;e<a.length;e++){let r,s=a[e];if(c){const e=t.getObjectPropValue(c,"ns");"string"==typeof e&&(r=e)}const i=this.config.extract.nsSeparator??":";if(!r&&i&&s.includes(i)){const e=s.split(i);r=e.shift(),s=e.join(i)}!r&&n?.defaultNs&&(r=n.defaultNs),r||(r=this.config.extract.defaultNS);let u=s;if(n?.keyPrefix){const e=this.config.extract.keySeparator??".";u=`${n.keyPrefix}${e}${s}`}const p=e===a.length-1&&y||s;if(c){const e=t.getObjectProperty(c,"context"),n=[];if("StringLiteral"===e?.value?.type||"NumericLiteral"===e?.value.type||"BooleanLiteral"===e?.value.type){const t=`${e.value.value}`,s=this.config.extract.contextSeparator??"_";""!==t&&n.push({key:`${u}${s}${t}`,ns:r,defaultValue:p})}else if(e?.value){const t=this.resolvePossibleStringValues(e.value),s=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{n.push({key:`${u}${s}${e}`,ns:r,defaultValue:p})}),n.push({key:u,ns:r,defaultValue:p}))}const s=void 0!==t.getObjectPropValue(c,"count"),i=!0===t.getObjectPropValue(c,"ordinal");if(s||l){if(n.length>0)for(const{key:e,ns:t}of n)this.handlePluralKeys(e,t,c,i||l);else this.handlePluralKeys(u,r,c,i||l);continue}if(n.length>0){n.forEach(this.pluginContext.addKey);continue}!0===t.getObjectPropValue(c,"returnObjects")&&this.objectKeys.add(u)}o&&this.objectKeys.add(u),this.pluginContext.addKey({key:u,ns:r,defaultValue:p})}}handleCallExpressionArgument(e,t){const r=e.arguments[t].expression,n=[];let s=!1;if("ArrowFunctionExpression"===r.type){const e=this.extractKeyFromSelector(r);e&&(n.push(e),s=!0)}else if("ArrayExpression"===r.type)for(const e of r.elements)e?.expression&&n.push(...this.resolvePossibleStringValues(e.expression));else n.push(...this.resolvePossibleStringValues(r));return{keysToProcess:n.filter(e=>!!e),isSelectorAPI:s}}handlePluralKeys(e,r,n,s){try{const i=s?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t.getObjectPropValue(n,"defaultValue"),u=t.getObjectPropValue(n,`defaultValue${o}other`),p=t.getObjectPropValue(n,`defaultValue${o}ordinal${o}other`);for(const i of a){const a=s?`defaultValue${o}ordinal${o}${i}`:`defaultValue${o}${i}`,c=t.getObjectPropValue(n,a);let f;f="string"==typeof c?c:"one"===i&&"string"==typeof l?l:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof l?l:e:u;const y=s?`${e}${o}ordinal${o}${i}`:`${e}${o}${i}`;this.pluginContext.addKey({key:y,ns:r,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(s){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=t.getObjectPropValue(n,"defaultValue");this.pluginContext.addKey({key:e,ns:r,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,r){try{const n=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,s=this.config.extract.pluralSeparator??"_";for(const i of n)this.pluginContext.addKey({key:`${e}${s}${i}`,ns:r,defaultValue:t,hasCount:!0})}catch(n){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:r,defaultValue:t})}}handleJSXElement(t){const r=this.getElementName(t);if(r&&(this.config.extract.transComponents||["Trans"]).includes(r)){const r=e.extractFromTransComponent(t,this.config),n=[];if(r){if(r.keyExpression){const e=this.resolvePossibleStringValues(r.keyExpression);n.push(...e)}else n.push(r.serializedChildren);let e;const{contextExpression:s,optionsNode:i,defaultValue:a,hasCount:o,isOrdinal:l,serializedChildren:u}=r;if(r.ns){const{ns:t}=r;e=n.map(e=>({key:e,ns:t,defaultValue:a||u,hasCount:o,isOrdinal:l}))}else{e=n.map(e=>{const t=this.config.extract.nsSeparator??":";let r;if(t&&e.includes(t)){let n;[r,...n]=e.split(t),e=n.join(t)}return{key:e,ns:r,defaultValue:a||u,hasCount:o,isOrdinal:l}});const r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===r?.type&&"JSXExpressionContainer"===r.value?.type&&"Identifier"===r.value.expression.type){const t=r.value.expression.value,n=this.getVarFromScope(t);n?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=n.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),s&&o){const r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),n=!!r,a=this.resolvePossibleStringValues(s),o=this.config.extract.contextSeparator??"_";if(a.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,n,i));for(const t of a)for(const r of e){const e=`${r.key}${o}${t}`;this.generatePluralKeysForTrans(e,r.defaultValue,r.ns,n,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,n,i))}else if(s){const t=this.resolvePossibleStringValues(s),r=this.config.extract.contextSeparator??"_";if(t.length>0){for(const n of t)for(const{key:t,ns:s,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${r}${n}`,ns:s,defaultValue:i});"StringLiteral"!==s.type&&e.forEach(this.pluginContext.addKey)}}else if(o){const r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),n=!!r;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,n,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,r,n,s,i){try{const a=s?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t.getObjectPropValue(i,`defaultValue${l}other`),p=t.getObjectPropValue(i,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=s?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`,c=i?t.getObjectPropValue(i,o):void 0;let f;f="string"==typeof c?c:"one"===a&&"string"==typeof r?r:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof r?r:e:u;const y=s?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:y,ns:n,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:n,defaultValue:r})}}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 r=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&r.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&r.unshift(t.value),r.join(".")}}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 r=t;const n=[];for(;"MemberExpression"===r.type;){const e=r.property;if("Identifier"===e.type)n.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;n.unshift(e.expression.value)}r=r.object}if(n.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return n.join(t)}return null}resolvePossibleStringValues(e,t=!1){if("StringLiteral"===e.type)return e.value||t?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent,t),...this.resolvePossibleStringValues(e.alternate,t)]}return"Identifier"===e.type&&"undefined"===e.value?[]:"TemplateLiteral"===e.type?this.resolvePossibleStringValuesFromTemplateString(e):"NumericLiteral"===e.type||"BooleanLiteral"===e.type?[`${e.value}`]:[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...r]=e.quasis;return e.expressions.reduce((e,t,n)=>e.flatMap(e=>{const s=r[n]?.cooked??"";return this.resolvePossibleStringValues(t,!0).map(t=>`${e}${t}${s}`)}),[t.cooked??""])}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const r of t){if("string"==typeof r&&r===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof r&&r.name===e)return{name:r.name,nsArg:r.nsArg??0,keyPrefixArg:r.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let r=e;for(;"MemberExpression"===r.type;){if("Identifier"!==r.property.type)return null;t.unshift(r.property.value),r=r.object}if("ThisExpression"===r.type)t.unshift("this");else{if("Identifier"!==r.type)return null;t.unshift(r.value)}return t.join(".")}return null}};
@@ -1 +1 @@
1
- "use strict";exports.createPluginContext=function(t,e,n){return{addKey:e=>{const n=`${e.ns??"translation"}:${e.key}`;if(!t.has(n)){const o=e.defaultValue??e.key;t.set(n,{...e,defaultValue:o})}},config:e,logger:n,getVarFromScope:()=>{}}},exports.initializePlugins=async function(t){for(const e of t)await(e.setup?.())};
1
+ "use strict";exports.createPluginContext=function(e,t,n,o){return{addKey:t=>{const n=`${t.ns??"translation"}:${t.key}`;if(!e.has(n)){const o=t.defaultValue??t.key;e.set(n,{...t,defaultValue:o})}},config:Object.freeze({...n,plugins:[...t]}),logger:o,getVarFromScope:()=>{}}},exports.initializePlugins=async function(e){for(const t of e)await(t.setup?.())};
package/dist/esm/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as t}from"commander";import o from"chokidar";import{glob as e}from"glob";import n from"chalk";import{ensureConfig as i,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 g}from"./status.js";import{runLocizeSync as f,runLocizeDownload as u,runLocizeMigrate as h}from"./locize.js";const w=new t;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.6.0"),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.").option("--dry-run","Run the extractor without writing any files to disk.").action(async t=>{const a=await i(),c=async()=>{const o=await r(a,{isWatchMode:t.watch,isDryRun:t.dryRun});t.ci&&o&&(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(),t.watch){console.log("\nWatching for changes...");o.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),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(t,o)=>{let e=await a();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await c();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 i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await g(e,{detail:t,namespace:o.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 t=>{const n=await i(),a=()=>s(n);if(await a(),t.watch){console.log("\nWatching for changes...");o.watch(await e(n.types?.input||[]),{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=await i();await l(t)}),w.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await m(t)}),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.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async t=>{const i=async()=>{let t=await a();if(!t){console.log(n.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(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!")),t=o}await d(t)};if(await i(),t.watch){console.log("\nWatching for changes...");const t=await a();if(t?.extract?.input){o.watch(await e(t.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),i()})}}}),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 t=>{const o=await i();await f(o,t)}),w.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const o=await i();await u(o,t)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const o=await i();await h(o,t)}),w.parse(process.argv);
2
+ import{Command as t}from"commander";import o from"chokidar";import{glob as e}from"glob";import n from"chalk";import{ensureConfig as i,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 g}from"./status.js";import{runLocizeSync as f,runLocizeDownload as u,runLocizeMigrate as h}from"./locize.js";const w=new t;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.6.1"),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.").option("--dry-run","Run the extractor without writing any files to disk.").action(async t=>{const a=await i(),c=async()=>{const o=await r(a,{isWatchMode:t.watch,isDryRun:t.dryRun});t.ci&&o&&(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(),t.watch){console.log("\nWatching for changes...");o.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),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(t,o)=>{let e=await a();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await c();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 i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await g(e,{detail:t,namespace:o.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 t=>{const n=await i(),a=()=>s(n);if(await a(),t.watch){console.log("\nWatching for changes...");o.watch(await e(n.types?.input||[]),{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=await i();await l(t)}),w.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await m(t)}),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.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async t=>{const i=async()=>{let t=await a();if(!t){console.log(n.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(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!")),t=o}await d(t)};if(await i(),t.watch){console.log("\nWatching for changes...");const t=await a();if(t?.extract?.input){o.watch(await e(t.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),i()})}}}),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 t=>{const o=await i();await f(o,t)}),w.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const o=await i();await u(o,t)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const o=await i();await h(o,t)}),w.parse(process.argv);
@@ -1 +1 @@
1
- import t from"ora";import o from"chalk";import{parse as e}from"@swc/core";import{mkdir as a,writeFile as n,readFile as r}from"node:fs/promises";import{dirname as i}from"node:path";import{findKeys as s}from"./key-finder.js";import{getTranslations as c}from"./translation-manager.js";import{validateExtractorConfig as l,ExtractorError as f}from"../../utils/validation.js";import{createPluginContext as m}from"../plugin-manager.js";import{extractKeysFromComments as p}from"../parsers/comment-parser.js";import{ConsoleLogger as u}from"../../utils/logger.js";import{serializeTranslationFile as g}from"../../utils/file-utils.js";import{shouldShowFunnel as y,recordFunnelShown as d}from"../../utils/funnel-msg-tracker.js";async function w(e,{isWatchMode:r=!1,isDryRun:f=!1}={},m=new u){e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(t=>t!==e?.extract?.primaryLanguage),e.extract.functions||=["t","*.t"],e.extract.transComponents||=["Trans"],l(e);const p=t("Running i18next key extractor...\n").start();try{const{allKeys:t,objectKeys:r}=await s(e,m);p.text=`Found ${t.size} unique keys. Updating translation files...`;const l=await c(t,r,e);let u=!1;for(const t of l)if(t.updated&&(u=!0,!f)){const r=g(t.newTranslations,e.extract.outputFormat,e.extract.indentation);await a(i(t.path),{recursive:!0}),await n(t.path,r),m.info(o.green(`Updated: ${t.path}`))}if((e.plugins||[]).length>0){p.text="Running post-extraction plugins...";for(const t of e.plugins||[])await(t.afterSync?.(l,e))}return p.succeed(o.bold("Extraction complete!")),u&&await async function(){if(!await y("extract"))return;return console.log(o.yellow.bold("\nšŸ’” Tip: Tired of running the extractor manually?")),console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,'),console.log(" where keys are created and translated automatically as you code."),console.log(` Learn more: ${o.cyan("https://www.locize.com/blog/i18next-savemissing-ai-automation")}`),console.log(` Watch the video: ${o.cyan("https://youtu.be/joPsZghT3wM")}`),d("extract")}(),u}catch(t){throw p.fail(o.red("Extraction failed.")),t}}async function x(t,o,a,n,i=new u){try{let s=await r(t,"utf-8");for(const e of o.plugins||[])try{const o=await(e.onLoad?.(s,t));void 0!==o&&(s=o)}catch(t){i.warn(`Plugin ${e.name} onLoad failed:`,t)}const c=await e(s,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0}),l=m(a,o,i);l.getVarFromScope=n.getVarFromScope.bind(n),p(s,l,o,n.getVarFromScope.bind(n)),n.visit(c),(o.plugins||[]).length>0&&h(c,o.plugins||[],l,i)}catch(o){throw new f("Failed to process file",t,o)}}function h(t,o,e,a=new u){if(t&&"object"==typeof t){for(const n of o)try{n.onVisitNode?.(t,e)}catch(t){a.warn(`Plugin ${n.name} onVisitNode failed:`,t)}for(const n of Object.keys(t)){const r=t[n];if(Array.isArray(r))for(const t of r)t&&"object"==typeof t&&h(t,o,e,a);else r&&"object"==typeof r&&h(r,o,e,a)}}}async function j(t){t.extract.primaryLanguage||=t.locales[0]||"en",t.extract.secondaryLanguages||=t.locales.filter(o=>o!==t?.extract?.primaryLanguage),t.extract.functions||=["t","*.t"],t.extract.transComponents||=["Trans"];const{allKeys:o,objectKeys:e}=await s(t);return c(o,e,t)}export{j as extract,x as processFile,w as runExtractor};
1
+ import t from"ora";import a from"chalk";import{parse as o}from"@swc/core";import{mkdir as e,writeFile as r,readFile as n}from"node:fs/promises";import{dirname as i}from"node:path";import{findKeys as s}from"./key-finder.js";import{getTranslations as c}from"./translation-manager.js";import{validateExtractorConfig as l,ExtractorError as m}from"../../utils/validation.js";import{extractKeysFromComments as u}from"../parsers/comment-parser.js";import{ConsoleLogger as p}from"../../utils/logger.js";import{serializeTranslationFile as f}from"../../utils/file-utils.js";import{shouldShowFunnel as g,recordFunnelShown as d}from"../../utils/funnel-msg-tracker.js";async function y(o,{isWatchMode:n=!1,isDryRun:m=!1}={},u=new p){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(t=>t!==o?.extract?.primaryLanguage),o.extract.functions||=["t","*.t"],o.extract.transComponents||=["Trans"],l(o);const y=t("Running i18next key extractor...\n").start();try{const{allKeys:t,objectKeys:n}=await s(o,u);y.text=`Found ${t.size} unique keys. Updating translation files...`;const l=await c(t,n,o);let p=!1;for(const t of l)if(t.updated&&(p=!0,!m)){const n=f(t.newTranslations,o.extract.outputFormat,o.extract.indentation);await e(i(t.path),{recursive:!0}),await r(t.path,n),u.info(a.green(`Updated: ${t.path}`))}if((o.plugins||[]).length>0){y.text="Running post-extraction plugins...";for(const t of o.plugins||[])await(t.afterSync?.(l,o))}return y.succeed(a.bold("Extraction complete!")),p&&await async function(){if(!await g("extract"))return;return console.log(a.yellow.bold("\nšŸ’” Tip: Tired of running the extractor manually?")),console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,'),console.log(" where keys are created and translated automatically as you code."),console.log(` Learn more: ${a.cyan("https://www.locize.com/blog/i18next-savemissing-ai-automation")}`),console.log(` Watch the video: ${a.cyan("https://youtu.be/joPsZghT3wM")}`),d("extract")}(),p}catch(t){throw y.fail(a.red("Extraction failed.")),t}}async function w(t,a,e,r,i,s=new p){try{let c=await n(t,"utf-8");for(const o of a)try{const a=await(o.onLoad?.(c,t));void 0!==a&&(c=a)}catch(t){s.warn(`Plugin ${o.name} onLoad failed:`,t)}const l=await o(c,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0});r.getVarFromScope=e.getVarFromScope.bind(e),e.visit(l),u(c,r,i,e.getVarFromScope.bind(e))}catch(a){throw new m("Failed to process file",t,a)}}async function x(t){t.extract.primaryLanguage||=t.locales[0]||"en",t.extract.secondaryLanguages||=t.locales.filter(a=>a!==t?.extract?.primaryLanguage),t.extract.functions||=["t","*.t"],t.extract.transComponents||=["Trans"];const{allKeys:a,objectKeys:o}=await s(t);return c(a,o,t)}export{x as extract,w as processFile,y as runExtractor};
@@ -1 +1 @@
1
- import{glob as r}from"glob";import{processFile as o}from"./extractor.js";import{ConsoleLogger as t}from"../../utils/logger.js";import{createPluginContext as e,initializePlugins as n}from"../plugin-manager.js";import{ASTVisitors as a}from"../parsers/ast-visitors.js";async function i(i,s=new t){const c=await async function(o){const t=["node_modules/**"],e=Array.isArray(o.extract.ignore)?o.extract.ignore:o.extract.ignore?[o.extract.ignore]:[];return await r(o.extract.input,{ignore:[...t,...e],cwd:process.cwd()})}(i),p=new Map,g=e(p,i,s),m=new a(i,g,s);g.getVarFromScope=m.getVarFromScope.bind(m),await n(i.plugins||[]);for(const r of c)await o(r,i,p,m,s);for(const r of i.plugins||[])await(r.onEnd?.(p));return{allKeys:p,objectKeys:m.objectKeys}}export{i as findKeys};
1
+ import{glob as o}from"glob";import{processFile as r}from"./extractor.js";import{ConsoleLogger as t}from"../../utils/logger.js";import{createPluginContext as e,initializePlugins as n}from"../plugin-manager.js";import{ASTVisitors as i}from"../parsers/ast-visitors.js";async function a(a,s=new t){const{plugins:c,...f}=a,m=c||[],g=await async function(r){const t=["node_modules/**"],e=Array.isArray(r.extract.ignore)?r.extract.ignore:r.extract.ignore?[r.extract.ignore]:[];return await o(r.extract.input,{ignore:[...t,...e],cwd:process.cwd()})}(a),p=new Map,w=e(p,m,f,s),d={onBeforeVisitNode:o=>{for(const r of m)try{r.onVisitNode?.(o,w)}catch(o){s.warn(`Plugin ${r.name} onVisitNode failed:`,o)}}},l=new i(f,w,s,d);w.getVarFromScope=l.getVarFromScope.bind(l),await n(m);for(const o of g)await r(o,m,l,w,f,s);for(const o of m)await(o.onEnd?.(p));return{allKeys:p,objectKeys:l.objectKeys}}export{a as findKeys};
@@ -1 +1 @@
1
- import{extractFromTransComponent as e}from"./jsx-parser.js";import{getObjectPropValue as t,getObjectProperty as n}from"./ast-utils.js";class s{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&&"object"==typeof n&&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 s=n.callee;if("Identifier"===s.type){const t=this.getUseTranslationConfig(s.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===s.type&&"Identifier"===s.property.type&&"getFixedT"===s.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,n,s){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=n.arguments?.[s.nsArg]?.expression;let a;"StringLiteral"===i?.type?a=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(a=i.elements[0].expression.value);const o=n.arguments?.[s.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,s=t.arguments,r=s[1]?.expression,i=s[2]?.expression,a="StringLiteral"===r?.type?r.value:void 0,o="StringLiteral"===i?.type?i.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const s=this.getFunctionName(e.callee);if(!s)return;const r=this.getVarFromScope(s),i=this.config.extract.functions||["t","*.t"];let a=void 0!==r;if(!a)for(const e of i)if(e.startsWith("*.")){if(s.endsWith(e.substring(1))){a=!0;break}}else if(e===s){a=!0;break}if(!a||0===e.arguments.length)return;const{keysToProcess:o,isSelectorAPI:l}=this.handleCallExpressionArgument(e,0);if(0===o.length)return;let u=!1;const p=this.config.extract.pluralSeparator??"_";for(let e=0;e<o.length;e++)o[e].endsWith(`${p}ordinal`)&&(u=!0,o[e]=o[e].slice(0,-8));let f,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(f=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const g=c?t(c,"defaultValue"):void 0,y="string"==typeof g?g:f;for(let e=0;e<o.length;e++){let s,i=o[e];if(c){const e=t(c,"ns");"string"==typeof e&&(s=e)}const a=this.config.extract.nsSeparator??":";if(!s&&a&&i.includes(a)){const e=i.split(a);s=e.shift(),i=e.join(a)}!s&&r?.defaultNs&&(s=r.defaultNs),s||(s=this.config.extract.defaultNS);let p=i;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";p=`${r.keyPrefix}${e}${i}`}const f=e===o.length-1&&y||i;if(c){const e=n(c,"context"),r=[];if("ConditionalExpression"===e?.value?.type){const t=this.resolvePossibleStringValues(e.value),n=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{r.push({key:`${p}${n}${e}`,ns:s,defaultValue:f})}),r.push({key:p,ns:s,defaultValue:f}))}else if("StringLiteral"===e?.value?.type){const t=e.value.value,n=this.config.extract.contextSeparator??"_";r.push({key:`${p}${n}${t}`,ns:s,defaultValue:f})}const i=void 0!==t(c,"count"),a=!0===t(c,"ordinal");if(i||u){if(r.length>0)for(const{key:e,ns:t}of r)this.handlePluralKeys(e,t,c,a||u);else this.handlePluralKeys(p,s,c,a||u);continue}if(r.length>0){r.forEach(this.pluginContext.addKey);continue}!0===t(c,"returnObjects")&&this.objectKeys.add(p)}l&&this.objectKeys.add(p),this.pluginContext.addKey({key:p,ns:s,defaultValue:f})}}handleCallExpressionArgument(e,t){const n=e.arguments[t].expression,s=[];let r=!1;if("ArrowFunctionExpression"===n.type){const e=this.extractKeyFromSelector(n);e&&(s.push(e),r=!0)}else if("ArrayExpression"===n.type)for(const e of n.elements)e?.expression&&s.push(...this.resolvePossibleStringValues(e.expression));else s.push(...this.resolvePossibleStringValues(n));return{keysToProcess:s.filter(e=>!!e),isSelectorAPI:r}}handlePluralKeys(e,n,s,r){try{const i=r?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t(s,"defaultValue"),u=t(s,`defaultValue${o}other`),p=t(s,`defaultValue${o}ordinal${o}other`);for(const i of a){const a=t(s,r?`defaultValue${o}ordinal${o}${i}`:`defaultValue${o}${i}`);let f;f="string"==typeof a?a:"one"===i&&"string"==typeof l?l:r&&"string"==typeof p?p:r||"string"!=typeof u?"string"==typeof l?l:e:u;const c=r?`${e}${o}ordinal${o}${i}`:`${e}${o}${i}`;this.pluginContext.addKey({key:c,ns:n,defaultValue:f,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=t(s,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,n){try{const s=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,r=this.config.extract.pluralSeparator??"_";for(const i of s)this.pluginContext.addKey({key:`${e}${r}${i}`,ns:n,defaultValue:t,hasCount:!0})}catch(s){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),s=[];if(n){if(n.keyExpression){const e=this.resolvePossibleStringValues(n.keyExpression);s.push(...e)}else s.push(n.serializedChildren);let e;const{contextExpression:r,optionsNode:i,defaultValue:a,hasCount:o,isOrdinal:l,serializedChildren:u}=n;if(n.ns){const{ns:t}=n;e=s.map(e=>({key:e,ns:t,defaultValue:a||u,hasCount:o,isOrdinal:l}))}else{e=s.map(e=>{const t=this.config.extract.nsSeparator??":";let n;if(t&&e.includes(t)){let s;[n,...s]=e.split(t),e=s.join(t)}return{key:e,ns:n,defaultValue:a||u,hasCount:o,isOrdinal:l}});const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===n?.type&&"JSXExpressionContainer"===n.value?.type&&"Identifier"===n.value.expression.type){const t=n.value.expression.value,s=this.getVarFromScope(t);s?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=s.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),r&&o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),s=!!n,a=this.resolvePossibleStringValues(r),o=this.config.extract.contextSeparator??"_";if(a.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,s,i));for(const t of a)for(const n of e){const e=`${n.key}${o}${t}`;this.generatePluralKeysForTrans(e,n.defaultValue,n.ns,s,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,s,i))}else if(r){const t=this.resolvePossibleStringValues(r),n=this.config.extract.contextSeparator??"_";if(t.length>0){for(const s of t)for(const{key:t,ns:r,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${n}${s}`,ns:r,defaultValue:i});"StringLiteral"!==r.type&&e.forEach(this.pluginContext.addKey)}}else if(o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),s=!!n;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,s,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,n,s,r,i){try{const a=r?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t(i,`defaultValue${l}other`),p=t(i,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=i?t(i,r?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`):void 0;let f;f="string"==typeof o?o:"one"===a&&"string"==typeof n?n:r&&"string"==typeof p?p:r||"string"!=typeof u?"string"==typeof n?n:e:u;const c=r?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:c,ns:s,defaultValue:f,hasCount:!0,isOrdinal:r})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:s,defaultValue: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(".")}}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 s=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)s.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;s.unshift(e.expression.value)}n=n.object}if(s.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return s.join(t)}return null}resolvePossibleStringValues(e,t=!1){if("StringLiteral"===e.type)return e.value||t?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent,t),...this.resolvePossibleStringValues(e.alternate,t)]}return"Identifier"===e.type&&"undefined"===e.value?[]:"TemplateLiteral"===e.type?this.resolvePossibleStringValuesFromTemplateString(e):"NumericLiteral"===e.type||"BooleanLiteral"===e.type?[`${e.value}`]:[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...n]=e.quasis;return e.expressions.reduce((e,t,s)=>e.flatMap(e=>{const r=n[s]?.cooked??"";return this.resolvePossibleStringValues(t,!0).map(t=>`${e}${t}${r}`)}),[t.cooked??""])}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}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}}export{s as ASTVisitors};
1
+ import{extractFromTransComponent as e}from"./jsx-parser.js";import{getObjectPropValue as t,getObjectProperty as n}from"./ast-utils.js";class r{pluginContext;config;logger;scopeStack=[];hooks;objectKeys=new Set;scope=new Map;constructor(e,t,n,r){this.pluginContext=t,this.config=e,this.logger=n,this.hooks={onBeforeVisitNode:r?.onBeforeVisitNode,onAfterVisitNode:r?.onAfterVisitNode}}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),this.hooks.onBeforeVisitNode?.(e),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}this.hooks.onAfterVisitNode?.(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&&"object"==typeof n&&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)}const t=this.scope.get(e);if(t)return t}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 this.handleUseTranslationDeclarator(e,n,t),void this.handleUseTranslationForComments(e,n,t)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationForComments(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 s=t.arguments?.[n.nsArg]?.expression,i=t.arguments?.[n.keyPrefixArg]?.expression;let a,o;if("StringLiteral"===s?.type?a=s.value:"ArrayExpression"===s?.type&&"StringLiteral"===s.elements[0]?.expression.type&&(a=s.elements[0].expression.value),"ObjectExpression"===i?.type){const e=i.properties.find(e=>"KeyValueProperty"===e.type&&"Identifier"===e.key.type&&"keyPrefix"===e.key.value);"KeyValueProperty"===e?.type&&"StringLiteral"===e.value.type&&(o=e.value.value)}(a||o)&&this.scope.set(r,{defaultNs:a,keyPrefix:o})}handleUseTranslationDeclarator(e,n,r){let s;if("Identifier"===e.id.type&&(s=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(s=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){s="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){s=t.value.value;break}}if(!s)return;const i=n.arguments?.[r.nsArg]?.expression;let a;"StringLiteral"===i?.type?a=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(a=i.elements[0].expression.value);const o=n.arguments?.[r.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(s,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,r=t.arguments,s=r[1]?.expression,i=r[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===i?.type?i.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const r=this.getFunctionName(e.callee);if(!r)return;const s=this.getVarFromScope(r),i=this.config.extract.functions||["t","*.t"];let a=void 0!==s;if(!a)for(const e of i)if(e.startsWith("*.")){if(r.endsWith(e.substring(1))){a=!0;break}}else if(e===r){a=!0;break}if(!a||0===e.arguments.length)return;const{keysToProcess:o,isSelectorAPI:l}=this.handleCallExpressionArgument(e,0);if(0===o.length)return;let u=!1;const p=this.config.extract.pluralSeparator??"_";for(let e=0;e<o.length;e++)o[e].endsWith(`${p}ordinal`)&&(u=!0,o[e]=o[e].slice(0,-8));let f,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(f=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const y=c?t(c,"defaultValue"):void 0,d="string"==typeof y?y:f;for(let e=0;e<o.length;e++){let r,i=o[e];if(c){const e=t(c,"ns");"string"==typeof e&&(r=e)}const a=this.config.extract.nsSeparator??":";if(!r&&a&&i.includes(a)){const e=i.split(a);r=e.shift(),i=e.join(a)}!r&&s?.defaultNs&&(r=s.defaultNs),r||(r=this.config.extract.defaultNS);let p=i;if(s?.keyPrefix){const e=this.config.extract.keySeparator??".";p=`${s.keyPrefix}${e}${i}`}const f=e===o.length-1&&d||i;if(c){const e=n(c,"context"),s=[];if("StringLiteral"===e?.value?.type||"NumericLiteral"===e?.value.type||"BooleanLiteral"===e?.value.type){const t=`${e.value.value}`,n=this.config.extract.contextSeparator??"_";""!==t&&s.push({key:`${p}${n}${t}`,ns:r,defaultValue:f})}else if(e?.value){const t=this.resolvePossibleStringValues(e.value),n=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{s.push({key:`${p}${n}${e}`,ns:r,defaultValue:f})}),s.push({key:p,ns:r,defaultValue:f}))}const i=void 0!==t(c,"count"),a=!0===t(c,"ordinal");if(i||u){if(s.length>0)for(const{key:e,ns:t}of s)this.handlePluralKeys(e,t,c,a||u);else this.handlePluralKeys(p,r,c,a||u);continue}if(s.length>0){s.forEach(this.pluginContext.addKey);continue}!0===t(c,"returnObjects")&&this.objectKeys.add(p)}l&&this.objectKeys.add(p),this.pluginContext.addKey({key:p,ns:r,defaultValue:f})}}handleCallExpressionArgument(e,t){const n=e.arguments[t].expression,r=[];let s=!1;if("ArrowFunctionExpression"===n.type){const e=this.extractKeyFromSelector(n);e&&(r.push(e),s=!0)}else if("ArrayExpression"===n.type)for(const e of n.elements)e?.expression&&r.push(...this.resolvePossibleStringValues(e.expression));else r.push(...this.resolvePossibleStringValues(n));return{keysToProcess:r.filter(e=>!!e),isSelectorAPI:s}}handlePluralKeys(e,n,r,s){try{const i=s?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t(r,"defaultValue"),u=t(r,`defaultValue${o}other`),p=t(r,`defaultValue${o}ordinal${o}other`);for(const i of a){const a=t(r,s?`defaultValue${o}ordinal${o}${i}`:`defaultValue${o}${i}`);let f;f="string"==typeof a?a:"one"===i&&"string"==typeof l?l:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof l?l:e:u;const c=s?`${e}${o}ordinal${o}${i}`:`${e}${o}${i}`;this.pluginContext.addKey({key:c,ns:n,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(s){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=t(r,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,n){try{const r=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,s=this.config.extract.pluralSeparator??"_";for(const i of r)this.pluginContext.addKey({key:`${e}${s}${i}`,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),r=[];if(n){if(n.keyExpression){const e=this.resolvePossibleStringValues(n.keyExpression);r.push(...e)}else r.push(n.serializedChildren);let e;const{contextExpression:s,optionsNode:i,defaultValue:a,hasCount:o,isOrdinal:l,serializedChildren:u}=n;if(n.ns){const{ns:t}=n;e=r.map(e=>({key:e,ns:t,defaultValue:a||u,hasCount:o,isOrdinal:l}))}else{e=r.map(e=>{const t=this.config.extract.nsSeparator??":";let n;if(t&&e.includes(t)){let r;[n,...r]=e.split(t),e=r.join(t)}return{key:e,ns:n,defaultValue:a||u,hasCount:o,isOrdinal:l}});const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===n?.type&&"JSXExpressionContainer"===n.value?.type&&"Identifier"===n.value.expression.type){const t=n.value.expression.value,r=this.getVarFromScope(t);r?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=r.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),s&&o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!n,a=this.resolvePossibleStringValues(s),o=this.config.extract.contextSeparator??"_";if(a.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i));for(const t of a)for(const n of e){const e=`${n.key}${o}${t}`;this.generatePluralKeysForTrans(e,n.defaultValue,n.ns,r,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i))}else if(s){const t=this.resolvePossibleStringValues(s),n=this.config.extract.contextSeparator??"_";if(t.length>0){for(const r of t)for(const{key:t,ns:s,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${n}${r}`,ns:s,defaultValue:i});"StringLiteral"!==s.type&&e.forEach(this.pluginContext.addKey)}}else if(o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!n;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,n,r,s,i){try{const a=s?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t(i,`defaultValue${l}other`),p=t(i,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=i?t(i,s?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`):void 0;let f;f="string"==typeof o?o:"one"===a&&"string"==typeof n?n:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof n?n:e:u;const c=s?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:c,ns:r,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:r,defaultValue: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(".")}}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,t=!1){if("StringLiteral"===e.type)return e.value||t?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent,t),...this.resolvePossibleStringValues(e.alternate,t)]}return"Identifier"===e.type&&"undefined"===e.value?[]:"TemplateLiteral"===e.type?this.resolvePossibleStringValuesFromTemplateString(e):"NumericLiteral"===e.type||"BooleanLiteral"===e.type?[`${e.value}`]:[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...n]=e.quasis;return e.expressions.reduce((e,t,r)=>e.flatMap(e=>{const s=n[r]?.cooked??"";return this.resolvePossibleStringValues(t,!0).map(t=>`${e}${t}${s}`)}),[t.cooked??""])}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}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}}export{r as ASTVisitors};
@@ -1 +1 @@
1
- async function t(t){for(const e of t)await(e.setup?.())}function e(t,e,n){return{addKey:e=>{const n=`${e.ns??"translation"}:${e.key}`;if(!t.has(n)){const o=e.defaultValue??e.key;t.set(n,{...e,defaultValue:o})}},config:e,logger:n,getVarFromScope:()=>{}}}export{e as createPluginContext,t as initializePlugins};
1
+ async function e(e){for(const t of e)await(t.setup?.())}function t(e,t,n,o){return{addKey:t=>{const n=`${t.ns??"translation"}:${t.key}`;if(!e.has(n)){const o=t.defaultValue??t.key;e.set(n,{...t,defaultValue:o})}},config:Object.freeze({...n,plugins:[...t]}),logger:o,getVarFromScope:()=>{}}}export{t as createPluginContext,e as initializePlugins};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -21,7 +21,7 @@ const program = new Command()
21
21
  program
22
22
  .name('i18next-cli')
23
23
  .description('A unified, high-performance i18next CLI.')
24
- .version('1.6.0')
24
+ .version('1.6.1')
25
25
 
26
26
  program
27
27
  .command('extract')
@@ -3,11 +3,10 @@ import chalk from 'chalk'
3
3
  import { parse } from '@swc/core'
4
4
  import { mkdir, readFile, writeFile } from 'node:fs/promises'
5
5
  import { dirname } from 'node:path'
6
- import type { Logger, ExtractedKey, PluginContext, I18nextToolkitConfig } from '../../types'
6
+ import type { Logger, I18nextToolkitConfig, Plugin, PluginContext } from '../../types'
7
7
  import { findKeys } from './key-finder'
8
8
  import { getTranslations } from './translation-manager'
9
9
  import { validateExtractorConfig, ExtractorError } from '../../utils/validation'
10
- import { createPluginContext } from '../plugin-manager'
11
10
  import { extractKeysFromComments } from '../parsers/comment-parser'
12
11
  import { ASTVisitors } from '../parsers/ast-visitors'
13
12
  import { ConsoleLogger } from '../../utils/logger'
@@ -129,16 +128,17 @@ export async function runExtractor (
129
128
  */
130
129
  export async function processFile (
131
130
  file: string,
132
- config: I18nextToolkitConfig,
133
- allKeys: Map<string, ExtractedKey>,
131
+ plugins: Plugin[],
134
132
  astVisitors: ASTVisitors,
133
+ pluginContext: PluginContext,
134
+ config: Omit<I18nextToolkitConfig, 'plugins'>,
135
135
  logger: Logger = new ConsoleLogger()
136
136
  ): Promise<void> {
137
137
  try {
138
138
  let code = await readFile(file, 'utf-8')
139
139
 
140
140
  // Run onLoad hooks from plugins with error handling
141
- for (const plugin of (config.plugins || [])) {
141
+ for (const plugin of plugins) {
142
142
  try {
143
143
  const result = await plugin.onLoad?.(code, file)
144
144
  if (result !== undefined) {
@@ -157,60 +157,20 @@ export async function processFile (
157
157
  comments: true
158
158
  })
159
159
 
160
- // 1. Create the base context with config and logger.
161
- const pluginContext = createPluginContext(allKeys, config, logger)
162
-
163
- // 2. "Wire up" the visitor's scope method to the context.
160
+ // "Wire up" the visitor's scope method to the context.
164
161
  // This avoids a circular dependency while giving plugins access to the scope.
165
162
  pluginContext.getVarFromScope = astVisitors.getVarFromScope.bind(astVisitors)
166
163
 
167
- // Extract keys from comments with scope resolution
168
- extractKeysFromComments(code, pluginContext, config, astVisitors.getVarFromScope.bind(astVisitors))
169
-
164
+ // 3. FIRST: Visit the AST to build scope information
170
165
  astVisitors.visit(ast)
171
166
 
172
- // Run plugin visitors
173
- if ((config.plugins || []).length > 0) {
174
- traverseEveryNode(ast, (config.plugins || []), pluginContext, logger)
175
- }
167
+ // 4. THEN: Extract keys from comments with scope resolution (now scope info is available)
168
+ extractKeysFromComments(code, pluginContext, config, astVisitors.getVarFromScope.bind(astVisitors))
176
169
  } catch (error) {
177
170
  throw new ExtractorError('Failed to process file', file, error as Error)
178
171
  }
179
172
  }
180
173
 
181
- /**
182
- * Recursively traverses AST nodes and calls plugin onVisitNode hooks.
183
- *
184
- * @param node - The AST node to traverse
185
- * @param plugins - Array of plugins to run hooks for
186
- * @param pluginContext - Context object with helper methods for plugins
187
- *
188
- * @internal
189
- */
190
- function traverseEveryNode (node: any, plugins: any[], pluginContext: PluginContext, logger: Logger = new ConsoleLogger()): void {
191
- if (!node || typeof node !== 'object') return
192
-
193
- // Call plugins for this node
194
- for (const plugin of plugins) {
195
- try {
196
- plugin.onVisitNode?.(node, pluginContext)
197
- } catch (err) {
198
- logger.warn(`Plugin ${plugin.name} onVisitNode failed:`, err)
199
- }
200
- }
201
-
202
- for (const key of Object.keys(node)) {
203
- const child = node[key]
204
- if (Array.isArray(child)) {
205
- for (const c of child) {
206
- if (c && typeof c === 'object') traverseEveryNode(c, plugins, pluginContext, logger)
207
- }
208
- } else if (child && typeof child === 'object') {
209
- traverseEveryNode(child, plugins, pluginContext, logger)
210
- }
211
- }
212
- }
213
-
214
174
  /**
215
175
  * Simplified extraction function that returns translation results without file writing.
216
176
  * Used primarily for testing and programmatic access.
@@ -3,7 +3,7 @@ import type { ExtractedKey, Logger, I18nextToolkitConfig } from '../../types'
3
3
  import { processFile } from './extractor'
4
4
  import { ConsoleLogger } from '../../utils/logger'
5
5
  import { initializePlugins, createPluginContext } from '../plugin-manager'
6
- import { ASTVisitors } from '../parsers/ast-visitors'
6
+ import { type ASTVisitorHooks, ASTVisitors } from '../parsers/ast-visitors'
7
7
 
8
8
  /**
9
9
  * Main function for finding translation keys across all source files in a project.
@@ -37,27 +37,45 @@ export async function findKeys (
37
37
  config: I18nextToolkitConfig,
38
38
  logger: Logger = new ConsoleLogger()
39
39
  ): Promise<{ allKeys: Map<string, ExtractedKey>, objectKeys: Set<string> }> {
40
+ const { plugins: pluginsOrUndefined, ...otherConfig } = config
41
+ const plugins = pluginsOrUndefined || []
42
+
40
43
  const sourceFiles = await processSourceFiles(config)
41
44
  const allKeys = new Map<string, ExtractedKey>()
42
45
 
43
46
  // 1. Create the base context with config and logger.
44
- const pluginContext = createPluginContext(allKeys, config, logger)
47
+ const pluginContext = createPluginContext(allKeys, plugins, otherConfig, logger)
48
+
49
+ // 2. Create hooks for plugins to hook into AST
50
+ const hooks = {
51
+ onBeforeVisitNode: (node) => {
52
+ for (const plugin of plugins) {
53
+ try {
54
+ plugin.onVisitNode?.(node, pluginContext)
55
+ } catch (err) {
56
+ logger.warn(`Plugin ${plugin.name} onVisitNode failed:`, err)
57
+ }
58
+ }
59
+ },
60
+ } satisfies ASTVisitorHooks
45
61
 
46
- // 2. Create the visitor instance, passing it the context.
47
- const astVisitors = new ASTVisitors(config, pluginContext, logger)
62
+ // 3. Create the visitor instance, passing it the context.
63
+ const astVisitors = new ASTVisitors(otherConfig, pluginContext, logger, hooks)
48
64
 
49
- // 3. "Wire up" the visitor's scope method to the context.
65
+ // 4. "Wire up" the visitor's scope method to the context.
50
66
  // This avoids a circular dependency while giving plugins access to the scope.
51
67
  pluginContext.getVarFromScope = astVisitors.getVarFromScope.bind(astVisitors)
52
68
 
53
- await initializePlugins(config.plugins || [])
69
+ // 5. Initialize plugins
70
+ await initializePlugins(plugins)
54
71
 
72
+ // 6. Process each file
55
73
  for (const file of sourceFiles) {
56
- await processFile(file, config, allKeys, astVisitors, logger)
74
+ await processFile(file, plugins, astVisitors, pluginContext, otherConfig, logger)
57
75
  }
58
76
 
59
- // Run onEnd hooks
60
- for (const plugin of (config.plugins || [])) {
77
+ // 7. Run onEnd hooks
78
+ for (const plugin of plugins) {
61
79
  await plugin.onEnd?.(allKeys)
62
80
  }
63
81
 
@@ -9,6 +9,11 @@ interface UseTranslationHookConfig {
9
9
  keyPrefixArg: number;
10
10
  }
11
11
 
12
+ export interface ASTVisitorHooks {
13
+ onBeforeVisitNode?: (node: Node) => void
14
+ onAfterVisitNode?: (node: Node) => void
15
+ }
16
+
12
17
  /**
13
18
  * AST visitor class that traverses JavaScript/TypeScript syntax trees to extract translation keys.
14
19
  *
@@ -33,12 +38,15 @@ interface UseTranslationHookConfig {
33
38
  */
34
39
  export class ASTVisitors {
35
40
  private readonly pluginContext: PluginContext
36
- private readonly config: I18nextToolkitConfig
41
+ private readonly config: Omit<I18nextToolkitConfig, 'plugins'>
37
42
  private readonly logger: Logger
38
43
  private scopeStack: Array<Map<string, ScopeInfo>> = []
44
+ private hooks: ASTVisitorHooks
39
45
 
40
46
  public objectKeys = new Set<string>()
41
47
 
48
+ private scope: Map<string, { defaultNs?: string; keyPrefix?: string }> = new Map()
49
+
42
50
  /**
43
51
  * Creates a new AST visitor instance.
44
52
  *
@@ -47,13 +55,18 @@ export class ASTVisitors {
47
55
  * @param logger - Logger for warnings and debug information
48
56
  */
49
57
  constructor (
50
- config: I18nextToolkitConfig,
58
+ config: Omit<I18nextToolkitConfig, 'plugins'>,
51
59
  pluginContext: PluginContext,
52
- logger: Logger
60
+ logger: Logger,
61
+ hooks?: ASTVisitorHooks
53
62
  ) {
54
63
  this.pluginContext = pluginContext
55
64
  this.config = config
56
65
  this.logger = logger
66
+ this.hooks = {
67
+ onBeforeVisitNode: hooks?.onBeforeVisitNode,
68
+ onAfterVisitNode: hooks?.onAfterVisitNode
69
+ }
57
70
  }
58
71
 
59
72
  /**
@@ -91,6 +104,8 @@ export class ASTVisitors {
91
104
  isNewScope = true
92
105
  }
93
106
 
107
+ this.hooks.onBeforeVisitNode?.(node)
108
+
94
109
  // --- VISIT LOGIC ---
95
110
  // Handle specific node types
96
111
  switch (node.type) {
@@ -104,6 +119,9 @@ export class ASTVisitors {
104
119
  this.handleJSXElement(node)
105
120
  break
106
121
  }
122
+
123
+ this.hooks.onAfterVisitNode?.(node)
124
+
107
125
  // --- END VISIT LOGIC ---
108
126
 
109
127
  // --- RECURSION ---
@@ -180,11 +198,20 @@ export class ASTVisitors {
180
198
  * @private
181
199
  */
182
200
  public getVarFromScope (name: string): ScopeInfo | undefined {
201
+ // First check the proper scope stack (this is the primary source of truth)
183
202
  for (let i = this.scopeStack.length - 1; i >= 0; i--) {
184
203
  if (this.scopeStack[i].has(name)) {
185
- return this.scopeStack[i].get(name)
204
+ const scopeInfo = this.scopeStack[i].get(name)
205
+ return scopeInfo
186
206
  }
187
207
  }
208
+
209
+ // Then check the legacy scope tracking for useTranslation calls (for comment parsing)
210
+ const legacyScope = this.scope.get(name)
211
+ if (legacyScope) {
212
+ return legacyScope
213
+ }
214
+
188
215
  return undefined
189
216
  }
190
217
 
@@ -222,6 +249,9 @@ export class ASTVisitors {
222
249
  const hookConfig = this.getUseTranslationConfig(callee.value)
223
250
  if (hookConfig) {
224
251
  this.handleUseTranslationDeclarator(node, callExpr, hookConfig)
252
+
253
+ // ALSO store in the legacy scope for comment parsing compatibility
254
+ this.handleUseTranslationForComments(node, callExpr, hookConfig)
225
255
  return
226
256
  }
227
257
  }
@@ -236,6 +266,84 @@ export class ASTVisitors {
236
266
  }
237
267
  }
238
268
 
269
+ /**
270
+ * Handles useTranslation calls for comment scope resolution.
271
+ * This is a separate method to store scope info in the legacy scope map
272
+ * that the comment parser can access.
273
+ *
274
+ * @param node - Variable declarator with useTranslation call
275
+ * @param callExpr - The CallExpression node representing the useTranslation invocation
276
+ * @param hookConfig - Configuration describing argument positions for namespace and keyPrefix
277
+ *
278
+ * @private
279
+ */
280
+ private handleUseTranslationForComments (node: VariableDeclarator, callExpr: CallExpression, hookConfig: UseTranslationHookConfig): void {
281
+ let variableName: string | undefined
282
+
283
+ // Handle simple assignment: let t = useTranslation()
284
+ if (node.id.type === 'Identifier') {
285
+ variableName = node.id.value
286
+ }
287
+
288
+ // Handle array destructuring: const [t, i18n] = useTranslation()
289
+ if (node.id.type === 'ArrayPattern') {
290
+ const firstElement = node.id.elements[0]
291
+ if (firstElement?.type === 'Identifier') {
292
+ variableName = firstElement.value
293
+ }
294
+ }
295
+
296
+ // Handle object destructuring: const { t } or { t: t1 } = useTranslation()
297
+ if (node.id.type === 'ObjectPattern') {
298
+ for (const prop of node.id.properties) {
299
+ if (prop.type === 'AssignmentPatternProperty' && prop.key.type === 'Identifier' && prop.key.value === 't') {
300
+ // This handles { t = defaultT }
301
+ variableName = 't'
302
+ break
303
+ }
304
+ if (prop.type === 'KeyValuePatternProperty' && prop.key.type === 'Identifier' && prop.key.value === 't' && prop.value.type === 'Identifier') {
305
+ // This handles { t: myT }
306
+ variableName = prop.value.value
307
+ break
308
+ }
309
+ }
310
+ }
311
+
312
+ // If we couldn't find a `t` function being declared, exit
313
+ if (!variableName) return
314
+
315
+ // Extract namespace from useTranslation arguments
316
+ const nsArg = callExpr.arguments?.[hookConfig.nsArg]?.expression
317
+ const optionsArg = callExpr.arguments?.[hookConfig.keyPrefixArg]?.expression
318
+
319
+ let defaultNs: string | undefined
320
+ let keyPrefix: string | undefined
321
+
322
+ // Parse namespace argument
323
+ if (nsArg?.type === 'StringLiteral') {
324
+ defaultNs = nsArg.value
325
+ } else if (nsArg?.type === 'ArrayExpression' && nsArg.elements[0]?.expression.type === 'StringLiteral') {
326
+ defaultNs = nsArg.elements[0].expression.value
327
+ }
328
+
329
+ // Parse keyPrefix from options object
330
+ if (optionsArg?.type === 'ObjectExpression') {
331
+ const keyPrefixProp = optionsArg.properties.find(
332
+ prop => prop.type === 'KeyValueProperty' &&
333
+ prop.key.type === 'Identifier' &&
334
+ prop.key.value === 'keyPrefix'
335
+ )
336
+ if (keyPrefixProp?.type === 'KeyValueProperty' && keyPrefixProp.value.type === 'StringLiteral') {
337
+ keyPrefix = keyPrefixProp.value.value
338
+ }
339
+ }
340
+
341
+ // Store in the legacy scope map for comment parsing
342
+ if (defaultNs || keyPrefix) {
343
+ this.scope.set(variableName, { defaultNs, keyPrefix })
344
+ }
345
+ }
346
+
239
347
  /**
240
348
  * Processes useTranslation hook declarations to extract scope information.
241
349
  *
@@ -456,7 +564,16 @@ export class ASTVisitors {
456
564
  const keysWithContext: ExtractedKey[] = []
457
565
 
458
566
  // 1. Handle Context
459
- if (contextProp?.value?.type === 'ConditionalExpression') {
567
+ if (contextProp?.value?.type === 'StringLiteral' || contextProp?.value.type === 'NumericLiteral' || contextProp?.value.type === 'BooleanLiteral') {
568
+ // If the context is static, we don't need to add the base key
569
+ const contextValue = `${contextProp.value.value}`
570
+
571
+ const contextSeparator = this.config.extract.contextSeparator ?? '_'
572
+ // Ignore context: ''
573
+ if (contextValue !== '') {
574
+ keysWithContext.push({ key: `${finalKey}${contextSeparator}${contextValue}`, ns, defaultValue: dv })
575
+ }
576
+ } else if (contextProp?.value) {
460
577
  const contextValues = this.resolvePossibleStringValues(contextProp.value)
461
578
  const contextSeparator = this.config.extract.contextSeparator ?? '_'
462
579
 
@@ -467,11 +584,6 @@ export class ASTVisitors {
467
584
  // For dynamic context, also add the base key as a fallback
468
585
  keysWithContext.push({ key: finalKey, ns, defaultValue: dv })
469
586
  }
470
- } else if (contextProp?.value?.type === 'StringLiteral') {
471
- const contextValue = contextProp.value.value
472
-
473
- const contextSeparator = this.config.extract.contextSeparator ?? '_'
474
- keysWithContext.push({ key: `${finalKey}${contextSeparator}${contextValue}`, ns, defaultValue: dv })
475
587
  }
476
588
 
477
589
  // 2. Handle Plurals
@@ -55,7 +55,7 @@ export function extractKeysFromComments (
55
55
  key = parts.join(nsSeparator)
56
56
  }
57
57
 
58
- // 3. NEW: If no explicit namespace found, try to resolve from scope
58
+ // 3. If no explicit namespace found, try to resolve from scope
59
59
  // This allows commented t() calls to inherit namespace from useTranslation scope
60
60
  if (!ns && scopeResolver) {
61
61
  const scopeInfo = scopeResolver('t')
@@ -1,4 +1,4 @@
1
- import type { ExtractedKey, PluginContext, I18nextToolkitConfig, Logger } from '../types'
1
+ import type { ExtractedKey, PluginContext, I18nextToolkitConfig, Logger, Plugin } from '../types'
2
2
 
3
3
  /**
4
4
  * Initializes an array of plugins by calling their setup hooks.
@@ -39,7 +39,12 @@ export async function initializePlugins (plugins: any[]): Promise<void> {
39
39
  * })
40
40
  * ```
41
41
  */
42
- export function createPluginContext (allKeys: Map<string, ExtractedKey>, config: I18nextToolkitConfig, logger: Logger): PluginContext {
42
+ export function createPluginContext (allKeys: Map<string, ExtractedKey>, plugins: Plugin[], config: Omit<I18nextToolkitConfig, 'plugins'>, logger: Logger): PluginContext {
43
+ const pluginContextConfig = Object.freeze({
44
+ ...config,
45
+ plugins: [...plugins],
46
+ })
47
+
43
48
  return {
44
49
  addKey: (keyInfo: ExtractedKey) => {
45
50
  // Use namespace in the unique map key to avoid collisions across namespaces
@@ -50,7 +55,7 @@ export function createPluginContext (allKeys: Map<string, ExtractedKey>, config:
50
55
  allKeys.set(uniqueKey, { ...keyInfo, defaultValue })
51
56
  }
52
57
  },
53
- config,
58
+ config: pluginContextConfig,
54
59
  logger,
55
60
  // This will be attached later, so we provide a placeholder
56
61
  getVarFromScope: () => undefined,
@@ -1,4 +1,4 @@
1
- import type { Logger, ExtractedKey, I18nextToolkitConfig } from '../../types';
1
+ import type { Logger, I18nextToolkitConfig, Plugin, PluginContext } from '../../types';
2
2
  import { ASTVisitors } from '../parsers/ast-visitors';
3
3
  /**
4
4
  * Main extractor function that runs the complete key extraction and file generation process.
@@ -50,7 +50,7 @@ export declare function runExtractor(config: I18nextToolkitConfig, { isWatchMode
50
50
  *
51
51
  * @internal
52
52
  */
53
- export declare function processFile(file: string, config: I18nextToolkitConfig, allKeys: Map<string, ExtractedKey>, astVisitors: ASTVisitors, logger?: Logger): Promise<void>;
53
+ export declare function processFile(file: string, plugins: Plugin[], astVisitors: ASTVisitors, pluginContext: PluginContext, config: Omit<I18nextToolkitConfig, 'plugins'>, logger?: Logger): Promise<void>;
54
54
  /**
55
55
  * Simplified extraction function that returns translation results without file writing.
56
56
  * Used primarily for testing and programmatic access.
@@ -1 +1 @@
1
- {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/extractor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAiB,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAM5F,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAKrD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,EACE,WAAmB,EACnB,QAAgB,EACjB,GAAE;IACD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACf,EACN,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,OAAO,CAAC,CAuDlB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,oBAAoB,EAC5B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAClC,WAAW,EAAE,WAAW,EACxB,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CA2Cf;AAmCD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,sDAO1D"}
1
+ {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/extractor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAKtF,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAKrD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,EACE,WAAmB,EACnB,QAAgB,EACjB,GAAE;IACD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACf,EACN,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,OAAO,CAAC,CAuDlB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CAoCf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,sDAO1D"}
@@ -1 +1 @@
1
- {"version":3,"file":"key-finder.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/key-finder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAM7E;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,oBAAoB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC;IAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CA0B1E"}
1
+ {"version":3,"file":"key-finder.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/key-finder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAM7E;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,oBAAoB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC;IAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CA4C1E"}
@@ -1,5 +1,9 @@
1
- import type { Module } from '@swc/core';
1
+ import type { Module, Node } from '@swc/core';
2
2
  import type { PluginContext, I18nextToolkitConfig, Logger, ScopeInfo } from '../../types';
3
+ export interface ASTVisitorHooks {
4
+ onBeforeVisitNode?: (node: Node) => void;
5
+ onAfterVisitNode?: (node: Node) => void;
6
+ }
3
7
  /**
4
8
  * AST visitor class that traverses JavaScript/TypeScript syntax trees to extract translation keys.
5
9
  *
@@ -27,7 +31,9 @@ export declare class ASTVisitors {
27
31
  private readonly config;
28
32
  private readonly logger;
29
33
  private scopeStack;
34
+ private hooks;
30
35
  objectKeys: Set<string>;
36
+ private scope;
31
37
  /**
32
38
  * Creates a new AST visitor instance.
33
39
  *
@@ -35,7 +41,7 @@ export declare class ASTVisitors {
35
41
  * @param pluginContext - Context for adding discovered translation keys
36
42
  * @param logger - Logger for warnings and debug information
37
43
  */
38
- constructor(config: I18nextToolkitConfig, pluginContext: PluginContext, logger: Logger);
44
+ constructor(config: Omit<I18nextToolkitConfig, 'plugins'>, pluginContext: PluginContext, logger: Logger, hooks?: ASTVisitorHooks);
39
45
  /**
40
46
  * Main entry point for AST traversal.
41
47
  * Creates a root scope and begins the recursive walk through the syntax tree.
@@ -105,6 +111,18 @@ export declare class ASTVisitors {
105
111
  * @private
106
112
  */
107
113
  private handleVariableDeclarator;
114
+ /**
115
+ * Handles useTranslation calls for comment scope resolution.
116
+ * This is a separate method to store scope info in the legacy scope map
117
+ * that the comment parser can access.
118
+ *
119
+ * @param node - Variable declarator with useTranslation call
120
+ * @param callExpr - The CallExpression node representing the useTranslation invocation
121
+ * @param hookConfig - Configuration describing argument positions for namespace and keyPrefix
122
+ *
123
+ * @private
124
+ */
125
+ private handleUseTranslationForComments;
108
126
  /**
109
127
  * Processes useTranslation hook declarations to extract scope information.
110
128
  *
@@ -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,EAAgI,MAAM,WAAW,CAAA;AACrK,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,aAAa,CAAA;AAUvG;;;;;;;;;;;;;;;;;;;;;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;IAsDZ;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;;;OAQG;IACI,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAS5D;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,wBAAwB;IAmChC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,8BAA8B;IAwDtC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IAgK5B;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,gBAAgB;IA4DxB;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAkB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IA4IxB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,0BAA0B;IA6DlC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,2BAA2B;IA4BnC;;;;;;;OAOG;IACH,OAAO,CAAC,6CAA6C;IAyBrD;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAA0H,MAAM,WAAW,CAAA;AACrK,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,aAAa,CAAA;AAUvG,MAAM,WAAW,eAAe;IAC9B,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IACxC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;CACxC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,KAAK,CAAiB;IAEvB,UAAU,cAAoB;IAErC,OAAO,CAAC,KAAK,CAAqE;IAElF;;;;;;OAMG;gBAED,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,eAAe;IAWzB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IA2DZ;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;;;OAQG;IACI,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkB5D;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,wBAAwB;IAsChC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,+BAA+B;IAmEvC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,8BAA8B;IAwDtC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IAoK5B;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,gBAAgB;IA4DxB;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAkB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IA4IxB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,0BAA0B;IA6DlC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,2BAA2B;IA4BnC;;;;;;;OAOG;IACH,OAAO,CAAC,6CAA6C;IAyBrD;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
@@ -1,4 +1,4 @@
1
- import type { ExtractedKey, PluginContext, I18nextToolkitConfig, Logger } from '../types';
1
+ import type { ExtractedKey, PluginContext, I18nextToolkitConfig, Logger, Plugin } from '../types';
2
2
  /**
3
3
  * Initializes an array of plugins by calling their setup hooks.
4
4
  * This function should be called before starting the extraction process.
@@ -33,5 +33,5 @@ export declare function initializePlugins(plugins: any[]): Promise<void>;
33
33
  * })
34
34
  * ```
35
35
  */
36
- export declare function createPluginContext(allKeys: Map<string, ExtractedKey>, config: I18nextToolkitConfig, logger: Logger): PluginContext;
36
+ export declare function createPluginContext(allKeys: Map<string, ExtractedKey>, plugins: Plugin[], config: Omit<I18nextToolkitConfig, 'plugins'>, logger: Logger): PluginContext;
37
37
  //# sourceMappingURL=plugin-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-manager.d.ts","sourceRoot":"","sources":["../../src/extractor/plugin-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAEzF;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CAAE,OAAO,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAItE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAgBpI"}
1
+ {"version":3,"file":"plugin-manager.d.ts","sourceRoot":"","sources":["../../src/extractor/plugin-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAEjG;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CAAE,OAAO,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAItE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAqBxK"}