i18next-cli 0.9.7 → 0.9.9

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/README.md +12 -0
  3. package/dist/cjs/cli.js +1 -1
  4. package/dist/cjs/config.js +1 -1
  5. package/dist/cjs/extractor/core/extractor.js +1 -1
  6. package/dist/cjs/extractor/core/key-finder.js +1 -1
  7. package/dist/cjs/extractor/core/translation-manager.js +1 -1
  8. package/dist/cjs/extractor/parsers/ast-visitors.js +1 -1
  9. package/dist/cjs/extractor/parsers/jsx-parser.js +1 -1
  10. package/dist/cjs/status.js +1 -1
  11. package/dist/esm/cli.js +1 -1
  12. package/dist/esm/config.js +1 -1
  13. package/dist/esm/extractor/core/extractor.js +1 -1
  14. package/dist/esm/extractor/core/key-finder.js +1 -1
  15. package/dist/esm/extractor/core/translation-manager.js +1 -1
  16. package/dist/esm/extractor/parsers/ast-visitors.js +1 -1
  17. package/dist/esm/extractor/parsers/jsx-parser.js +1 -1
  18. package/dist/esm/status.js +1 -1
  19. package/package.json +1 -1
  20. package/src/cli.ts +1 -1
  21. package/src/config.ts +10 -9
  22. package/src/extractor/core/extractor.ts +12 -18
  23. package/src/extractor/core/key-finder.ts +8 -4
  24. package/src/extractor/core/translation-manager.ts +7 -1
  25. package/src/extractor/parsers/ast-visitors.ts +145 -49
  26. package/src/extractor/parsers/jsx-parser.ts +11 -1
  27. package/src/status.ts +1 -1
  28. package/src/types.ts +6 -3
  29. package/types/config.d.ts +3 -3
  30. package/types/config.d.ts.map +1 -1
  31. package/types/extractor/core/extractor.d.ts +2 -1
  32. package/types/extractor/core/extractor.d.ts.map +1 -1
  33. package/types/extractor/core/key-finder.d.ts +4 -1
  34. package/types/extractor/core/key-finder.d.ts.map +1 -1
  35. package/types/extractor/core/translation-manager.d.ts +1 -1
  36. package/types/extractor/core/translation-manager.d.ts.map +1 -1
  37. package/types/extractor/parsers/ast-visitors.d.ts +34 -15
  38. package/types/extractor/parsers/ast-visitors.d.ts.map +1 -1
  39. package/types/extractor/parsers/jsx-parser.d.ts.map +1 -1
  40. package/types/types.d.ts +5 -3
  41. package/types/types.d.ts.map +1 -1
package/CHANGELOG.md CHANGED
@@ -5,10 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [1.0.0](https://github.com/i18next/i18next-cli/compare/v0.9.7...v1.0.0) - 2025-xx-yy
8
+ ## [1.0.0](https://github.com/i18next/i18next-cli/compare/v0.9.9...v1.0.0) - 2025-xx-yy
9
9
 
10
10
  - not yet released
11
11
 
12
+ ## [0.9.9] - 2025-09-25
13
+
14
+ - **Extractor:** Now supports static and dynamic (ternary) `context` options in both `t()` and `<Trans>`.
15
+
16
+ ## [0.9.8] - 2025-09-25
17
+
18
+ - support t returnObjects
19
+
12
20
  ## [0.9.7] - 2025-09-25
13
21
 
14
22
  - support t key fallbacks
package/README.md CHANGED
@@ -478,17 +478,29 @@ t('key', { ns: 'namespace' })
478
478
  // With interpolation
479
479
  t('key', { name: 'John' })
480
480
 
481
+ // With plurals and context
482
+ t('key', { count: 1 });
483
+ t('keyWithContext', { context: 'male' });
484
+ t('keyWithDynContext', { context: isMale ? 'male' : 'female' });
485
+
481
486
  // With key fallbacks
482
487
  t(['key.primary', 'key.fallback']);
483
488
  t(['key.primary', 'key.fallback'], 'The fallback value');
484
489
  t(['key.primary', 'key.fallback'], { defaultValue: 'The fallback value' });
490
+
491
+ // With structured content (returnObjects)
492
+ t('countries', { returnObjects: true });
485
493
  ```
486
494
 
495
+ The extractor correctly handles pluralization (`count`) and context options, generating all necessary suffixed keys (e.g., `key_one`, `key_other`, `keyWithContext_male`). It can even statically analyze ternary expressions in the `context` option to extract all possible variations.
496
+
487
497
  ### React Components
488
498
  ```jsx
489
499
  // Trans component
490
500
  <Trans i18nKey="welcome">Welcome {{name}}</Trans>
491
501
  <Trans ns="common">user.greeting</Trans>
502
+ <Trans count={num}>You have {{num}} message</Trans>
503
+ <Trans context={isMale ? 'male' : 'female'}>A friend</Trans>
492
504
 
493
505
  // useTranslation hook
494
506
  const { t } = useTranslation('namespace');
package/dist/cjs/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:fs/promises"),require("node:path");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.7"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a);e.ci&&t&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let o=await i.loadConfig();if(!o){console.log(n.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),o=e}await g.runStatus(o,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const n=await i.ensureConfig(),a=()=>c.runTypesGenerator(n);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(n.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await l.runMigrator()}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let e=await i.loadConfig();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await d.runLinter(e)}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
2
+ "use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:fs/promises"),require("node:path");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.9"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a);e.ci&&t&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let o=await i.loadConfig();if(!o){console.log(n.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),o=e}await g.runStatus(o,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const n=await i.ensureConfig(),a=()=>c.runTypesGenerator(n);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(n.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await l.runMigrator()}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let e=await i.loadConfig();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await d.runLinter(e)}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
@@ -1 +1 @@
1
- "use strict";var e=require("node:path"),r=require("node:url"),n=require("node:fs/promises"),o=require("jiti"),t=require("inquirer"),i=require("chalk"),a=require("./init.js"),c="undefined"!=typeof document?document.currentScript:null;const s=["i18next.config.ts","i18next.config.js","i18next.config.mjs","i18next.config.cjs"];async function u(){const t=await async function(){for(const r of s){const o=e.resolve(process.cwd(),r);try{return await n.access(o),o}catch{}}return null}();if(!t)return null;try{let e;if(t.endsWith(".ts")){const r=o.createJiti("undefined"==typeof document?require("url").pathToFileURL(__filename).href:c&&"SCRIPT"===c.tagName.toUpperCase()&&c.src||new URL("config.js",document.baseURI).href),n=await r.import(t,{default:!0});e=n}else{const n=r.pathToFileURL(t).href,o=await import(`${n}?t=${Date.now()}`);e=o.default}return e?(e.extract||={},e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(r=>r!==e.extract.primaryLanguage),e):(console.error(`Error: No default export found in ${t}`),null)}catch(e){return console.error(`Error loading configuration from ${t}`),console.error(e),null}}exports.defineConfig=function(e){return e},exports.ensureConfig=async function(){let e=await u();if(e)return e;const{shouldInit:r}=await t.prompt([{type:"confirm",name:"shouldInit",message:i.yellow("Configuration file not found. Would you like to create one now?"),default:!0}]);if(r){if(await a.runInit(),console.log(i.green("Configuration created. Resuming command...")),e=await u(),e)return e;console.error(i.red("Error: Failed to load configuration after creation. Please try running the command again.")),process.exit(1)}else console.log("Operation cancelled. Please create a configuration file to proceed."),process.exit(0)},exports.loadConfig=u;
1
+ "use strict";var e=require("node:path"),r=require("node:url"),n=require("node:fs/promises"),t=require("jiti"),o=require("inquirer"),i=require("chalk"),a=require("./init.js"),c=require("./utils/logger.js"),u="undefined"!=typeof document?document.currentScript:null;const s=["i18next.config.ts","i18next.config.js","i18next.config.mjs","i18next.config.cjs"];async function l(o=new c.ConsoleLogger){const i=await async function(){for(const r of s){const t=e.resolve(process.cwd(),r);try{return await n.access(t),t}catch{}}return null}();if(!i)return null;try{let e;if(i.endsWith(".ts")){const r=t.createJiti("undefined"==typeof document?require("url").pathToFileURL(__filename).href:u&&"SCRIPT"===u.tagName.toUpperCase()&&u.src||new URL("config.js",document.baseURI).href),n=await r.import(i,{default:!0});e=n}else{const n=r.pathToFileURL(i).href,t=await import(`${n}?t=${Date.now()}`);e=t.default}return e?(e.extract||={},e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(r=>r!==e.extract.primaryLanguage),e):(o.error(`Error: No default export found in ${i}`),null)}catch(e){return o.error(`Error loading configuration from ${i}`),o.error(e),null}}exports.defineConfig=function(e){return e},exports.ensureConfig=async function(e=new c.ConsoleLogger){let r=await l();if(r)return r;const{shouldInit:n}=await o.prompt([{type:"confirm",name:"shouldInit",message:i.yellow("Configuration file not found. Would you like to create one now?"),default:!0}]);if(n){if(await a.runInit(),e.info(i.green("Configuration created. Resuming command...")),r=await l(),r)return r;e.error(i.red("Error: Failed to load configuration after creation. Please try running the command again.")),process.exit(1)}else e.info("Operation cancelled. Please create a configuration file to proceed."),process.exit(0)},exports.loadConfig=l;
@@ -1 +1 @@
1
- "use strict";var t=require("ora"),e=require("chalk"),r=require("@swc/core"),a=require("node:fs/promises"),n=require("node:path"),s=require("./key-finder.js"),o=require("./translation-manager.js"),i=require("../../utils/validation.js"),c=require("../plugin-manager.js"),u=require("../parsers/comment-parser.js"),l=require("../parsers/ast-visitors.js"),g=require("../../utils/logger.js");function f(t,e,r){if(t&&"object"==typeof t){for(const a of e)try{a.onVisitNode?.(t,r)}catch(t){console.warn(`Plugin ${a.name} onVisitNode failed:`,t)}for(const a of Object.keys(t)){const n=t[a];if(Array.isArray(n))for(const t of n)t&&"object"==typeof t&&f(t,e,r);else n&&"object"==typeof n&&f(n,e,r)}}}exports.extract=async function(t){t.extract.primaryLanguage||(t.extract.primaryLanguage=t.locales[0]),t.extract.secondaryLanguages||(t.extract.secondaryLanguages=t.locales.filter(e=>e!==t?.extract?.primaryLanguage)),t.extract.functions||(t.extract.functions=["t"]),t.extract.transComponents||(t.extract.transComponents=["Trans"]);const e=await s.findKeys(t);return o.getTranslations(e,t)},exports.processFile=async function(t,e,n,s){try{let o=await a.readFile(t,"utf-8");for(const r of e.plugins||[])o=await(r.onLoad?.(o,t))??o;const i=await r.parse(o,{syntax:"typescript",tsx:!0,comments:!0}),g=c.createPluginContext(s);u.extractKeysFromComments(o,e.extract.functions||["t"],g,e);new l.ASTVisitors(e,g,n).visit(i),(e.plugins||[]).length>0&&f(i,e.plugins||[],g)}catch(e){throw new i.ExtractorError("Failed to process file",t,e)}},exports.runExtractor=async function(r,c=new g.ConsoleLogger){r.extract.primaryLanguage||(r.extract.primaryLanguage=r.locales[0]||"en"),r.extract.secondaryLanguages||(r.extract.secondaryLanguages=r.locales.filter(t=>t!==r?.extract?.primaryLanguage)),i.validateExtractorConfig(r);const u=t("Running i18next key extractor...\n").start();try{const t=await s.findKeys(r,c);u.text=`Found ${t.size} unique keys. Updating translation files...`;const i=await o.getTranslations(t,r);let l=!1;for(const t of i)t.updated&&(l=!0,await a.mkdir(n.dirname(t.path),{recursive:!0}),await a.writeFile(t.path,JSON.stringify(t.newTranslations,null,2)),c.info(e.green(`Updated: ${t.path}`)));return u.succeed(e.bold("Extraction complete!")),l}catch(t){throw u.fail(e.red("Extraction failed.")),t}};
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"),s=require("./translation-manager.js"),i=require("../../utils/validation.js"),c=require("../plugin-manager.js"),l=require("../parsers/comment-parser.js"),u=require("../../utils/logger.js");function g(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&&g(e,t,r,a);else o&&"object"==typeof o&&g(o,t,r,a)}}}exports.extract=async function(e){e.extract.primaryLanguage||(e.extract.primaryLanguage=e.locales[0]),e.extract.secondaryLanguages||(e.extract.secondaryLanguages=e.locales.filter(t=>t!==e?.extract?.primaryLanguage)),e.extract.functions||(e.extract.functions=["t"]),e.extract.transComponents||(e.extract.transComponents=["Trans"]);const{allKeys:t,objectKeys:r}=await o.findKeys(e);return s.getTranslations(t,r,e)},exports.processFile=async function(e,t,n,o,s=new u.ConsoleLogger){try{let i=await a.readFile(e,"utf-8");for(const r of t.plugins||[])i=await(r.onLoad?.(i,e))??i;const u=await r.parse(i,{syntax:"typescript",tsx:!0,comments:!0}),f=c.createPluginContext(n);l.extractKeysFromComments(i,t.extract.functions||["t"],f,t),o.visit(u),(t.plugins||[]).length>0&&g(u,t.plugins||[],f,s)}catch(t){throw new i.ExtractorError("Failed to process file",e,t)}},exports.runExtractor=async function(r,c=new u.ConsoleLogger){r.extract.primaryLanguage||(r.extract.primaryLanguage=r.locales[0]||"en"),r.extract.secondaryLanguages||(r.extract.secondaryLanguages=r.locales.filter(e=>e!==r?.extract?.primaryLanguage)),i.validateExtractorConfig(r);const l=e("Running i18next key extractor...\n").start();try{const{allKeys:e,objectKeys:i}=await o.findKeys(r,c);l.text=`Found ${e.size} unique keys. Updating translation files...`;const u=await s.getTranslations(e,i,r);let g=!1;for(const e of u)e.updated&&(g=!0,await a.mkdir(n.dirname(e.path),{recursive:!0}),await a.writeFile(e.path,JSON.stringify(e.newTranslations,null,2)),c.info(t.green(`Updated: ${e.path}`)));return l.succeed(t.bold("Extraction complete!")),g}catch(e){throw l.fail(t.red("Extraction failed.")),e}};
@@ -1 +1 @@
1
- "use strict";var e=require("glob"),n=require("./extractor.js"),r=require("../../utils/logger.js"),i=require("../plugin-manager.js");exports.findKeys=async function(o,s=new r.ConsoleLogger){const t=await async function(n){return await e.glob(n.extract.input,{ignore:"node_modules/**",cwd:process.cwd()})}(o),a=new Map;await i.initializePlugins(o.plugins||[]);for(const e of t)await n.processFile(e,o,s,a);for(const e of o.plugins||[])await(e.onEnd?.(a));return a};
1
+ "use strict";var e=require("glob"),r=require("./extractor.js"),s=require("../../utils/logger.js"),i=require("../plugin-manager.js"),t=require("../parsers/ast-visitors.js");exports.findKeys=async function(n,o=new s.ConsoleLogger){const a=await async function(r){return await e.glob(r.extract.input,{ignore:"node_modules/**",cwd:process.cwd()})}(n),u=new Map,c=new t.ASTVisitors(n,i.createPluginContext(u),o);await i.initializePlugins(n.plugins||[]);for(const e of a)await r.processFile(e,n,u,c,o);for(const e of n.plugins||[])await(e.onEnd?.(u));return{allKeys:u,objectKeys:c.objectKeys}};
@@ -1 +1 @@
1
- "use strict";var e=require("node:fs/promises"),t=require("node:path"),a=require("../../utils/nested-object.js"),s=require("../../utils/file-utils.js");function r(e){const t=`^${e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(t)}exports.getTranslations=async function(n,o){const c=o.extract.defaultNS??"translation",u=o.extract.keySeparator??".",l=(o.extract.preservePatterns??[]).map(r);o.extract.primaryLanguage||(o.extract.primaryLanguage=o.locales[0]||"en"),o.extract.secondaryLanguages||(o.extract.secondaryLanguages=o.locales.filter(e=>e!==o.extract.primaryLanguage));const i=new Map;for(const e of n.values()){const t=e.ns||c;i.has(t)||i.set(t,[]),i.get(t).push(e)}const g=[];for(const r of o.locales)for(const[n,c]of i.entries()){const i=s.getOutputPath(o.extract.output,r,n),p=t.resolve(process.cwd(),i);let f="",d={};try{f=await e.readFile(p,"utf-8"),d=JSON.parse(f)}catch(e){}const x={},y=a.getNestedKeys(d,u);for(const e of y)if(l.some(t=>t.test(e))){const t=a.getNestedValue(d,e,u);a.setNestedValue(x,e,t,u)}const m=!1===o.extract.sort?c:c.sort((e,t)=>e.key.localeCompare(t.key));for(const{key:e,defaultValue:t}of m){const s=a.getNestedValue(d,e,u)??(r===o.extract?.primaryLanguage?t:"");a.setNestedValue(x,e,s,u)}const N=o.extract.indentation??2,h=JSON.stringify(x,null,N);g.push({path:p,updated:h!==f,newTranslations:x,existingTranslations:d})}return g};
1
+ "use strict";var e=require("node:fs/promises"),t=require("node:path"),s=require("../../utils/nested-object.js"),a=require("../../utils/file-utils.js");function r(e){const t=`^${e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(t)}exports.getTranslations=async function(n,o,c){const u=c.extract.defaultNS??"translation",l=c.extract.keySeparator??".",i=[...c.extract.preservePatterns||[]];for(const e of o)i.push(`${e}.*`);const p=i.map(r);c.extract.primaryLanguage||(c.extract.primaryLanguage=c.locales[0]||"en"),c.extract.secondaryLanguages||(c.extract.secondaryLanguages=c.locales.filter(e=>e!==c.extract.primaryLanguage));const g=new Map;for(const e of n.values()){const t=e.ns||u;g.has(t)||g.set(t,[]),g.get(t).push(e)}const f=[];for(const r of c.locales)for(const[n,o]of g.entries()){const u=a.getOutputPath(c.extract.output,r,n),i=t.resolve(process.cwd(),u);let g="",d={};try{g=await e.readFile(i,"utf-8"),d=JSON.parse(g)}catch(e){}const x={},y=s.getNestedKeys(d,l);for(const e of y)if(p.some(t=>t.test(e))){const t=s.getNestedValue(d,e,l);s.setNestedValue(x,e,t,l)}const h=!1===c.extract.sort?o:o.sort((e,t)=>e.key.localeCompare(t.key));for(const{key:e,defaultValue:t}of h){const a=s.getNestedValue(d,e,l)??(r===c.extract?.primaryLanguage?t:"");s.setNestedValue(x,e,a,l)}const m=c.extract.indentation??2,N=JSON.stringify(x,null,m);f.push({path:i,updated:N!==g,newTranslations:x,existingTranslations:d})}return f};
@@ -1 +1 @@
1
- "use strict";var e=require("./jsx-parser.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];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&&e.type&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){if("CallExpression"!==e.init?.type)return;const t=e.init.callee;"Identifier"===t.type&&(this.config.extract.useTranslationNames||["useTranslation","getT","useT"]).indexOf(t.value)>-1?this.handleUseTranslationDeclarator(e):"MemberExpression"===t.type&&"Identifier"===t.property.type&&"getFixedT"===t.property.value&&this.handleGetFixedTDeclarator(e)}handleUseTranslationDeclarator(e){if(!e.init||"CallExpression"!==e.init.type)return;let t;if("ArrayPattern"===e.id.type){const n=e.id.elements[0];"Identifier"===n?.type&&(t=n.value)}if("ObjectPattern"===e.id.type)for(const n of e.id.properties){if("AssignmentPatternProperty"===n.type&&"Identifier"===n.key.type&&"t"===n.key.value){t="t";break}if("KeyValuePatternProperty"===n.type&&"Identifier"===n.key.type&&"t"===n.key.value&&"Identifier"===n.value.type){t=n.value.value;break}}if(!t)return;const n=e.init.arguments?.[0]?.expression;let i;"StringLiteral"===n?.type?i=n.value:"ArrayExpression"===n?.type&&"StringLiteral"===n.elements[0]?.expression.type&&(i=n.elements[0].expression.value);const r=e.init.arguments?.[1]?.expression;let s;"ObjectExpression"===r?.type&&(s=this.getObjectPropValue(r,"keyPrefix")),this.setVarInScope(t,{defaultNs:i,keyPrefix:s})}handleGetFixedTDeclarator(e){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const t=e.id.value,n=e.init.arguments,i=n[1]?.expression,r=n[2]?.expression,s="StringLiteral"===i?.type?i.value:void 0,a="StringLiteral"===r?.type?r.value:void 0;(s||a)&&this.setVarInScope(t,{defaultNs:s,keyPrefix:a})}handleCallExpression(e){const t=e.callee;if("Identifier"!==t.type)return;const n=this.getVarFromScope(t.value);if(!((this.config.extract.functions||[]).includes(t.value)||void 0!==n)||0===e.arguments.length)return;const i=e.arguments[0].expression,r=[];if("StringLiteral"===i.type)r.push(i.value);else if("ArrowFunctionExpression"===i.type){const e=this.extractKeyFromSelector(i);e&&r.push(e)}else if("ArrayExpression"===i.type)for(const e of i.elements)"StringLiteral"===e?.expression.type&&r.push(e.expression.value);if(0===r.length)return;const s=e.arguments.length>1?e.arguments[1].expression:void 0,a=this.getDefaultValue(e,r[r.length-1]);for(let e=0;e<r.length;e++){let t,i=r[e];"ObjectExpression"===s?.type&&(t=this.getObjectPropValue(s,"ns")),!t&&n?.defaultNs&&(t=n.defaultNs);const o=this.config.extract.nsSeparator??":";if(!t&&o&&i.includes(o)){const e=i.split(o);t=e.shift(),i=e.join(o)}t||(t=this.config.extract.defaultNS);let l=i;if(n?.keyPrefix){const e=this.config.extract.keySeparator??".";l=`${n.keyPrefix}${e}${i}`}const p=e===r.length-1?a:i;if("ObjectExpression"===s?.type){const e=this.getObjectPropValue(s,"context"),n=this.config.extract.contextSeparator??"_";if(e){this.pluginContext.addKey({key:`${l}${n}${e}`,ns:t,defaultValue:p});continue}if(void 0!==this.getObjectPropValue(s,"count")){this.handlePluralKeys(l,p,t);continue}}this.pluginContext.addKey({key:l,ns:t,defaultValue:p})}}handlePluralKeys(e,t,n){try{const i=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,r=this.config.extract.pluralSeparator??"_";for(const s of i)this.pluginContext.addKey({key:`${e}${r}${s}`,ns:n,defaultValue:t,hasCount:!0})}catch(i){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,defaultValue:t,ns:n})}}getDefaultValue(e,t){if(e.arguments.length<=1)return t;const n=e.arguments[1].expression;return"StringLiteral"===n.type?n.value||t:"ObjectExpression"===n.type&&this.getObjectPropValue(n,"defaultValue")||t}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e.extractFromTransComponent(t,this.config);if(n){if(!n.ns){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===e?.type&&"JSXExpressionContainer"===e.value?.type&&"Identifier"===e.value.expression.type){const t=e.value.expression.value,i=this.getVarFromScope(t);i?.defaultNs&&(n.ns=i.defaultNs)}}n.ns||(n.ns=this.config.extract.defaultNS),n.hasCount?this.handlePluralKeys(n.key,n.defaultValue,n.ns):this.pluginContext.addKey(n)}}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}getObjectPropValue(e,t){const n=e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t));if("KeyValueProperty"===n?.type){const e=n.value;return"StringLiteral"===e.type?e.value:""}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const i=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)i.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;i.unshift(e.expression.value)}n=n.object}if(i.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return i.join(t)}return null}};
1
+ "use strict";var e=require("./jsx-parser.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&e.type&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){if("CallExpression"!==e.init?.type)return;const t=e.init.callee;"Identifier"===t.type&&(this.config.extract.useTranslationNames||["useTranslation","getT","useT"]).indexOf(t.value)>-1?this.handleUseTranslationDeclarator(e):"MemberExpression"===t.type&&"Identifier"===t.property.type&&"getFixedT"===t.property.value&&this.handleGetFixedTDeclarator(e)}handleUseTranslationDeclarator(e){if(!e.init||"CallExpression"!==e.init.type)return;let t;if("ArrayPattern"===e.id.type){const n=e.id.elements[0];"Identifier"===n?.type&&(t=n.value)}if("ObjectPattern"===e.id.type)for(const n of e.id.properties){if("AssignmentPatternProperty"===n.type&&"Identifier"===n.key.type&&"t"===n.key.value){t="t";break}if("KeyValuePatternProperty"===n.type&&"Identifier"===n.key.type&&"t"===n.key.value&&"Identifier"===n.value.type){t=n.value.value;break}}if(!t)return;const n=e.init.arguments?.[0]?.expression;let i;"StringLiteral"===n?.type?i=n.value:"ArrayExpression"===n?.type&&"StringLiteral"===n.elements[0]?.expression.type&&(i=n.elements[0].expression.value);const r=e.init.arguments?.[1]?.expression;let s;if("ObjectExpression"===r?.type){const e=this.getObjectPropValue(r,"keyPrefix");s="string"==typeof e?e:void 0}this.setVarInScope(t,{defaultNs:i,keyPrefix:s})}handleGetFixedTDeclarator(e){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const t=e.id.value,n=e.init.arguments,i=n[1]?.expression,r=n[2]?.expression,s="StringLiteral"===i?.type?i.value:void 0,a="StringLiteral"===r?.type?r.value:void 0;(s||a)&&this.setVarInScope(t,{defaultNs:s,keyPrefix:a})}handleCallExpression(e){const t=e.callee;if("Identifier"!==t.type)return;const n=this.getVarFromScope(t.value);if(!((this.config.extract.functions||[]).includes(t.value)||void 0!==n)||0===e.arguments.length)return;const i=e.arguments[0].expression,r=[];if("StringLiteral"===i.type)r.push(i.value);else if("ArrowFunctionExpression"===i.type){const e=this.extractKeyFromSelector(i);e&&r.push(e)}else if("ArrayExpression"===i.type)for(const e of i.elements)"StringLiteral"===e?.expression.type&&r.push(e.expression.value);if(0===r.length)return;let s,a;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?a=t:"StringLiteral"===t.type&&(s=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(a=t)}const o=a?this.getObjectPropValue(a,"defaultValue"):void 0,l="string"==typeof o?o:s;for(let e=0;e<r.length;e++){let t,i=r[e];if("ObjectExpression"===a?.type){const e=this.getObjectPropValue(a,"ns");"string"==typeof e&&(t=e)}!t&&n?.defaultNs&&(t=n.defaultNs);const s=this.config.extract.nsSeparator??":";if(!t&&s&&i.includes(s)){const e=i.split(s);t=e.shift(),i=e.join(s)}t||(t=this.config.extract.defaultNS);let o=i;if(n?.keyPrefix){const e=this.config.extract.keySeparator??".";o=`${n.keyPrefix}${e}${i}`}const p=e===r.length-1&&l||i;if("ObjectExpression"===a?.type){const e=this.getObjectProperty(a,"context");if("ConditionalExpression"===e?.value?.type){const n=this.resolvePossibleStringValues(e.value),i=this.config.extract.contextSeparator??"_";if(n.length>0){n.forEach(e=>{this.pluginContext.addKey({key:`${o}${i}${e}`,ns:t,defaultValue:p})}),this.pluginContext.addKey({key:o,ns:t,defaultValue:p});continue}}const n=this.getObjectPropValue(a,"context");if("string"==typeof n&&n){const e=this.config.extract.contextSeparator??"_";this.pluginContext.addKey({key:`${o}${e}${n}`,ns:t,defaultValue:p});continue}if(void 0!==this.getObjectPropValue(a,"count")){this.handlePluralKeys(o,p,t);continue}!0===this.getObjectPropValue(a,"returnObjects")&&this.objectKeys.add(o)}this.pluginContext.addKey({key:o,ns:t,defaultValue:p})}}handlePluralKeys(e,t,n){try{const i=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,r=this.config.extract.pluralSeparator??"_";for(const s of i)this.pluginContext.addKey({key:`${e}${r}${s}`,ns:n,defaultValue:t,hasCount:!0})}catch(i){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,defaultValue:t,ns:n})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e.extractFromTransComponent(t,this.config);if(n){if(!n.ns){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===e?.type&&"JSXExpressionContainer"===e.value?.type&&"Identifier"===e.value.expression.type){const t=e.value.expression.value,i=this.getVarFromScope(t);i?.defaultNs&&(n.ns=i.defaultNs)}}if(n.ns||(n.ns=this.config.extract.defaultNS),n.contextExpression){const e=this.resolvePossibleStringValues(n.contextExpression),t=this.config.extract.contextSeparator??"_";if(e.length>0){for(const i of e)this.pluginContext.addKey({key:`${n.key}${t}${i}`,ns:n.ns,defaultValue:n.defaultValue});this.pluginContext.addKey(n)}}else n.hasCount?this.handlePluralKeys(n.key,n.defaultValue,n.ns):this.pluginContext.addKey(n)}}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}getObjectPropValue(e,t){const n=e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t));if("KeyValueProperty"===n?.type){const e=n.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const i=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)i.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;i.unshift(e.expression.value)}n=n.object}if(i.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return i.join(t)}return null}resolvePossibleStringValues(e){if("StringLiteral"===e.type)return[e.value];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent),...this.resolvePossibleStringValues(e.alternate)]}return"Identifier"===e.type&&e.value,[]}getObjectProperty(e,t){return e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t))}};
@@ -1 +1 @@
1
- "use strict";function e(e,t){const n=new Set(t.extract.transKeepBasicHtmlNodesFor??["br","strong","i","p"]);return function e(t){let i="";return t.forEach((t,r)=>{if("JSXText"===t.type)i+=t.value;else if("JSXExpressionContainer"===t.type){const e=t.expression;if("StringLiteral"===e.type)i+=e.value;else if("Identifier"===e.type)i+=`{{${e.value}}}`;else if("ObjectExpression"===e.type){const t=e.properties[0];t&&"Identifier"===t.type&&(i+=`{{${t.value}}}`)}}else if("JSXElement"===t.type){let a;"Identifier"===t.opening.name.type&&(a=t.opening.name.value);const l=e(t.children);a&&n.has(a)?i+=`<${a}>${l}</${a}>`:i+=`<${r}>${l}</${r}>`}else"JSXFragment"===t.type&&(i+=e(t.children))}),i}(e).trim().replace(/\s{2,}/g," ")}exports.extractFromTransComponent=function(t,n){const i=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),a=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),l=!!a;let u;if(u="JSXAttribute"===i?.type&&"StringLiteral"===i.value?.type?i.value.value:e(t.children,n),!u)return null;const p=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value),s="JSXAttribute"===p?.type&&"StringLiteral"===p.value?.type?p.value.value:void 0;let o=n.extract.defaultValue||"";return o="JSXAttribute"===r?.type&&"StringLiteral"===r.value?.type?r.value.value:e(t.children,n),{key:u,ns:s,defaultValue:o||u,hasCount:l}};
1
+ "use strict";function e(e,t){const n=new Set(t.extract.transKeepBasicHtmlNodesFor??["br","strong","i","p"]);return function e(t){let i="";return t.forEach((t,r)=>{if("JSXText"===t.type)i+=t.value;else if("JSXExpressionContainer"===t.type){const e=t.expression;if("StringLiteral"===e.type)i+=e.value;else if("Identifier"===e.type)i+=`{{${e.value}}}`;else if("ObjectExpression"===e.type){const t=e.properties[0];t&&"Identifier"===t.type&&(i+=`{{${t.value}}}`)}}else if("JSXElement"===t.type){let a;"Identifier"===t.opening.name.type&&(a=t.opening.name.value);const u=e(t.children);a&&n.has(a)?i+=`<${a}>${u}</${a}>`:i+=`<${r}>${u}</${r}>`}else"JSXFragment"===t.type&&(i+=e(t.children))}),i}(e).trim().replace(/\s{2,}/g," ")}exports.extractFromTransComponent=function(t,n){const i=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),a=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),u=!!a,l=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value),p="JSXAttribute"===l?.type&&"JSXExpressionContainer"===l.value?.type?l.value.expression:void 0;let s;if(s="JSXAttribute"===i?.type&&"StringLiteral"===i.value?.type?i.value.value:e(t.children,n),!s)return null;const o=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value),y="JSXAttribute"===o?.type&&"StringLiteral"===o.value?.type?o.value.value:void 0;let f=n.extract.defaultValue||"";return f="JSXAttribute"===r?.type&&"StringLiteral"===r.value?.type?r.value.value:e(t.children,n),{key:s,ns:y,defaultValue:f||s,hasCount:u,contextExpression:p}};
@@ -1 +1 @@
1
- "use strict";var e=require("chalk"),o=require("ora"),t=require("node:path"),a=require("node:fs/promises"),s=require("./extractor/core/key-finder.js"),n=require("./utils/nested-object.js"),r=require("./utils/file-utils.js");function l(o,t,a){const s=a>0?Math.round(t/a*100):100,n=c(s);console.log(`${e.bold(o)}: ${n} ${s}% (${t}/${a})`)}function c(o){const t=Math.round(o/100*20),a=20-t;return`[${e.green("".padStart(t,"■"))}${"".padStart(a,"□")}]`}function i(){console.log(e.yellow.bold("\n✨ Take your localization to the next level!")),console.log("Manage translations with your team in the cloud with locize => https://www.locize.com/docs/getting-started"),console.log(`Run ${e.cyan("npx i18next-cli locize-migrate")} to get started.`)}exports.runStatus=async function(u,y={}){u.extract.primaryLanguage||(u.extract.primaryLanguage=u.locales[0]||"en"),u.extract.secondaryLanguages||(u.extract.secondaryLanguages=u.locales.filter(e=>e!==u?.extract?.primaryLanguage));const d=o("Analyzing project localization status...\n").start();try{const o=await async function(e){const o=await s.findKeys(e),{primaryLanguage:l,keySeparator:c=".",defaultNS:i="translation"}=e.extract,u=e.locales.filter(e=>e!==l),y=new Map;for(const e of o.values()){const o=e.ns||i;y.has(o)||y.set(o,[]),y.get(o).push(e)}const d={totalKeys:o.size,keysByNs:y,locales:new Map};for(const o of u){let s=0;const l=new Map;for(const[i,u]of y.entries()){const y=r.getOutputPath(e.extract.output,o,i);let d={};try{const e=await a.readFile(t.resolve(process.cwd(),y),"utf-8");d=JSON.parse(e)}catch{}let g=0;const f=u.map(({key:e})=>{const o=!!n.getNestedValue(d,e,c??".");return o&&g++,{key:e,isTranslated:o}});l.set(i,{totalKeys:u.length,translatedKeys:g,keyDetails:f}),s+=g}d.locales.set(o,{totalTranslated:s,namespaces:l})}return d}(u);d.succeed("Analysis complete."),function(o,t,a){a.detail?function(o,t,a,s){if(a===t.extract.primaryLanguage)return void console.log(e.yellow(`Locale "${a}" is the primary language. All keys are considered present.`));if(!t.locales.includes(a))return void console.error(e.red(`Error: Locale "${a}" is not defined in your configuration.`));const n=o.locales.get(a);if(!n)return void console.error(e.red(`Error: Locale "${a}" is not a valid secondary language.`));console.log(e.bold(`\nKey Status for "${e.cyan(a)}":`));const r=Array.from(o.keysByNs.values()).flat().length;l("Overall",n.totalTranslated,r);const c=s?[s]:Array.from(n.namespaces.keys()).sort();for(const o of c){const t=n.namespaces.get(o);t&&(console.log(e.cyan.bold(`\nNamespace: ${o}`)),l("Namespace Progress",t.translatedKeys,t.totalKeys),t.keyDetails.forEach(({key:o,isTranslated:t})=>{const a=t?e.green("✓"):e.red("✗");console.log(` ${a} ${o}`)}))}const u=r-n.totalTranslated;u>0?console.log(e.yellow.bold(`\nSummary: Found ${u} missing translations for "${a}".`)):console.log(e.green.bold(`\nSummary: 🎉 All keys are translated for "${a}".`));i()}(o,t,a.detail,a.namespace):a.namespace?function(o,t,a){const s=o.keysByNs.get(a);if(!s)return void console.error(e.red(`Error: Namespace "${a}" was not found in your source code.`));console.log(e.cyan.bold(`\nStatus for Namespace: "${a}"`)),console.log("------------------------");for(const[e,t]of o.locales.entries()){const o=t.namespaces.get(a);if(o){const t=o.totalKeys>0?Math.round(o.translatedKeys/o.totalKeys*100):100,a=c(t);console.log(`- ${e}: ${a} ${t}% (${o.translatedKeys}/${o.totalKeys} keys)`)}}i()}(o,0,a.namespace):function(o,t){const{primaryLanguage:a}=t.extract;console.log(e.cyan.bold("\ni18next Project Status")),console.log("------------------------"),console.log(`🔑 Keys Found: ${e.bold(o.totalKeys)}`),console.log(`📚 Namespaces Found: ${e.bold(o.keysByNs.size)}`),console.log(`🌍 Locales: ${e.bold(t.locales.join(", "))}`),console.log(`✅ Primary Language: ${e.bold(a)}`),console.log("\nTranslation Progress:");for(const[e,t]of o.locales.entries()){const a=o.totalKeys>0?Math.round(t.totalTranslated/o.totalKeys*100):100,s=c(a);console.log(`- ${e}: ${s} ${a}% (${t.totalTranslated}/${o.totalKeys} keys)`)}i()}(o,t)}(o,u,y)}catch(e){d.fail("Failed to generate status report."),console.error(e)}};
1
+ "use strict";var e=require("chalk"),o=require("ora"),t=require("node:path"),a=require("node:fs/promises"),s=require("./extractor/core/key-finder.js"),n=require("./utils/nested-object.js"),r=require("./utils/file-utils.js");function l(o,t,a){const s=a>0?Math.round(t/a*100):100,n=c(s);console.log(`${e.bold(o)}: ${n} ${s}% (${t}/${a})`)}function c(o){const t=Math.round(o/100*20),a=20-t;return`[${e.green("".padStart(t,"■"))}${"".padStart(a,"□")}]`}function i(){console.log(e.yellow.bold("\n✨ Take your localization to the next level!")),console.log("Manage translations with your team in the cloud with locize => https://www.locize.com/docs/getting-started"),console.log(`Run ${e.cyan("npx i18next-cli locize-migrate")} to get started.`)}exports.runStatus=async function(u,y={}){u.extract.primaryLanguage||(u.extract.primaryLanguage=u.locales[0]||"en"),u.extract.secondaryLanguages||(u.extract.secondaryLanguages=u.locales.filter(e=>e!==u?.extract?.primaryLanguage));const d=o("Analyzing project localization status...\n").start();try{const o=await async function(e){const{allKeys:o}=await s.findKeys(e),{primaryLanguage:l,keySeparator:c=".",defaultNS:i="translation"}=e.extract,u=e.locales.filter(e=>e!==l),y=new Map;for(const e of o.values()){const o=e.ns||i;y.has(o)||y.set(o,[]),y.get(o).push(e)}const d={totalKeys:o.size,keysByNs:y,locales:new Map};for(const o of u){let s=0;const l=new Map;for(const[i,u]of y.entries()){const y=r.getOutputPath(e.extract.output,o,i);let d={};try{const e=await a.readFile(t.resolve(process.cwd(),y),"utf-8");d=JSON.parse(e)}catch{}let g=0;const f=u.map(({key:e})=>{const o=!!n.getNestedValue(d,e,c??".");return o&&g++,{key:e,isTranslated:o}});l.set(i,{totalKeys:u.length,translatedKeys:g,keyDetails:f}),s+=g}d.locales.set(o,{totalTranslated:s,namespaces:l})}return d}(u);d.succeed("Analysis complete."),function(o,t,a){a.detail?function(o,t,a,s){if(a===t.extract.primaryLanguage)return void console.log(e.yellow(`Locale "${a}" is the primary language. All keys are considered present.`));if(!t.locales.includes(a))return void console.error(e.red(`Error: Locale "${a}" is not defined in your configuration.`));const n=o.locales.get(a);if(!n)return void console.error(e.red(`Error: Locale "${a}" is not a valid secondary language.`));console.log(e.bold(`\nKey Status for "${e.cyan(a)}":`));const r=Array.from(o.keysByNs.values()).flat().length;l("Overall",n.totalTranslated,r);const c=s?[s]:Array.from(n.namespaces.keys()).sort();for(const o of c){const t=n.namespaces.get(o);t&&(console.log(e.cyan.bold(`\nNamespace: ${o}`)),l("Namespace Progress",t.translatedKeys,t.totalKeys),t.keyDetails.forEach(({key:o,isTranslated:t})=>{const a=t?e.green("✓"):e.red("✗");console.log(` ${a} ${o}`)}))}const u=r-n.totalTranslated;u>0?console.log(e.yellow.bold(`\nSummary: Found ${u} missing translations for "${a}".`)):console.log(e.green.bold(`\nSummary: 🎉 All keys are translated for "${a}".`));i()}(o,t,a.detail,a.namespace):a.namespace?function(o,t,a){const s=o.keysByNs.get(a);if(!s)return void console.error(e.red(`Error: Namespace "${a}" was not found in your source code.`));console.log(e.cyan.bold(`\nStatus for Namespace: "${a}"`)),console.log("------------------------");for(const[e,t]of o.locales.entries()){const o=t.namespaces.get(a);if(o){const t=o.totalKeys>0?Math.round(o.translatedKeys/o.totalKeys*100):100,a=c(t);console.log(`- ${e}: ${a} ${t}% (${o.translatedKeys}/${o.totalKeys} keys)`)}}i()}(o,0,a.namespace):function(o,t){const{primaryLanguage:a}=t.extract;console.log(e.cyan.bold("\ni18next Project Status")),console.log("------------------------"),console.log(`🔑 Keys Found: ${e.bold(o.totalKeys)}`),console.log(`📚 Namespaces Found: ${e.bold(o.keysByNs.size)}`),console.log(`🌍 Locales: ${e.bold(t.locales.join(", "))}`),console.log(`✅ Primary Language: ${e.bold(a)}`),console.log("\nTranslation Progress:");for(const[e,t]of o.locales.entries()){const a=o.totalKeys>0?Math.round(t.totalTranslated/o.totalKeys*100):100,s=c(a);console.log(`- ${e}: ${s} ${a}% (${t.totalTranslated}/${o.totalKeys} keys)`)}i()}(o,t)}(o,u,y)}catch(e){d.fail("Failed to generate status report."),console.error(e)}};
package/dist/esm/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as o}from"commander";import e from"chokidar";import{glob as t}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:fs/promises";import"node:path";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.7"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async o=>{const a=await n(),c=async()=>{const e=await r(a);o.ci&&e&&(console.error(i.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(i.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${i.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),o.watch){console.log("\nWatching for changes...");e.watch(await t(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(o,e)=>{let t=await a();if(!t){console.log(i.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),t=o}await f(t,{detail:o,namespace:e.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async o=>{const i=await n(),a=()=>s(i);if(await a(),o.watch){console.log("\nWatching for changes...");e.watch(await t(i.types?.input||[]),{persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const o=await n();await l(o)}),w.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await m()}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let o=await a();if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const e=await c();e||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),o=e}await d(o)}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async o=>{const e=await n();await g(e,o)}),w.command("locize-download").description("Download all translations from your locize project.").action(async o=>{const e=await n();await u(e,o)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async o=>{const e=await n();await y(e,o)}),w.parse(process.argv);
2
+ import{Command as o}from"commander";import e from"chokidar";import{glob as t}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:fs/promises";import"node:path";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.9"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async o=>{const a=await n(),c=async()=>{const e=await r(a);o.ci&&e&&(console.error(i.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(i.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${i.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),o.watch){console.log("\nWatching for changes...");e.watch(await t(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(o,e)=>{let t=await a();if(!t){console.log(i.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),t=o}await f(t,{detail:o,namespace:e.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async o=>{const i=await n(),a=()=>s(i);if(await a(),o.watch){console.log("\nWatching for changes...");e.watch(await t(i.types?.input||[]),{persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const o=await n();await l(o)}),w.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await m()}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let o=await a();if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const e=await c();e||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),o=e}await d(o)}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async o=>{const e=await n();await g(e,o)}),w.command("locize-download").description("Download all translations from your locize project.").action(async o=>{const e=await n();await u(e,o)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async o=>{const e=await n();await y(e,o)}),w.parse(process.argv);
@@ -1 +1 @@
1
- import{resolve as o}from"node:path";import{pathToFileURL as r}from"node:url";import{access as t}from"node:fs/promises";import{createJiti as e}from"jiti";import n from"inquirer";import i from"chalk";import{runInit as a}from"./init.js";const c=["i18next.config.ts","i18next.config.js","i18next.config.mjs","i18next.config.cjs"];function l(o){return o}async function s(){const n=await async function(){for(const r of c){const e=o(process.cwd(),r);try{return await t(e),e}catch{}}return null}();if(!n)return null;try{let o;if(n.endsWith(".ts")){const r=e(import.meta.url),t=await r.import(n,{default:!0});o=t}else{const t=r(n).href,e=await import(`${t}?t=${Date.now()}`);o=e.default}return o?(o.extract||={},o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(r=>r!==o.extract.primaryLanguage),o):(console.error(`Error: No default export found in ${n}`),null)}catch(o){return console.error(`Error loading configuration from ${n}`),console.error(o),null}}async function f(){let o=await s();if(o)return o;const{shouldInit:r}=await n.prompt([{type:"confirm",name:"shouldInit",message:i.yellow("Configuration file not found. Would you like to create one now?"),default:!0}]);if(r){if(await a(),console.log(i.green("Configuration created. Resuming command...")),o=await s(),o)return o;console.error(i.red("Error: Failed to load configuration after creation. Please try running the command again.")),process.exit(1)}else console.log("Operation cancelled. Please create a configuration file to proceed."),process.exit(0)}export{l as defineConfig,f as ensureConfig,s as loadConfig};
1
+ import{resolve as r}from"node:path";import{pathToFileURL as t}from"node:url";import{access as o}from"node:fs/promises";import{createJiti as e}from"jiti";import n from"inquirer";import i from"chalk";import{runInit as a}from"./init.js";import{ConsoleLogger as c}from"./utils/logger.js";const f=["i18next.config.ts","i18next.config.js","i18next.config.mjs","i18next.config.cjs"];function l(r){return r}async function s(n=new c){const i=await async function(){for(const t of f){const e=r(process.cwd(),t);try{return await o(e),e}catch{}}return null}();if(!i)return null;try{let r;if(i.endsWith(".ts")){const t=e(import.meta.url),o=await t.import(i,{default:!0});r=o}else{const o=t(i).href,e=await import(`${o}?t=${Date.now()}`);r=e.default}return r?(r.extract||={},r.extract.primaryLanguage||=r.locales[0]||"en",r.extract.secondaryLanguages||=r.locales.filter(t=>t!==r.extract.primaryLanguage),r):(n.error(`Error: No default export found in ${i}`),null)}catch(r){return n.error(`Error loading configuration from ${i}`),n.error(r),null}}async function u(r=new c){let t=await s();if(t)return t;const{shouldInit:o}=await n.prompt([{type:"confirm",name:"shouldInit",message:i.yellow("Configuration file not found. Would you like to create one now?"),default:!0}]);if(o){if(await a(),r.info(i.green("Configuration created. Resuming command...")),t=await s(),t)return t;r.error(i.red("Error: Failed to load configuration after creation. Please try running the command again.")),process.exit(1)}else r.info("Operation cancelled. Please create a configuration file to proceed."),process.exit(0)}export{l as defineConfig,u as ensureConfig,s as loadConfig};
@@ -1 +1 @@
1
- import t from"ora";import a from"chalk";import{parse as r}from"@swc/core";import{mkdir as o,writeFile as e,readFile as n}from"node:fs/promises";import{dirname as s}from"node:path";import{findKeys as i}from"./key-finder.js";import{getTranslations as c}from"./translation-manager.js";import{validateExtractorConfig as f,ExtractorError as p}from"../../utils/validation.js";import{createPluginContext as m}from"../plugin-manager.js";import{extractKeysFromComments as l}from"../parsers/comment-parser.js";import{ASTVisitors as u}from"../parsers/ast-visitors.js";import{ConsoleLogger as g}from"../../utils/logger.js";async function y(r,n=new g){r.extract.primaryLanguage||(r.extract.primaryLanguage=r.locales[0]||"en"),r.extract.secondaryLanguages||(r.extract.secondaryLanguages=r.locales.filter(t=>t!==r?.extract?.primaryLanguage)),f(r);const p=t("Running i18next key extractor...\n").start();try{const t=await i(r,n);p.text=`Found ${t.size} unique keys. Updating translation files...`;const f=await c(t,r);let m=!1;for(const t of f)t.updated&&(m=!0,await o(s(t.path),{recursive:!0}),await e(t.path,JSON.stringify(t.newTranslations,null,2)),n.info(a.green(`Updated: ${t.path}`)));return p.succeed(a.bold("Extraction complete!")),m}catch(t){throw p.fail(a.red("Extraction failed.")),t}}async function d(t,a,o,e){try{let s=await n(t,"utf-8");for(const r of a.plugins||[])s=await(r.onLoad?.(s,t))??s;const i=await r(s,{syntax:"typescript",tsx:!0,comments:!0}),c=m(e);l(s,a.extract.functions||["t"],c,a);new u(a,c,o).visit(i),(a.plugins||[]).length>0&&x(i,a.plugins||[],c)}catch(a){throw new p("Failed to process file",t,a)}}function x(t,a,r){if(t&&"object"==typeof t){for(const o of a)try{o.onVisitNode?.(t,r)}catch(t){console.warn(`Plugin ${o.name} onVisitNode failed:`,t)}for(const o of Object.keys(t)){const e=t[o];if(Array.isArray(e))for(const t of e)t&&"object"==typeof t&&x(t,a,r);else e&&"object"==typeof e&&x(e,a,r)}}}async function w(t){t.extract.primaryLanguage||(t.extract.primaryLanguage=t.locales[0]),t.extract.secondaryLanguages||(t.extract.secondaryLanguages=t.locales.filter(a=>a!==t?.extract?.primaryLanguage)),t.extract.functions||(t.extract.functions=["t"]),t.extract.transComponents||(t.extract.transComponents=["Trans"]);const a=await i(t);return c(a,t)}export{w as extract,d as processFile,y as runExtractor};
1
+ import t from"ora";import a from"chalk";import{parse as e}from"@swc/core";import{mkdir as r,writeFile as o,readFile as n}from"node:fs/promises";import{dirname as s}from"node:path";import{findKeys as i}from"./key-finder.js";import{getTranslations as c}from"./translation-manager.js";import{validateExtractorConfig as f,ExtractorError as l}from"../../utils/validation.js";import{createPluginContext as p}from"../plugin-manager.js";import{extractKeysFromComments as m}from"../parsers/comment-parser.js";import{ConsoleLogger as u}from"../../utils/logger.js";async function g(e,n=new u){e.extract.primaryLanguage||(e.extract.primaryLanguage=e.locales[0]||"en"),e.extract.secondaryLanguages||(e.extract.secondaryLanguages=e.locales.filter(t=>t!==e?.extract?.primaryLanguage)),f(e);const l=t("Running i18next key extractor...\n").start();try{const{allKeys:t,objectKeys:f}=await i(e,n);l.text=`Found ${t.size} unique keys. Updating translation files...`;const p=await c(t,f,e);let m=!1;for(const t of p)t.updated&&(m=!0,await r(s(t.path),{recursive:!0}),await o(t.path,JSON.stringify(t.newTranslations,null,2)),n.info(a.green(`Updated: ${t.path}`)));return l.succeed(a.bold("Extraction complete!")),m}catch(t){throw l.fail(a.red("Extraction failed.")),t}}async function y(t,a,r,o,s=new u){try{let i=await n(t,"utf-8");for(const e of a.plugins||[])i=await(e.onLoad?.(i,t))??i;const c=await e(i,{syntax:"typescript",tsx:!0,comments:!0}),f=p(r);m(i,a.extract.functions||["t"],f,a),o.visit(c),(a.plugins||[]).length>0&&d(c,a.plugins||[],f,s)}catch(a){throw new l("Failed to process file",t,a)}}function d(t,a,e,r=new u){if(t&&"object"==typeof t){for(const o of a)try{o.onVisitNode?.(t,e)}catch(t){r.warn(`Plugin ${o.name} onVisitNode failed:`,t)}for(const o of Object.keys(t)){const n=t[o];if(Array.isArray(n))for(const t of n)t&&"object"==typeof t&&d(t,a,e,r);else n&&"object"==typeof n&&d(n,a,e,r)}}}async function x(t){t.extract.primaryLanguage||(t.extract.primaryLanguage=t.locales[0]),t.extract.secondaryLanguages||(t.extract.secondaryLanguages=t.locales.filter(a=>a!==t?.extract?.primaryLanguage)),t.extract.functions||(t.extract.functions=["t"]),t.extract.transComponents||(t.extract.transComponents=["Trans"]);const{allKeys:a,objectKeys:e}=await i(t);return c(a,e,t)}export{x as extract,y as processFile,g as runExtractor};
@@ -1 +1 @@
1
- import{glob as o}from"glob";import{processFile as t}from"./extractor.js";import{ConsoleLogger as n}from"../../utils/logger.js";import{initializePlugins as r}from"../plugin-manager.js";async function a(a,i=new n){const s=await async function(t){return await o(t.extract.input,{ignore:"node_modules/**",cwd:process.cwd()})}(a),e=new Map;await r(a.plugins||[]);for(const o of s)await t(o,a,i,e);for(const o of a.plugins||[])await(o.onEnd?.(e));return e}export{a as findKeys};
1
+ import{glob as o}from"glob";import{processFile as t}from"./extractor.js";import{ConsoleLogger as r}from"../../utils/logger.js";import{initializePlugins as n,createPluginContext as s}from"../plugin-manager.js";import{ASTVisitors as a}from"../parsers/ast-visitors.js";async function e(e,i=new r){const c=await async function(t){return await o(t.extract.input,{ignore:"node_modules/**",cwd:process.cwd()})}(e),p=new Map,m=new a(e,s(p),i);await n(e.plugins||[]);for(const o of c)await t(o,e,p,m,i);for(const o of e.plugins||[])await(o.onEnd?.(p));return{allKeys:p,objectKeys:m.objectKeys}}export{e as findKeys};
@@ -1 +1 @@
1
- import{readFile as t}from"node:fs/promises";import{resolve as e}from"node:path";import{getNestedKeys as a,getNestedValue as r,setNestedValue as o}from"../../utils/nested-object.js";import{getOutputPath as s}from"../../utils/file-utils.js";function n(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}async function c(c,i){const p=i.extract.defaultNS??"translation",l=i.extract.keySeparator??".",f=(i.extract.preservePatterns??[]).map(n);i.extract.primaryLanguage||(i.extract.primaryLanguage=i.locales[0]||"en"),i.extract.secondaryLanguages||(i.extract.secondaryLanguages=i.locales.filter(t=>t!==i.extract.primaryLanguage));const u=new Map;for(const t of c.values()){const e=t.ns||p;u.has(e)||u.set(e,[]),u.get(e).push(t)}const g=[];for(const n of i.locales)for(const[c,p]of u.entries()){const u=s(i.extract.output,n,c),m=e(process.cwd(),u);let x="",y={};try{x=await t(m,"utf-8"),y=JSON.parse(x)}catch(t){}const d={},h=a(y,l);for(const t of h)if(f.some(e=>e.test(t))){const e=r(y,t,l);o(d,t,e,l)}const L=!1===i.extract.sort?p:p.sort((t,e)=>t.key.localeCompare(e.key));for(const{key:t,defaultValue:e}of L){const a=r(y,t,l)??(n===i.extract?.primaryLanguage?e:"");o(d,t,a,l)}const w=i.extract.indentation??2,k=JSON.stringify(d,null,w);g.push({path:m,updated:k!==x,newTranslations:d,existingTranslations:y})}return g}export{c as getTranslations};
1
+ import{readFile as t}from"node:fs/promises";import{resolve as e}from"node:path";import{getNestedKeys as a,getNestedValue as o,setNestedValue as r}from"../../utils/nested-object.js";import{getOutputPath as s}from"../../utils/file-utils.js";function n(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}async function c(c,i,p){const f=p.extract.defaultNS??"translation",l=p.extract.keySeparator??".",u=[...p.extract.preservePatterns||[]];for(const t of i)u.push(`${t}.*`);const g=u.map(n);p.extract.primaryLanguage||(p.extract.primaryLanguage=p.locales[0]||"en"),p.extract.secondaryLanguages||(p.extract.secondaryLanguages=p.locales.filter(t=>t!==p.extract.primaryLanguage));const m=new Map;for(const t of c.values()){const e=t.ns||f;m.has(e)||m.set(e,[]),m.get(e).push(t)}const x=[];for(const n of p.locales)for(const[c,i]of m.entries()){const f=s(p.extract.output,n,c),u=e(process.cwd(),f);let m="",y={};try{m=await t(u,"utf-8"),y=JSON.parse(m)}catch(t){}const d={},h=a(y,l);for(const t of h)if(g.some(e=>e.test(t))){const e=o(y,t,l);r(d,t,e,l)}const L=!1===p.extract.sort?i:i.sort((t,e)=>t.key.localeCompare(e.key));for(const{key:t,defaultValue:e}of L){const a=o(y,t,l)??(n===p.extract?.primaryLanguage?e:"");r(d,t,a,l)}const w=p.extract.indentation??2,$=JSON.stringify(d,null,w);x.push({path:u,updated:$!==m,newTranslations:d,existingTranslations:y})}return x}export{c as getTranslations};
@@ -1 +1 @@
1
- import{extractFromTransComponent as e}from"./jsx-parser.js";class t{pluginContext;config;logger;scopeStack=[];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&&e.type&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){if("CallExpression"!==e.init?.type)return;const t=e.init.callee;"Identifier"===t.type&&(this.config.extract.useTranslationNames||["useTranslation","getT","useT"]).indexOf(t.value)>-1?this.handleUseTranslationDeclarator(e):"MemberExpression"===t.type&&"Identifier"===t.property.type&&"getFixedT"===t.property.value&&this.handleGetFixedTDeclarator(e)}handleUseTranslationDeclarator(e){if(!e.init||"CallExpression"!==e.init.type)return;let t;if("ArrayPattern"===e.id.type){const n=e.id.elements[0];"Identifier"===n?.type&&(t=n.value)}if("ObjectPattern"===e.id.type)for(const n of e.id.properties){if("AssignmentPatternProperty"===n.type&&"Identifier"===n.key.type&&"t"===n.key.value){t="t";break}if("KeyValuePatternProperty"===n.type&&"Identifier"===n.key.type&&"t"===n.key.value&&"Identifier"===n.value.type){t=n.value.value;break}}if(!t)return;const n=e.init.arguments?.[0]?.expression;let i;"StringLiteral"===n?.type?i=n.value:"ArrayExpression"===n?.type&&"StringLiteral"===n.elements[0]?.expression.type&&(i=n.elements[0].expression.value);const r=e.init.arguments?.[1]?.expression;let s;"ObjectExpression"===r?.type&&(s=this.getObjectPropValue(r,"keyPrefix")),this.setVarInScope(t,{defaultNs:i,keyPrefix:s})}handleGetFixedTDeclarator(e){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const t=e.id.value,n=e.init.arguments,i=n[1]?.expression,r=n[2]?.expression,s="StringLiteral"===i?.type?i.value:void 0,a="StringLiteral"===r?.type?r.value:void 0;(s||a)&&this.setVarInScope(t,{defaultNs:s,keyPrefix:a})}handleCallExpression(e){const t=e.callee;if("Identifier"!==t.type)return;const n=this.getVarFromScope(t.value);if(!((this.config.extract.functions||[]).includes(t.value)||void 0!==n)||0===e.arguments.length)return;const i=e.arguments[0].expression,r=[];if("StringLiteral"===i.type)r.push(i.value);else if("ArrowFunctionExpression"===i.type){const e=this.extractKeyFromSelector(i);e&&r.push(e)}else if("ArrayExpression"===i.type)for(const e of i.elements)"StringLiteral"===e?.expression.type&&r.push(e.expression.value);if(0===r.length)return;const s=e.arguments.length>1?e.arguments[1].expression:void 0,a=this.getDefaultValue(e,r[r.length-1]);for(let e=0;e<r.length;e++){let t,i=r[e];"ObjectExpression"===s?.type&&(t=this.getObjectPropValue(s,"ns")),!t&&n?.defaultNs&&(t=n.defaultNs);const o=this.config.extract.nsSeparator??":";if(!t&&o&&i.includes(o)){const e=i.split(o);t=e.shift(),i=e.join(o)}t||(t=this.config.extract.defaultNS);let l=i;if(n?.keyPrefix){const e=this.config.extract.keySeparator??".";l=`${n.keyPrefix}${e}${i}`}const p=e===r.length-1?a:i;if("ObjectExpression"===s?.type){const e=this.getObjectPropValue(s,"context"),n=this.config.extract.contextSeparator??"_";if(e){this.pluginContext.addKey({key:`${l}${n}${e}`,ns:t,defaultValue:p});continue}if(void 0!==this.getObjectPropValue(s,"count")){this.handlePluralKeys(l,p,t);continue}}this.pluginContext.addKey({key:l,ns:t,defaultValue:p})}}handlePluralKeys(e,t,n){try{const i=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,r=this.config.extract.pluralSeparator??"_";for(const s of i)this.pluginContext.addKey({key:`${e}${r}${s}`,ns:n,defaultValue:t,hasCount:!0})}catch(i){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,defaultValue:t,ns:n})}}getDefaultValue(e,t){if(e.arguments.length<=1)return t;const n=e.arguments[1].expression;return"StringLiteral"===n.type?n.value||t:"ObjectExpression"===n.type&&this.getObjectPropValue(n,"defaultValue")||t}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e(t,this.config);if(n){if(!n.ns){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===e?.type&&"JSXExpressionContainer"===e.value?.type&&"Identifier"===e.value.expression.type){const t=e.value.expression.value,i=this.getVarFromScope(t);i?.defaultNs&&(n.ns=i.defaultNs)}}n.ns||(n.ns=this.config.extract.defaultNS),n.hasCount?this.handlePluralKeys(n.key,n.defaultValue,n.ns):this.pluginContext.addKey(n)}}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}getObjectPropValue(e,t){const n=e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t));if("KeyValueProperty"===n?.type){const e=n.value;return"StringLiteral"===e.type?e.value:""}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const i=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)i.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;i.unshift(e.expression.value)}n=n.object}if(i.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return i.join(t)}return null}}export{t as ASTVisitors};
1
+ import{extractFromTransComponent as e}from"./jsx-parser.js";class t{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&e.type&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){if("CallExpression"!==e.init?.type)return;const t=e.init.callee;"Identifier"===t.type&&(this.config.extract.useTranslationNames||["useTranslation","getT","useT"]).indexOf(t.value)>-1?this.handleUseTranslationDeclarator(e):"MemberExpression"===t.type&&"Identifier"===t.property.type&&"getFixedT"===t.property.value&&this.handleGetFixedTDeclarator(e)}handleUseTranslationDeclarator(e){if(!e.init||"CallExpression"!==e.init.type)return;let t;if("ArrayPattern"===e.id.type){const n=e.id.elements[0];"Identifier"===n?.type&&(t=n.value)}if("ObjectPattern"===e.id.type)for(const n of e.id.properties){if("AssignmentPatternProperty"===n.type&&"Identifier"===n.key.type&&"t"===n.key.value){t="t";break}if("KeyValuePatternProperty"===n.type&&"Identifier"===n.key.type&&"t"===n.key.value&&"Identifier"===n.value.type){t=n.value.value;break}}if(!t)return;const n=e.init.arguments?.[0]?.expression;let i;"StringLiteral"===n?.type?i=n.value:"ArrayExpression"===n?.type&&"StringLiteral"===n.elements[0]?.expression.type&&(i=n.elements[0].expression.value);const r=e.init.arguments?.[1]?.expression;let s;if("ObjectExpression"===r?.type){const e=this.getObjectPropValue(r,"keyPrefix");s="string"==typeof e?e:void 0}this.setVarInScope(t,{defaultNs:i,keyPrefix:s})}handleGetFixedTDeclarator(e){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const t=e.id.value,n=e.init.arguments,i=n[1]?.expression,r=n[2]?.expression,s="StringLiteral"===i?.type?i.value:void 0,a="StringLiteral"===r?.type?r.value:void 0;(s||a)&&this.setVarInScope(t,{defaultNs:s,keyPrefix:a})}handleCallExpression(e){const t=e.callee;if("Identifier"!==t.type)return;const n=this.getVarFromScope(t.value);if(!((this.config.extract.functions||[]).includes(t.value)||void 0!==n)||0===e.arguments.length)return;const i=e.arguments[0].expression,r=[];if("StringLiteral"===i.type)r.push(i.value);else if("ArrowFunctionExpression"===i.type){const e=this.extractKeyFromSelector(i);e&&r.push(e)}else if("ArrayExpression"===i.type)for(const e of i.elements)"StringLiteral"===e?.expression.type&&r.push(e.expression.value);if(0===r.length)return;let s,a;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?a=t:"StringLiteral"===t.type&&(s=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(a=t)}const o=a?this.getObjectPropValue(a,"defaultValue"):void 0,l="string"==typeof o?o:s;for(let e=0;e<r.length;e++){let t,i=r[e];if("ObjectExpression"===a?.type){const e=this.getObjectPropValue(a,"ns");"string"==typeof e&&(t=e)}!t&&n?.defaultNs&&(t=n.defaultNs);const s=this.config.extract.nsSeparator??":";if(!t&&s&&i.includes(s)){const e=i.split(s);t=e.shift(),i=e.join(s)}t||(t=this.config.extract.defaultNS);let o=i;if(n?.keyPrefix){const e=this.config.extract.keySeparator??".";o=`${n.keyPrefix}${e}${i}`}const p=e===r.length-1&&l||i;if("ObjectExpression"===a?.type){const e=this.getObjectProperty(a,"context");if("ConditionalExpression"===e?.value?.type){const n=this.resolvePossibleStringValues(e.value),i=this.config.extract.contextSeparator??"_";if(n.length>0){n.forEach(e=>{this.pluginContext.addKey({key:`${o}${i}${e}`,ns:t,defaultValue:p})}),this.pluginContext.addKey({key:o,ns:t,defaultValue:p});continue}}const n=this.getObjectPropValue(a,"context");if("string"==typeof n&&n){const e=this.config.extract.contextSeparator??"_";this.pluginContext.addKey({key:`${o}${e}${n}`,ns:t,defaultValue:p});continue}if(void 0!==this.getObjectPropValue(a,"count")){this.handlePluralKeys(o,p,t);continue}!0===this.getObjectPropValue(a,"returnObjects")&&this.objectKeys.add(o)}this.pluginContext.addKey({key:o,ns:t,defaultValue:p})}}handlePluralKeys(e,t,n){try{const i=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,r=this.config.extract.pluralSeparator??"_";for(const s of i)this.pluginContext.addKey({key:`${e}${r}${s}`,ns:n,defaultValue:t,hasCount:!0})}catch(i){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,defaultValue:t,ns:n})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e(t,this.config);if(n){if(!n.ns){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===e?.type&&"JSXExpressionContainer"===e.value?.type&&"Identifier"===e.value.expression.type){const t=e.value.expression.value,i=this.getVarFromScope(t);i?.defaultNs&&(n.ns=i.defaultNs)}}if(n.ns||(n.ns=this.config.extract.defaultNS),n.contextExpression){const e=this.resolvePossibleStringValues(n.contextExpression),t=this.config.extract.contextSeparator??"_";if(e.length>0){for(const i of e)this.pluginContext.addKey({key:`${n.key}${t}${i}`,ns:n.ns,defaultValue:n.defaultValue});this.pluginContext.addKey(n)}}else n.hasCount?this.handlePluralKeys(n.key,n.defaultValue,n.ns):this.pluginContext.addKey(n)}}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}getObjectPropValue(e,t){const n=e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t));if("KeyValueProperty"===n?.type){const e=n.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const i=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)i.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;i.unshift(e.expression.value)}n=n.object}if(i.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return i.join(t)}return null}resolvePossibleStringValues(e){if("StringLiteral"===e.type)return[e.value];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent),...this.resolvePossibleStringValues(e.alternate)]}return"Identifier"===e.type&&e.value,[]}getObjectProperty(e,t){return e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t))}}export{t as ASTVisitors};
@@ -1 +1 @@
1
- function e(e,n){const i=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),r=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),a=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),l=!!a;let u;if(u="JSXAttribute"===i?.type&&"StringLiteral"===i.value?.type?i.value.value:t(e.children,n),!u)return null;const p=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value),s="JSXAttribute"===p?.type&&"StringLiteral"===p.value?.type?p.value.value:void 0;let o=n.extract.defaultValue||"";return o="JSXAttribute"===r?.type&&"StringLiteral"===r.value?.type?r.value.value:t(e.children,n),{key:u,ns:s,defaultValue:o||u,hasCount:l}}function t(e,t){const n=new Set(t.extract.transKeepBasicHtmlNodesFor??["br","strong","i","p"]);return function e(t){let i="";return t.forEach((t,r)=>{if("JSXText"===t.type)i+=t.value;else if("JSXExpressionContainer"===t.type){const e=t.expression;if("StringLiteral"===e.type)i+=e.value;else if("Identifier"===e.type)i+=`{{${e.value}}}`;else if("ObjectExpression"===e.type){const t=e.properties[0];t&&"Identifier"===t.type&&(i+=`{{${t.value}}}`)}}else if("JSXElement"===t.type){let a;"Identifier"===t.opening.name.type&&(a=t.opening.name.value);const l=e(t.children);a&&n.has(a)?i+=`<${a}>${l}</${a}>`:i+=`<${r}>${l}</${r}>`}else"JSXFragment"===t.type&&(i+=e(t.children))}),i}(e).trim().replace(/\s{2,}/g," ")}export{e as extractFromTransComponent};
1
+ function e(e,n){const i=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),r=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),a=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),u=!!a,l=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value),p="JSXAttribute"===l?.type&&"JSXExpressionContainer"===l.value?.type?l.value.expression:void 0;let s;if(s="JSXAttribute"===i?.type&&"StringLiteral"===i.value?.type?i.value.value:t(e.children,n),!s)return null;const o=e.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value),y="JSXAttribute"===o?.type&&"StringLiteral"===o.value?.type?o.value.value:void 0;let f=n.extract.defaultValue||"";return f="JSXAttribute"===r?.type&&"StringLiteral"===r.value?.type?r.value.value:t(e.children,n),{key:s,ns:y,defaultValue:f||s,hasCount:u,contextExpression:p}}function t(e,t){const n=new Set(t.extract.transKeepBasicHtmlNodesFor??["br","strong","i","p"]);return function e(t){let i="";return t.forEach((t,r)=>{if("JSXText"===t.type)i+=t.value;else if("JSXExpressionContainer"===t.type){const e=t.expression;if("StringLiteral"===e.type)i+=e.value;else if("Identifier"===e.type)i+=`{{${e.value}}}`;else if("ObjectExpression"===e.type){const t=e.properties[0];t&&"Identifier"===t.type&&(i+=`{{${t.value}}}`)}}else if("JSXElement"===t.type){let a;"Identifier"===t.opening.name.type&&(a=t.opening.name.value);const u=e(t.children);a&&n.has(a)?i+=`<${a}>${u}</${a}>`:i+=`<${r}>${u}</${r}>`}else"JSXFragment"===t.type&&(i+=e(t.children))}),i}(e).trim().replace(/\s{2,}/g," ")}export{e as extractFromTransComponent};
@@ -1 +1 @@
1
- import o from"chalk";import e from"ora";import{resolve as t}from"node:path";import{readFile as a}from"node:fs/promises";import{findKeys as s}from"./extractor/core/key-finder.js";import{getNestedValue as n}from"./utils/nested-object.js";import{getOutputPath as r}from"./utils/file-utils.js";async function l(l,d={}){l.extract.primaryLanguage||(l.extract.primaryLanguage=l.locales[0]||"en"),l.extract.secondaryLanguages||(l.extract.secondaryLanguages=l.locales.filter(o=>o!==l?.extract?.primaryLanguage));const g=e("Analyzing project localization status...\n").start();try{const e=await async function(o){const e=await s(o),{primaryLanguage:l,keySeparator:c=".",defaultNS:i="translation"}=o.extract,y=o.locales.filter(o=>o!==l),d=new Map;for(const o of e.values()){const e=o.ns||i;d.has(e)||d.set(e,[]),d.get(e).push(o)}const g={totalKeys:e.size,keysByNs:d,locales:new Map};for(const e of y){let s=0;const l=new Map;for(const[i,y]of d.entries()){const d=r(o.extract.output,e,i);let g={};try{const o=await a(t(process.cwd(),d),"utf-8");g=JSON.parse(o)}catch{}let u=0;const f=y.map(({key:o})=>{const e=!!n(g,o,c??".");return e&&u++,{key:o,isTranslated:e}});l.set(i,{totalKeys:y.length,translatedKeys:u,keyDetails:f}),s+=u}g.locales.set(e,{totalTranslated:s,namespaces:l})}return g}(l);g.succeed("Analysis complete."),function(e,t,a){a.detail?function(e,t,a,s){if(a===t.extract.primaryLanguage)return void console.log(o.yellow(`Locale "${a}" is the primary language. All keys are considered present.`));if(!t.locales.includes(a))return void console.error(o.red(`Error: Locale "${a}" is not defined in your configuration.`));const n=e.locales.get(a);if(!n)return void console.error(o.red(`Error: Locale "${a}" is not a valid secondary language.`));console.log(o.bold(`\nKey Status for "${o.cyan(a)}":`));const r=Array.from(e.keysByNs.values()).flat().length;c("Overall",n.totalTranslated,r);const l=s?[s]:Array.from(n.namespaces.keys()).sort();for(const e of l){const t=n.namespaces.get(e);t&&(console.log(o.cyan.bold(`\nNamespace: ${e}`)),c("Namespace Progress",t.translatedKeys,t.totalKeys),t.keyDetails.forEach(({key:e,isTranslated:t})=>{const a=t?o.green("✓"):o.red("✗");console.log(` ${a} ${e}`)}))}const i=r-n.totalTranslated;i>0?console.log(o.yellow.bold(`\nSummary: Found ${i} missing translations for "${a}".`)):console.log(o.green.bold(`\nSummary: 🎉 All keys are translated for "${a}".`));y()}(e,t,a.detail,a.namespace):a.namespace?function(e,t,a){const s=e.keysByNs.get(a);if(!s)return void console.error(o.red(`Error: Namespace "${a}" was not found in your source code.`));console.log(o.cyan.bold(`\nStatus for Namespace: "${a}"`)),console.log("------------------------");for(const[o,t]of e.locales.entries()){const e=t.namespaces.get(a);if(e){const t=e.totalKeys>0?Math.round(e.translatedKeys/e.totalKeys*100):100,a=i(t);console.log(`- ${o}: ${a} ${t}% (${e.translatedKeys}/${e.totalKeys} keys)`)}}y()}(e,0,a.namespace):function(e,t){const{primaryLanguage:a}=t.extract;console.log(o.cyan.bold("\ni18next Project Status")),console.log("------------------------"),console.log(`🔑 Keys Found: ${o.bold(e.totalKeys)}`),console.log(`📚 Namespaces Found: ${o.bold(e.keysByNs.size)}`),console.log(`🌍 Locales: ${o.bold(t.locales.join(", "))}`),console.log(`✅ Primary Language: ${o.bold(a)}`),console.log("\nTranslation Progress:");for(const[o,t]of e.locales.entries()){const a=e.totalKeys>0?Math.round(t.totalTranslated/e.totalKeys*100):100,s=i(a);console.log(`- ${o}: ${s} ${a}% (${t.totalTranslated}/${e.totalKeys} keys)`)}y()}(e,t)}(e,l,d)}catch(o){g.fail("Failed to generate status report."),console.error(o)}}function c(e,t,a){const s=a>0?Math.round(t/a*100):100,n=i(s);console.log(`${o.bold(e)}: ${n} ${s}% (${t}/${a})`)}function i(e){const t=Math.round(e/100*20),a=20-t;return`[${o.green("".padStart(t,"■"))}${"".padStart(a,"□")}]`}function y(){console.log(o.yellow.bold("\n✨ Take your localization to the next level!")),console.log("Manage translations with your team in the cloud with locize => https://www.locize.com/docs/getting-started"),console.log(`Run ${o.cyan("npx i18next-cli locize-migrate")} to get started.`)}export{l as runStatus};
1
+ import o from"chalk";import e from"ora";import{resolve as t}from"node:path";import{readFile as a}from"node:fs/promises";import{findKeys as s}from"./extractor/core/key-finder.js";import{getNestedValue as n}from"./utils/nested-object.js";import{getOutputPath as r}from"./utils/file-utils.js";async function l(l,d={}){l.extract.primaryLanguage||(l.extract.primaryLanguage=l.locales[0]||"en"),l.extract.secondaryLanguages||(l.extract.secondaryLanguages=l.locales.filter(o=>o!==l?.extract?.primaryLanguage));const g=e("Analyzing project localization status...\n").start();try{const e=await async function(o){const{allKeys:e}=await s(o),{primaryLanguage:l,keySeparator:c=".",defaultNS:i="translation"}=o.extract,y=o.locales.filter(o=>o!==l),d=new Map;for(const o of e.values()){const e=o.ns||i;d.has(e)||d.set(e,[]),d.get(e).push(o)}const g={totalKeys:e.size,keysByNs:d,locales:new Map};for(const e of y){let s=0;const l=new Map;for(const[i,y]of d.entries()){const d=r(o.extract.output,e,i);let g={};try{const o=await a(t(process.cwd(),d),"utf-8");g=JSON.parse(o)}catch{}let u=0;const f=y.map(({key:o})=>{const e=!!n(g,o,c??".");return e&&u++,{key:o,isTranslated:e}});l.set(i,{totalKeys:y.length,translatedKeys:u,keyDetails:f}),s+=u}g.locales.set(e,{totalTranslated:s,namespaces:l})}return g}(l);g.succeed("Analysis complete."),function(e,t,a){a.detail?function(e,t,a,s){if(a===t.extract.primaryLanguage)return void console.log(o.yellow(`Locale "${a}" is the primary language. All keys are considered present.`));if(!t.locales.includes(a))return void console.error(o.red(`Error: Locale "${a}" is not defined in your configuration.`));const n=e.locales.get(a);if(!n)return void console.error(o.red(`Error: Locale "${a}" is not a valid secondary language.`));console.log(o.bold(`\nKey Status for "${o.cyan(a)}":`));const r=Array.from(e.keysByNs.values()).flat().length;c("Overall",n.totalTranslated,r);const l=s?[s]:Array.from(n.namespaces.keys()).sort();for(const e of l){const t=n.namespaces.get(e);t&&(console.log(o.cyan.bold(`\nNamespace: ${e}`)),c("Namespace Progress",t.translatedKeys,t.totalKeys),t.keyDetails.forEach(({key:e,isTranslated:t})=>{const a=t?o.green("✓"):o.red("✗");console.log(` ${a} ${e}`)}))}const i=r-n.totalTranslated;i>0?console.log(o.yellow.bold(`\nSummary: Found ${i} missing translations for "${a}".`)):console.log(o.green.bold(`\nSummary: 🎉 All keys are translated for "${a}".`));y()}(e,t,a.detail,a.namespace):a.namespace?function(e,t,a){const s=e.keysByNs.get(a);if(!s)return void console.error(o.red(`Error: Namespace "${a}" was not found in your source code.`));console.log(o.cyan.bold(`\nStatus for Namespace: "${a}"`)),console.log("------------------------");for(const[o,t]of e.locales.entries()){const e=t.namespaces.get(a);if(e){const t=e.totalKeys>0?Math.round(e.translatedKeys/e.totalKeys*100):100,a=i(t);console.log(`- ${o}: ${a} ${t}% (${e.translatedKeys}/${e.totalKeys} keys)`)}}y()}(e,0,a.namespace):function(e,t){const{primaryLanguage:a}=t.extract;console.log(o.cyan.bold("\ni18next Project Status")),console.log("------------------------"),console.log(`🔑 Keys Found: ${o.bold(e.totalKeys)}`),console.log(`📚 Namespaces Found: ${o.bold(e.keysByNs.size)}`),console.log(`🌍 Locales: ${o.bold(t.locales.join(", "))}`),console.log(`✅ Primary Language: ${o.bold(a)}`),console.log("\nTranslation Progress:");for(const[o,t]of e.locales.entries()){const a=e.totalKeys>0?Math.round(t.totalTranslated/e.totalKeys*100):100,s=i(a);console.log(`- ${o}: ${s} ${a}% (${t.totalTranslated}/${e.totalKeys} keys)`)}y()}(e,t)}(e,l,d)}catch(o){g.fail("Failed to generate status report."),console.error(o)}}function c(e,t,a){const s=a>0?Math.round(t/a*100):100,n=i(s);console.log(`${o.bold(e)}: ${n} ${s}% (${t}/${a})`)}function i(e){const t=Math.round(e/100*20),a=20-t;return`[${o.green("".padStart(t,"■"))}${"".padStart(a,"□")}]`}function y(){console.log(o.yellow.bold("\n✨ Take your localization to the next level!")),console.log("Manage translations with your team in the cloud with locize => https://www.locize.com/docs/getting-started"),console.log(`Run ${o.cyan("npx i18next-cli locize-migrate")} to get started.`)}export{l as runStatus};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
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('0.9.7')
24
+ .version('0.9.9')
25
25
 
26
26
  program
27
27
  .command('extract')
package/src/config.ts CHANGED
@@ -4,8 +4,9 @@ import { access } from 'node:fs/promises'
4
4
  import { createJiti } from 'jiti'
5
5
  import inquirer from 'inquirer'
6
6
  import chalk from 'chalk'
7
- import type { I18nextToolkitConfig } from './types'
7
+ import type { I18nextToolkitConfig, Logger } from './types'
8
8
  import { runInit } from './init'
9
+ import { ConsoleLogger } from './utils/logger'
9
10
 
10
11
  /**
11
12
  * List of supported configuration file names in order of precedence
@@ -78,7 +79,7 @@ async function findConfigFile (): Promise<string | null> {
78
79
  * }
79
80
  * ```
80
81
  */
81
- export async function loadConfig (): Promise<I18nextToolkitConfig | null> {
82
+ export async function loadConfig (logger: Logger = new ConsoleLogger()): Promise<I18nextToolkitConfig | null> {
82
83
  const configPath = await findConfigFile()
83
84
 
84
85
  if (!configPath) {
@@ -101,7 +102,7 @@ export async function loadConfig (): Promise<I18nextToolkitConfig | null> {
101
102
  }
102
103
 
103
104
  if (!config) {
104
- console.error(`Error: No default export found in ${configPath}`)
105
+ logger.error(`Error: No default export found in ${configPath}`)
105
106
  return null
106
107
  }
107
108
 
@@ -112,8 +113,8 @@ export async function loadConfig (): Promise<I18nextToolkitConfig | null> {
112
113
 
113
114
  return config
114
115
  } catch (error) {
115
- console.error(`Error loading configuration from ${configPath}`)
116
- console.error(error)
116
+ logger.error(`Error loading configuration from ${configPath}`)
117
+ logger.error(error)
117
118
  return null
118
119
  }
119
120
  }
@@ -125,7 +126,7 @@ export async function loadConfig (): Promise<I18nextToolkitConfig | null> {
125
126
  * @returns A promise that resolves to a valid configuration object.
126
127
  * @throws Exits the process if the user declines to create a config or if loading fails after creation.
127
128
  */
128
- export async function ensureConfig (): Promise<I18nextToolkitConfig> {
129
+ export async function ensureConfig (logger: Logger = new ConsoleLogger()): Promise<I18nextToolkitConfig> {
129
130
  let config = await loadConfig()
130
131
 
131
132
  if (config) {
@@ -142,17 +143,17 @@ export async function ensureConfig (): Promise<I18nextToolkitConfig> {
142
143
 
143
144
  if (shouldInit) {
144
145
  await runInit() // Run the interactive setup wizard
145
- console.log(chalk.green('Configuration created. Resuming command...'))
146
+ logger.info(chalk.green('Configuration created. Resuming command...'))
146
147
  config = await loadConfig() // Try loading the newly created config
147
148
 
148
149
  if (config) {
149
150
  return config
150
151
  } else {
151
- console.error(chalk.red('Error: Failed to load configuration after creation. Please try running the command again.'))
152
+ logger.error(chalk.red('Error: Failed to load configuration after creation. Please try running the command again.'))
152
153
  process.exit(1)
153
154
  }
154
155
  } else {
155
- console.log('Operation cancelled. Please create a configuration file to proceed.')
156
+ logger.info('Operation cancelled. Please create a configuration file to proceed.')
156
157
  process.exit(0)
157
158
  }
158
159
  }
@@ -50,10 +50,10 @@ export async function runExtractor (
50
50
  const spinner = ora('Running i18next key extractor...\n').start()
51
51
 
52
52
  try {
53
- const allKeys = await findKeys(config, logger)
53
+ const { allKeys, objectKeys } = await findKeys(config, logger)
54
54
  spinner.text = `Found ${allKeys.size} unique keys. Updating translation files...`
55
55
 
56
- const results = await getTranslations(allKeys, config)
56
+ const results = await getTranslations(allKeys, objectKeys, config)
57
57
 
58
58
  let anyFileUpdated = false
59
59
  for (const result of results) {
@@ -97,8 +97,9 @@ export async function runExtractor (
97
97
  export async function processFile (
98
98
  file: string,
99
99
  config: I18nextToolkitConfig,
100
- logger: Logger,
101
- allKeys: Map<string, ExtractedKey>
100
+ allKeys: Map<string, ExtractedKey>,
101
+ astVisitors: ASTVisitors,
102
+ logger: Logger = new ConsoleLogger()
102
103
  ): Promise<void> {
103
104
  try {
104
105
  let code = await readFile(file, 'utf-8')
@@ -119,18 +120,11 @@ export async function processFile (
119
120
  // Extract keys from comments
120
121
  extractKeysFromComments(code, config.extract.functions || ['t'], pluginContext, config)
121
122
 
122
- // Extract keys from AST using visitors
123
- const astVisitors = new ASTVisitors(
124
- config,
125
- pluginContext,
126
- logger
127
- )
128
-
129
123
  astVisitors.visit(ast)
130
124
 
131
125
  // Run plugin visitors
132
126
  if ((config.plugins || []).length > 0) {
133
- traverseEveryNode(ast, (config.plugins || []), pluginContext)
127
+ traverseEveryNode(ast, (config.plugins || []), pluginContext, logger)
134
128
  }
135
129
  } catch (error) {
136
130
  throw new ExtractorError('Failed to process file', file, error as Error)
@@ -146,7 +140,7 @@ export async function processFile (
146
140
  *
147
141
  * @internal
148
142
  */
149
- function traverseEveryNode (node: any, plugins: any[], pluginContext: PluginContext): void {
143
+ function traverseEveryNode (node: any, plugins: any[], pluginContext: PluginContext, logger: Logger = new ConsoleLogger()): void {
150
144
  if (!node || typeof node !== 'object') return
151
145
 
152
146
  // Call plugins for this node
@@ -154,7 +148,7 @@ function traverseEveryNode (node: any, plugins: any[], pluginContext: PluginCont
154
148
  try {
155
149
  plugin.onVisitNode?.(node, pluginContext)
156
150
  } catch (err) {
157
- console.warn(`Plugin ${plugin.name} onVisitNode failed:`, err)
151
+ logger.warn(`Plugin ${plugin.name} onVisitNode failed:`, err)
158
152
  }
159
153
  }
160
154
 
@@ -162,10 +156,10 @@ function traverseEveryNode (node: any, plugins: any[], pluginContext: PluginCont
162
156
  const child = node[key]
163
157
  if (Array.isArray(child)) {
164
158
  for (const c of child) {
165
- if (c && typeof c === 'object') traverseEveryNode(c, plugins, pluginContext)
159
+ if (c && typeof c === 'object') traverseEveryNode(c, plugins, pluginContext, logger)
166
160
  }
167
161
  } else if (child && typeof child === 'object') {
168
- traverseEveryNode(child, plugins, pluginContext)
162
+ traverseEveryNode(child, plugins, pluginContext, logger)
169
163
  }
170
164
  }
171
165
  }
@@ -190,6 +184,6 @@ export async function extract (config: I18nextToolkitConfig) {
190
184
  if (!config.extract.secondaryLanguages) config.extract.secondaryLanguages = config.locales.filter((l: string) => l !== config?.extract?.primaryLanguage)
191
185
  if (!config.extract.functions) config.extract.functions = ['t']
192
186
  if (!config.extract.transComponents) config.extract.transComponents = ['Trans']
193
- const allKeys = await findKeys(config)
194
- return getTranslations(allKeys, config)
187
+ const { allKeys, objectKeys } = await findKeys(config)
188
+ return getTranslations(allKeys, objectKeys, config)
195
189
  }
@@ -2,7 +2,8 @@ import { glob } from 'glob'
2
2
  import type { ExtractedKey, Logger, I18nextToolkitConfig } from '../../types'
3
3
  import { processFile } from './extractor'
4
4
  import { ConsoleLogger } from '../../utils/logger'
5
- import { initializePlugins } from '../plugin-manager'
5
+ import { initializePlugins, createPluginContext } from '../plugin-manager'
6
+ import { ASTVisitors } from '../parsers/ast-visitors'
6
7
 
7
8
  /**
8
9
  * Main function for finding translation keys across all source files in a project.
@@ -35,14 +36,17 @@ import { initializePlugins } from '../plugin-manager'
35
36
  export async function findKeys (
36
37
  config: I18nextToolkitConfig,
37
38
  logger: Logger = new ConsoleLogger()
38
- ): Promise<Map<string, ExtractedKey>> {
39
+ ): Promise<{ allKeys: Map<string, ExtractedKey>, objectKeys: Set<string> }> {
39
40
  const sourceFiles = await processSourceFiles(config)
40
41
  const allKeys = new Map<string, ExtractedKey>()
41
42
 
43
+ // Create a single visitors instance to accumulate data across all files
44
+ const astVisitors = new ASTVisitors(config, createPluginContext(allKeys), logger)
45
+
42
46
  await initializePlugins(config.plugins || [])
43
47
 
44
48
  for (const file of sourceFiles) {
45
- await processFile(file, config, logger, allKeys)
49
+ await processFile(file, config, allKeys, astVisitors, logger)
46
50
  }
47
51
 
48
52
  // Run onEnd hooks
@@ -50,7 +54,7 @@ export async function findKeys (
50
54
  await plugin.onEnd?.(allKeys)
51
55
  }
52
56
 
53
- return allKeys
57
+ return { allKeys, objectKeys: astVisitors.objectKeys }
54
58
  }
55
59
 
56
60
  /**
@@ -45,11 +45,17 @@ function globToRegex (glob: string): RegExp {
45
45
  */
46
46
  export async function getTranslations (
47
47
  keys: Map<string, ExtractedKey>,
48
+ objectKeys: Set<string>,
48
49
  config: I18nextToolkitConfig
49
50
  ): Promise<TranslationResult[]> {
50
51
  const defaultNS = config.extract.defaultNS ?? 'translation'
51
52
  const keySeparator = config.extract.keySeparator ?? '.'
52
- const preservePatterns = (config.extract.preservePatterns ?? []).map(globToRegex)
53
+ const patternsToPreserve = [...(config.extract.preservePatterns || [])]
54
+ for (const key of objectKeys) {
55
+ // Convert the object key to a glob pattern to preserve all its children
56
+ patternsToPreserve.push(`${key}.*`)
57
+ }
58
+ const preservePatterns = patternsToPreserve.map(globToRegex)
53
59
  if (!config.extract.primaryLanguage) config.extract.primaryLanguage = config.locales[0] || 'en'
54
60
  if (!config.extract.secondaryLanguages) config.extract.secondaryLanguages = config.locales.filter((l: string) => l !== config.extract.primaryLanguage)
55
61
 
@@ -1,4 +1,4 @@
1
- import type { Module, Node, CallExpression, VariableDeclarator, JSXElement, ArrowFunctionExpression, ObjectExpression } from '@swc/core'
1
+ import type { Module, Node, CallExpression, VariableDeclarator, JSXElement, ArrowFunctionExpression, ObjectExpression, Expression } from '@swc/core'
2
2
  import type { PluginContext, I18nextToolkitConfig, Logger } from '../../types'
3
3
  import { extractFromTransComponent } from './jsx-parser'
4
4
 
@@ -41,6 +41,8 @@ export class ASTVisitors {
41
41
  private readonly logger: Logger
42
42
  private scopeStack: Array<Map<string, ScopeInfo>> = []
43
43
 
44
+ public objectKeys = new Set<string>()
45
+
44
46
  /**
45
47
  * Creates a new AST visitor instance.
46
48
  *
@@ -275,7 +277,8 @@ export class ASTVisitors {
275
277
  const optionsArg = node.init.arguments?.[1]?.expression
276
278
  let keyPrefix: string | undefined
277
279
  if (optionsArg?.type === 'ObjectExpression') {
278
- keyPrefix = this.getObjectPropValue(optionsArg, 'keyPrefix')
280
+ const kp = this.getObjectPropValue(optionsArg, 'keyPrefix')
281
+ keyPrefix = typeof kp === 'string' ? kp : undefined
279
282
  }
280
283
 
281
284
  // Store the scope info for the declared variable
@@ -340,7 +343,6 @@ export class ASTVisitors {
340
343
  const firstArg = node.arguments[0].expression
341
344
  const keysToProcess: string[] = []
342
345
 
343
- // --- NEW: Handle ArrayExpression for key fallbacks ---
344
346
  if (firstArg.type === 'StringLiteral') {
345
347
  keysToProcess.push(firstArg.value)
346
348
  } else if (firstArg.type === 'ArrowFunctionExpression') {
@@ -357,8 +359,26 @@ export class ASTVisitors {
357
359
 
358
360
  if (keysToProcess.length === 0) return
359
361
 
360
- const options = node.arguments.length > 1 ? node.arguments[1].expression : undefined
361
- const defaultValue = this.getDefaultValue(node, keysToProcess[keysToProcess.length - 1])
362
+ let defaultValue: string | undefined
363
+ let options: ObjectExpression | undefined
364
+
365
+ if (node.arguments.length > 1) {
366
+ const arg2 = node.arguments[1].expression
367
+ if (arg2.type === 'ObjectExpression') {
368
+ options = arg2
369
+ } else if (arg2.type === 'StringLiteral') {
370
+ defaultValue = arg2.value
371
+ }
372
+ }
373
+ if (node.arguments.length > 2) {
374
+ const arg3 = node.arguments[2].expression
375
+ if (arg3.type === 'ObjectExpression') {
376
+ options = arg3
377
+ }
378
+ }
379
+
380
+ const defaultValueFromOptions = options ? this.getObjectPropValue(options, 'defaultValue') : undefined
381
+ const finalDefaultValue = (typeof defaultValueFromOptions === 'string' ? defaultValueFromOptions : defaultValue)
362
382
 
363
383
  // Loop through each key found (could be one or more) and process it
364
384
  for (let i = 0; i < keysToProcess.length; i++) {
@@ -366,7 +386,10 @@ export class ASTVisitors {
366
386
  let ns: string | undefined
367
387
 
368
388
  // Determine namespace (explicit ns > scope ns > ns:key > default)
369
- if (options?.type === 'ObjectExpression') ns = this.getObjectPropValue(options, 'ns')
389
+ if (options?.type === 'ObjectExpression') {
390
+ const nsVal = this.getObjectPropValue(options, 'ns')
391
+ if (typeof nsVal === 'string') ns = nsVal
392
+ }
370
393
  if (!ns && scopeInfo?.defaultNs) ns = scopeInfo.defaultNs
371
394
 
372
395
  const nsSeparator = this.config.extract.nsSeparator ?? ':'
@@ -386,22 +409,49 @@ export class ASTVisitors {
386
409
  // The explicit defaultValue only applies to the LAST key in the fallback array.
387
410
  // For all preceding keys, their own key is their fallback.
388
411
  const isLastKey = i === keysToProcess.length - 1
389
- const dv = isLastKey ? defaultValue : key
412
+ const dv = isLastKey ? (finalDefaultValue || key) : key
390
413
 
391
- // Handle plurals and context for each key
414
+ // Handle plurals, context, and returnObjects
392
415
  if (options?.type === 'ObjectExpression') {
416
+ const contextProp = this.getObjectProperty(options, 'context')
417
+
418
+ // 1. Handle Dynamic Context (Ternary) first
419
+ if (contextProp?.value?.type === 'ConditionalExpression') {
420
+ const contextValues = this.resolvePossibleStringValues(contextProp.value)
421
+ const contextSeparator = this.config.extract.contextSeparator ?? '_'
422
+
423
+ if (contextValues.length > 0) {
424
+ contextValues.forEach(context => {
425
+ this.pluginContext.addKey({ key: `${finalKey}${contextSeparator}${context}`, ns, defaultValue: dv })
426
+ })
427
+ // For dynamic context, also add the base key as a fallback
428
+ this.pluginContext.addKey({ key: finalKey, ns, defaultValue: dv })
429
+ continue // This key is fully handled, move to the next in the array
430
+ }
431
+ }
432
+
433
+ // 2. Handle Static Context
393
434
  const contextValue = this.getObjectPropValue(options, 'context')
394
- const contextSeparator = this.config.extract.contextSeparator ?? '_'
395
- if (contextValue) {
435
+ if (typeof contextValue === 'string' && contextValue) {
436
+ const contextSeparator = this.config.extract.contextSeparator ?? '_'
396
437
  this.pluginContext.addKey({ key: `${finalKey}${contextSeparator}${contextValue}`, ns, defaultValue: dv })
397
- continue // This key is handled, move to the next one in the array
438
+ continue // This key is fully handled
398
439
  }
440
+
441
+ // 3. Handle Plurals
399
442
  if (this.getObjectPropValue(options, 'count') !== undefined) {
400
443
  this.handlePluralKeys(finalKey, dv, ns)
401
- continue // This key is handled, move to the next one in the array
444
+ continue // This key is fully handled
445
+ }
446
+
447
+ // 4. Handle returnObjects
448
+ if (this.getObjectPropValue(options, 'returnObjects') === true) {
449
+ this.objectKeys.add(finalKey)
450
+ // Fall through to add the base key itself
402
451
  }
403
452
  }
404
453
 
454
+ // 5. Default case: Add the simple key
405
455
  this.pluginContext.addKey({ key: finalKey, ns, defaultValue: dv })
406
456
  }
407
457
  }
@@ -438,36 +488,6 @@ export class ASTVisitors {
438
488
  }
439
489
  }
440
490
 
441
- /**
442
- * Extracts default value from translation function call arguments.
443
- *
444
- * Supports multiple patterns:
445
- * - String as second argument: `t('key', 'Default')`
446
- * - Object with defaultValue: `t('key', { defaultValue: 'Default' })`
447
- * - Falls back to the key itself if no default found
448
- *
449
- * @param node - Call expression node
450
- * @param fallback - Fallback value if no default found
451
- * @returns Extracted default value
452
- *
453
- * @private
454
- */
455
- private getDefaultValue (node: CallExpression, fallback: string): string {
456
- if (node.arguments.length <= 1) return fallback
457
-
458
- const secondArg = node.arguments[1].expression
459
-
460
- if (secondArg.type === 'StringLiteral') {
461
- return secondArg.value || fallback
462
- }
463
-
464
- if (secondArg.type === 'ObjectExpression') {
465
- return this.getObjectPropValue(secondArg, 'defaultValue') || fallback
466
- }
467
-
468
- return fallback
469
- }
470
-
471
491
  /**
472
492
  * Processes JSX elements to extract translation keys from Trans components.
473
493
  *
@@ -512,11 +532,20 @@ export class ASTVisitors {
512
532
  extractedKey.ns = this.config.extract.defaultNS
513
533
  }
514
534
 
515
- // If the component has a `count` prop, use the plural handler
516
- if (extractedKey.hasCount) {
535
+ if (extractedKey.contextExpression) {
536
+ const contextValues = this.resolvePossibleStringValues(extractedKey.contextExpression)
537
+ const contextSeparator = this.config.extract.contextSeparator ?? '_'
538
+
539
+ if (contextValues.length > 0) {
540
+ for (const context of contextValues) {
541
+ this.pluginContext.addKey({ key: `${extractedKey.key}${contextSeparator}${context}`, ns: extractedKey.ns, defaultValue: extractedKey.defaultValue })
542
+ }
543
+ // Add the base key as well
544
+ this.pluginContext.addKey(extractedKey)
545
+ }
546
+ } else if (extractedKey.hasCount) {
517
547
  this.handlePluralKeys(extractedKey.key, extractedKey.defaultValue, extractedKey.ns)
518
548
  } else {
519
- // Otherwise, add the key as-is
520
549
  this.pluginContext.addKey(extractedKey)
521
550
  }
522
551
  // The duplicated addKey call has been removed.
@@ -564,7 +593,7 @@ export class ASTVisitors {
564
593
  *
565
594
  * @private
566
595
  */
567
- private getObjectPropValue (object: ObjectExpression, propName: string): string | undefined {
596
+ private getObjectPropValue (object: ObjectExpression, propName: string): string | boolean | number | undefined {
568
597
  const prop = (object.properties).find(
569
598
  (p) =>
570
599
  p.type === 'KeyValueProperty' &&
@@ -576,12 +605,18 @@ export class ASTVisitors {
576
605
 
577
606
  if (prop?.type === 'KeyValueProperty') {
578
607
  const val = prop.value
579
- // Only return the value if it's a string, otherwise we just care that it exists (for `count`)
608
+ // Return concrete literal values when possible
580
609
  if (val.type === 'StringLiteral') {
581
610
  return val.value
582
611
  }
583
- // For properties like `count`, the value could be a number, but we just need to know it's there.
584
- // So we return a non-undefined value. An empty string is fine.
612
+ if (val.type === 'BooleanLiteral') {
613
+ return val.value
614
+ }
615
+ if (val.type === 'NumericLiteral') {
616
+ return val.value
617
+ }
618
+ // For other expression types (identifier, member expr, etc.) we only care that the prop exists.
619
+ // Return an empty string to indicate presence.
585
620
  return ''
586
621
  }
587
622
  return undefined
@@ -645,4 +680,65 @@ export class ASTVisitors {
645
680
 
646
681
  return null
647
682
  }
683
+
684
+ /**
685
+ * Resolves an expression to one or more possible string values that can be
686
+ * determined statically from the AST.
687
+ *
688
+ * Supports:
689
+ * - StringLiteral -> single value
690
+ * - ConditionalExpression (ternary) -> union of consequent and alternate resolved values
691
+ * - The identifier `undefined` -> empty array
692
+ *
693
+ * For any other expression types (identifiers, function calls, member expressions,
694
+ * etc.) the value cannot be determined statically and an empty array is returned.
695
+ *
696
+ * @private
697
+ * @param expression - The SWC AST expression node to resolve
698
+ * @returns An array of possible string values that the expression may produce.
699
+ */
700
+ private resolvePossibleStringValues (expression: Expression): string[] {
701
+ if (expression.type === 'StringLiteral') {
702
+ return [expression.value]
703
+ }
704
+
705
+ if (expression.type === 'ConditionalExpression') { // This is a ternary operator
706
+ const consequentValues = this.resolvePossibleStringValues(expression.consequent)
707
+ const alternateValues = this.resolvePossibleStringValues(expression.alternate)
708
+ return [...consequentValues, ...alternateValues]
709
+ }
710
+
711
+ if (expression.type === 'Identifier' && expression.value === 'undefined') {
712
+ return [] // Handle the `undefined` case
713
+ }
714
+
715
+ // We can't statically determine the value of other expressions (e.g., variables, function calls)
716
+ return []
717
+ }
718
+
719
+ /**
720
+ * Finds and returns the full property node (KeyValueProperty) for the given
721
+ * property name from an ObjectExpression.
722
+ *
723
+ * Matches both identifier keys (e.g., { ns: 'value' }) and string literal keys
724
+ * (e.g., { 'ns': 'value' }).
725
+ *
726
+ * This helper returns the full property node rather than just its primitive
727
+ * value so callers can inspect expression types (ConditionalExpression, etc.).
728
+ *
729
+ * @private
730
+ * @param object - The SWC ObjectExpression to search
731
+ * @param propName - The property name to locate
732
+ * @returns The matching KeyValueProperty node if found, otherwise undefined.
733
+ */
734
+ private getObjectProperty (object: ObjectExpression, propName: string): any {
735
+ return (object.properties).find(
736
+ (p) =>
737
+ p.type === 'KeyValueProperty' &&
738
+ (
739
+ (p.key?.type === 'Identifier' && p.key.value === propName) ||
740
+ (p.key?.type === 'StringLiteral' && p.key.value === propName)
741
+ )
742
+ )
743
+ }
648
744
  }
@@ -55,6 +55,16 @@ export function extractFromTransComponent (node: JSXElement, config: I18nextTool
55
55
  )
56
56
  const hasCount = !!countAttr
57
57
 
58
+ const contextAttr = node.opening.attributes?.find(
59
+ (attr) =>
60
+ attr.type === 'JSXAttribute' &&
61
+ attr.name.type === 'Identifier' &&
62
+ attr.name.value === 'context'
63
+ )
64
+ const contextExpression = (contextAttr?.type === 'JSXAttribute' && contextAttr.value?.type === 'JSXExpressionContainer')
65
+ ? contextAttr.value.expression
66
+ : undefined
67
+
58
68
  let key: string
59
69
  if (i18nKeyAttr?.type === 'JSXAttribute' && i18nKeyAttr.value?.type === 'StringLiteral') {
60
70
  key = i18nKeyAttr.value.value
@@ -81,7 +91,7 @@ export function extractFromTransComponent (node: JSXElement, config: I18nextTool
81
91
  defaultValue = serializeJSXChildren(node.children, config)
82
92
  }
83
93
 
84
- return { key, ns, defaultValue: defaultValue || key, hasCount }
94
+ return { key, ns, defaultValue: defaultValue || key, hasCount, contextExpression }
85
95
  }
86
96
 
87
97
  /**
package/src/status.ts CHANGED
@@ -84,7 +84,7 @@ export async function runStatus (config: I18nextToolkitConfig, options: StatusOp
84
84
  * @throws {Error} When key extraction fails or configuration is invalid
85
85
  */
86
86
  async function generateStatusReport (config: I18nextToolkitConfig): Promise<StatusReport> {
87
- const allExtractedKeys = await findKeys(config)
87
+ const { allKeys: allExtractedKeys } = await findKeys(config)
88
88
  const { primaryLanguage, keySeparator = '.', defaultNS = 'translation' } = config.extract
89
89
  const secondaryLanguages = config.locales.filter(l => l !== primaryLanguage)
90
90
 
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Node } from '@swc/core'
1
+ import type { Node, Expression } from '@swc/core'
2
2
 
3
3
  /**
4
4
  * Main configuration interface for the i18next toolkit.
@@ -223,6 +223,9 @@ export interface ExtractedKey {
223
223
 
224
224
  /** Whether this key is used with pluralization (count parameter) */
225
225
  hasCount?: boolean;
226
+
227
+ /** hold the raw context expression from the AST */
228
+ contextExpression?: Expression;
226
229
  }
227
230
 
228
231
  /**
@@ -277,13 +280,13 @@ export interface Logger {
277
280
  * Logs a warning message.
278
281
  * @param message - The warning message to log
279
282
  */
280
- warn(message: string): void;
283
+ warn(message: string, more?: any): void;
281
284
 
282
285
  /**
283
286
  * Logs an error message.
284
287
  * @param message - The error message to log
285
288
  */
286
- error(message: string): void;
289
+ error(message: string | any): void;
287
290
  }
288
291
 
289
292
  /**
package/types/config.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { I18nextToolkitConfig } from './types';
1
+ import type { I18nextToolkitConfig, Logger } from './types';
2
2
  /**
3
3
  * A helper function for defining the i18next-cli config with type-safety.
4
4
  *
@@ -38,7 +38,7 @@ export declare function defineConfig(config: I18nextToolkitConfig): I18nextToolk
38
38
  * }
39
39
  * ```
40
40
  */
41
- export declare function loadConfig(): Promise<I18nextToolkitConfig | null>;
41
+ export declare function loadConfig(logger?: Logger): Promise<I18nextToolkitConfig | null>;
42
42
  /**
43
43
  * NEW: Ensures a configuration exists, prompting the user to create one if necessary.
44
44
  * This function is a wrapper around loadConfig that provides an interactive fallback.
@@ -46,5 +46,5 @@ export declare function loadConfig(): Promise<I18nextToolkitConfig | null>;
46
46
  * @returns A promise that resolves to a valid configuration object.
47
47
  * @throws Exits the process if the user declines to create a config or if loading fails after creation.
48
48
  */
49
- export declare function ensureConfig(): Promise<I18nextToolkitConfig>;
49
+ export declare function ensureConfig(logger?: Logger): Promise<I18nextToolkitConfig>;
50
50
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAanD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAE,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,CAEhF;AAqBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,UAAU,IAAK,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAsCxE;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,IAAK,OAAO,CAAC,oBAAoB,CAAC,CA8BnE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAc3D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAE,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,CAEhF;AAqBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,UAAU,CAAE,MAAM,GAAE,MAA4B,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAsC5G;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAE,MAAM,GAAE,MAA4B,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA8BvG"}
@@ -1,4 +1,5 @@
1
1
  import type { Logger, ExtractedKey, I18nextToolkitConfig } from '../../types';
2
+ import { ASTVisitors } from '../parsers/ast-visitors';
2
3
  /**
3
4
  * Main extractor function that runs the complete key extraction and file generation process.
4
5
  *
@@ -46,7 +47,7 @@ export declare function runExtractor(config: I18nextToolkitConfig, logger?: Logg
46
47
  *
47
48
  * @internal
48
49
  */
49
- export declare function processFile(file: string, config: I18nextToolkitConfig, logger: Logger, allKeys: Map<string, ExtractedKey>): Promise<void>;
50
+ export declare function processFile(file: string, config: I18nextToolkitConfig, allKeys: Map<string, ExtractedKey>, astVisitors: ASTVisitors, logger?: Logger): Promise<void>;
50
51
  /**
51
52
  * Simplified extraction function that returns translation results without file writing.
52
53
  * 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;AAS5F;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,OAAO,CAAC,CA+BlB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,oBAAoB,EAC5B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC,CAoCf;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,YAAY,EAAiB,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAM5F,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAGrD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,OAAO,CAAC,CA+BlB;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,CA6Bf;AAmCD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,sDAO1D"}
@@ -27,5 +27,8 @@ import type { ExtractedKey, Logger, I18nextToolkitConfig } from '../../types';
27
27
  * console.log(`Found ${keys.size} unique translation keys`)
28
28
  * ```
29
29
  */
30
- export declare function findKeys(config: I18nextToolkitConfig, logger?: Logger): Promise<Map<string, ExtractedKey>>;
30
+ export declare function findKeys(config: I18nextToolkitConfig, logger?: Logger): Promise<{
31
+ allKeys: Map<string, ExtractedKey>;
32
+ objectKeys: Set<string>;
33
+ }>;
31
34
  //# sourceMappingURL=key-finder.d.ts.map
@@ -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;AAK7E;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,oBAAoB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAgBpC"}
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,CAmB1E"}
@@ -27,5 +27,5 @@ import { TranslationResult, ExtractedKey, I18nextToolkitConfig } from '../../typ
27
27
  * // Results contain update status and new/existing translations for each locale
28
28
  * ```
29
29
  */
30
- export declare function getTranslations(keys: Map<string, ExtractedKey>, config: I18nextToolkitConfig): Promise<TranslationResult[]>;
30
+ export declare function getTranslations(keys: Map<string, ExtractedKey>, objectKeys: Set<string>, config: I18nextToolkitConfig): Promise<TranslationResult[]>;
31
31
  //# sourceMappingURL=translation-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAiBnF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAkE9B"}
1
+ {"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAiBnF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAuE9B"}
@@ -27,6 +27,7 @@ export declare class ASTVisitors {
27
27
  private readonly config;
28
28
  private readonly logger;
29
29
  private scopeStack;
30
+ objectKeys: Set<string>;
30
31
  /**
31
32
  * Creates a new AST visitor instance.
32
33
  *
@@ -162,21 +163,6 @@ export declare class ASTVisitors {
162
163
  * @private
163
164
  */
164
165
  private handlePluralKeys;
165
- /**
166
- * Extracts default value from translation function call arguments.
167
- *
168
- * Supports multiple patterns:
169
- * - String as second argument: `t('key', 'Default')`
170
- * - Object with defaultValue: `t('key', { defaultValue: 'Default' })`
171
- * - Falls back to the key itself if no default found
172
- *
173
- * @param node - Call expression node
174
- * @param fallback - Fallback value if no default found
175
- * @returns Extracted default value
176
- *
177
- * @private
178
- */
179
- private getDefaultValue;
180
166
  /**
181
167
  * Processes JSX elements to extract translation keys from Trans components.
182
168
  *
@@ -231,5 +217,38 @@ export declare class ASTVisitors {
231
217
  * @private
232
218
  */
233
219
  private extractKeyFromSelector;
220
+ /**
221
+ * Resolves an expression to one or more possible string values that can be
222
+ * determined statically from the AST.
223
+ *
224
+ * Supports:
225
+ * - StringLiteral -> single value
226
+ * - ConditionalExpression (ternary) -> union of consequent and alternate resolved values
227
+ * - The identifier `undefined` -> empty array
228
+ *
229
+ * For any other expression types (identifiers, function calls, member expressions,
230
+ * etc.) the value cannot be determined statically and an empty array is returned.
231
+ *
232
+ * @private
233
+ * @param expression - The SWC AST expression node to resolve
234
+ * @returns An array of possible string values that the expression may produce.
235
+ */
236
+ private resolvePossibleStringValues;
237
+ /**
238
+ * Finds and returns the full property node (KeyValueProperty) for the given
239
+ * property name from an ObjectExpression.
240
+ *
241
+ * Matches both identifier keys (e.g., { ns: 'value' }) and string literal keys
242
+ * (e.g., { 'ns': 'value' }).
243
+ *
244
+ * This helper returns the full property node rather than just its primitive
245
+ * value so callers can inspect expression types (ConditionalExpression, etc.).
246
+ *
247
+ * @private
248
+ * @param object - The SWC ObjectExpression to search
249
+ * @param propName - The property name to locate
250
+ * @returns The matching KeyValueProperty node if found, otherwise undefined.
251
+ */
252
+ private getObjectProperty;
234
253
  }
235
254
  //# sourceMappingURL=ast-visitors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAmG,MAAM,WAAW,CAAA;AACxI,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAc9E;;;;;;;;;;;;;;;;;;;;;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;IAEtD;;;;;;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;IAiDZ;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IASvB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,wBAAwB;IAqBhC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,8BAA8B;IAiDtC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IA6E5B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,gBAAgB;IAmBxB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,eAAe;IAgBvB;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IA8CxB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,kBAAkB;IAuB1B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,sBAAsB;CA0C/B"}
1
+ {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA+G,MAAM,WAAW,CAAA;AACpJ,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAc9E;;;;;;;;;;;;;;;;;;;;;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;IAiDZ;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IASvB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,wBAAwB;IAqBhC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,8BAA8B;IAkDtC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IA4H5B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,gBAAgB;IAmBxB;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IAuDxB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,kBAAkB;IA6B1B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,2BAA2B;IAmBnC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,iBAAiB;CAU1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"jsx-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,yBAAyB,CAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,GAAG,YAAY,GAAG,IAAI,CAkD9G"}
1
+ {"version":3,"file":"jsx-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,yBAAyB,CAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,GAAG,YAAY,GAAG,IAAI,CA4D9G"}
package/types/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Node } from '@swc/core';
1
+ import type { Node, Expression } from '@swc/core';
2
2
  /**
3
3
  * Main configuration interface for the i18next toolkit.
4
4
  * Defines all available options for extraction, type generation, synchronization, and integrations.
@@ -185,6 +185,8 @@ export interface ExtractedKey {
185
185
  ns?: string;
186
186
  /** Whether this key is used with pluralization (count parameter) */
187
187
  hasCount?: boolean;
188
+ /** hold the raw context expression from the AST */
189
+ contextExpression?: Expression;
188
190
  }
189
191
  /**
190
192
  * Result of processing translation files for a specific locale and namespace.
@@ -233,12 +235,12 @@ export interface Logger {
233
235
  * Logs a warning message.
234
236
  * @param message - The warning message to log
235
237
  */
236
- warn(message: string): void;
238
+ warn(message: string, more?: any): void;
237
239
  /**
238
240
  * Logs an error message.
239
241
  * @param message - The error message to log
240
242
  */
241
- error(message: string): void;
243
+ error(message: string | any): void;
242
244
  }
243
245
  /**
244
246
  * Context object provided to plugins during AST traversal.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}