i18next-cli 1.5.8 → 1.5.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ 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.5.9](https://github.com/i18next/i18next-cli/compare/v1.5.8...v1.5.9) - 2025-10-05
9
+
10
+ ### Added
11
+ - **Extractor (`<Trans>`):** Added support for plural-specific default values from `tOptions` prop (e.g., `defaultValue_other: "Items"`). The extractor now correctly uses these values when generating plural keys for Trans components. [#36](https://github.com/i18next/i18next-cli/pull/36)
12
+ - **Extractor:** Added support for dynamic expressions in `t()` function arguments. The extractor can now resolve ternary operators and other static expressions to extract all possible key variants (e.g., `t(isOpen ? 'open' : 'closed')`). [#37](https://github.com/i18next/i18next-cli/pull/37)
13
+
14
+ ### Enhanced
15
+ - **Extractor (`<Trans>`):** Improved namespace resolution consistency by prioritizing namespace from `i18nKey` prop over other sources. When a Trans component uses `i18nKey="ns:key"`, the namespace from the key now takes precedence over the `t` prop namespace, matching i18next's behavior. [#38](https://github.com/i18next/i18next-cli/pull/38)
16
+
8
17
  ## [1.5.8](https://github.com/i18next/i18next-cli/compare/v1.5.7...v1.5.8) - 2025-10-05
9
18
 
10
19
  - **Extractor:** Fixed namespace resolution/override order where explicitly passed `ns` options in `t()` calls were being incorrectly overridden by hook-level namespaces. The extractor now properly prioritizes explicit namespace options over inferred ones. [#32](https://github.com/i18next/i18next-cli/issues/32)
package/dist/cjs/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var e=require("commander"),t=require("chokidar"),n=require("glob"),o=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.5.8"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").option("--dry-run","Run the extractor without writing any files to disk.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a,{isWatchMode:e.watch,isDryRun:e.dryRun});e.ci&&t&&(console.error(o.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(o.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${o.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let n=await i.loadConfig();if(!n){console.log(o.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),n=e}await g.runStatus(n,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const o=await i.ensureConfig(),a=()=>c.runTypesGenerator(o);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(o.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await l.runMigrator(e)}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const r=async()=>{let e=await i.loadConfig();if(!e){console.log(o.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),e=t}await d.runLinter(e)};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await i.loadConfig();if(e?.extract?.input){t.watch(await n.glob(e.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
2
+ "use strict";var e=require("commander"),t=require("chokidar"),n=require("glob"),o=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.5.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.").option("--dry-run","Run the extractor without writing any files to disk.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a,{isWatchMode:e.watch,isDryRun:e.dryRun});e.ci&&t&&(console.error(o.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(o.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${o.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let n=await i.loadConfig();if(!n){console.log(o.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),n=e}await g.runStatus(n,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const o=await i.ensureConfig(),a=()=>c.runTypesGenerator(o);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(o.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await l.runMigrator(e)}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const r=async()=>{let e=await i.loadConfig();if(!e){console.log(o.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),e=t}await d.runLinter(e)};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await i.loadConfig();if(e?.extract?.input){t.watch(await n.glob(e.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
@@ -1 +1 @@
1
- "use strict";function e(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))}exports.getObjectPropValue=function(t,r){const y=e(t,r);if("KeyValueProperty"===y?.type){const e=y.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}},exports.getObjectProperty=e;
1
+ "use strict";function e(e,t){return e.properties.filter(e=>"KeyValueProperty"===e.type).find(e=>"Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t)}exports.getObjectPropValue=function(t,r){const i=e(t,r);if("KeyValueProperty"===i?.type){const e=i.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}},exports.getObjectProperty=e;
@@ -1 +1 @@
1
- "use strict";var e=require("./jsx-parser.js"),t=require("./ast-utils.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&"object"==typeof n&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const r=n.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,n,r){let i;if("Identifier"===e.id.type&&(i=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(i=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){i="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){i=t.value.value;break}}if(!i)return;const s=n.arguments?.[r.nsArg]?.expression;let a;"StringLiteral"===s?.type?a=s.value:"ArrayExpression"===s?.type&&"StringLiteral"===s.elements[0]?.expression.type&&(a=s.elements[0].expression.value);const o=n.arguments?.[r.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t.getObjectPropValue(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(i,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,r=t.arguments,i=r[1]?.expression,s=r[2]?.expression,a="StringLiteral"===i?.type?i.value:void 0,o="StringLiteral"===s?.type?s.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const n=this.getFunctionName(e.callee);if(!n)return;const r=this.getVarFromScope(n),i=this.config.extract.functions||["t","*.t"];let s=void 0!==r;if(!s)for(const e of i)if(e.startsWith("*.")){if(n.endsWith(e.substring(1))){s=!0;break}}else if(e===n){s=!0;break}if(!s||0===e.arguments.length)return;const a=e.arguments[0].expression;let o=[],l=!1;if("StringLiteral"===a.type)o.push(a.value);else if("ArrowFunctionExpression"===a.type){const e=this.extractKeyFromSelector(a);e&&(o.push(e),l=!0)}else if("ArrayExpression"===a.type)for(const e of a.elements)"StringLiteral"===e?.expression.type&&o.push(e.expression.value);if(o=o.filter(e=>!!e),0===o.length)return;let u=!1;const p=this.config.extract.pluralSeparator??"_";for(let e=0;e<o.length;e++)o[e].endsWith(`${p}ordinal`)&&(u=!0,o[e]=o[e].slice(0,-8));let c,f;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?f=t:"StringLiteral"===t.type&&(c=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(f=t)}const g=f?t.getObjectPropValue(f,"defaultValue"):void 0,y="string"==typeof g?g:c;for(let e=0;e<o.length;e++){let n,i=o[e];if(f){const e=t.getObjectPropValue(f,"ns");"string"==typeof e&&(n=e)}const s=this.config.extract.nsSeparator??":";if(!n&&s&&i.includes(s)){const e=i.split(s);n=e.shift(),i=e.join(s)}!n&&r?.defaultNs&&(n=r.defaultNs),n||(n=this.config.extract.defaultNS);let a=i;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";a=`${r.keyPrefix}${e}${i}`}const p=e===o.length-1&&y||i;if(f){const e=t.getObjectProperty(f,"context");if("ConditionalExpression"===e?.value?.type){const t=this.resolvePossibleStringValues(e.value),r=this.config.extract.contextSeparator??"_";if(t.length>0){t.forEach(e=>{this.pluginContext.addKey({key:`${a}${r}${e}`,ns:n,defaultValue:p})}),this.pluginContext.addKey({key:a,ns:n,defaultValue:p});continue}}const r=t.getObjectPropValue(f,"context");if("string"==typeof r&&r){const e=this.config.extract.contextSeparator??"_";this.pluginContext.addKey({key:`${a}${e}${r}`,ns:n,defaultValue:p});continue}const i=void 0!==t.getObjectPropValue(f,"count"),s=!0===t.getObjectPropValue(f,"ordinal");if(i||u){this.handlePluralKeys(a,n,f,s||u);continue}!0===t.getObjectPropValue(f,"returnObjects")&&this.objectKeys.add(a)}l&&this.objectKeys.add(a),this.pluginContext.addKey({key:a,ns:n,defaultValue:p})}}handlePluralKeys(e,n,r,i){try{const s=i?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:s}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t.getObjectPropValue(r,"defaultValue"),u=t.getObjectPropValue(r,`defaultValue${o}other`),p=t.getObjectPropValue(r,`defaultValue${o}ordinal${o}other`);for(const s of a){const a=i?`defaultValue${o}ordinal${o}${s}`:`defaultValue${o}${s}`,c=t.getObjectPropValue(r,a);let f;f="string"==typeof c?c:"one"===s&&"string"==typeof l?l:i&&"string"==typeof p?p:i||"string"!=typeof u?"string"==typeof l?l:e:u;const g=i?`${e}${o}ordinal${o}${s}`:`${e}${o}${s}`;this.pluginContext.addKey({key:g,ns:n,defaultValue:f,hasCount:!0,isOrdinal:i})}}catch(i){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const s=t.getObjectPropValue(r,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof s?s:e})}}handleSimplePluralKeys(e,t,n){try{const r=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,i=this.config.extract.pluralSeparator??"_";for(const s of r)this.pluginContext.addKey({key:`${e}${i}${s}`,ns:n,defaultValue:t,hasCount:!0})}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:n,defaultValue:t})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e.extractFromTransComponent(t,this.config);if(n){if(!n.ns){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===e?.type&&"JSXExpressionContainer"===e.value?.type&&"Identifier"===e.value.expression.type){const t=e.value.expression.value,r=this.getVarFromScope(t);r?.defaultNs&&(n.ns=r.defaultNs)}}if(n.ns||(n.ns=this.config.extract.defaultNS),n.contextExpression&&n.hasCount){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!e,i=this.resolvePossibleStringValues(n.contextExpression),s=this.config.extract.contextSeparator??"_";if(i.length>0){this.generatePluralKeysForTrans(n.key,n.defaultValue,n.ns,r,n.optionsNode);for(const e of i){const t=`${n.key}${s}${e}`;this.generatePluralKeysForTrans(t,n.defaultValue,n.ns,r,n.optionsNode)}}else this.generatePluralKeysForTrans(n.key,n.defaultValue,n.ns,r,n.optionsNode)}else if(n.contextExpression){const e=this.resolvePossibleStringValues(n.contextExpression),t=this.config.extract.contextSeparator??"_";if(e.length>0){for(const r of e)this.pluginContext.addKey({key:`${n.key}${t}${r}`,ns:n.ns,defaultValue:n.defaultValue});"StringLiteral"!==n.contextExpression.type&&this.pluginContext.addKey(n)}}else if(n.hasCount){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!e;this.generatePluralKeysForTrans(n.key,n.defaultValue,n.ns,r,n.optionsNode)}else this.pluginContext.addKey(n)}}}generatePluralKeysForTrans(e,n,r,i,s){try{const a=i?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;s&&(u=t.getObjectPropValue(s,`defaultValue${l}other`),p=t.getObjectPropValue(s,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=i?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`,c=s?t.getObjectPropValue(s,o):void 0;let f;f="string"==typeof c?c:"one"===a&&"string"==typeof n?n:i&&"string"==typeof p?p:i||"string"!=typeof u?"string"==typeof n?n:e:u;const g=i?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:g,ns:r,defaultValue:f,hasCount:!0,isOrdinal:i})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:r,defaultValue:n})}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const r=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)r.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;r.unshift(e.expression.value)}n=n.object}if(r.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return r.join(t)}return null}resolvePossibleStringValues(e){if("StringLiteral"===e.type)return e.value?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent),...this.resolvePossibleStringValues(e.alternate)]}return"Identifier"===e.type&&e.value,[]}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}};
1
+ "use strict";var e=require("./jsx-parser.js"),t=require("./ast-utils.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&"object"==typeof n&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const r=n.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,n,r){let s;if("Identifier"===e.id.type&&(s=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(s=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){s="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){s=t.value.value;break}}if(!s)return;const i=n.arguments?.[r.nsArg]?.expression;let a;"StringLiteral"===i?.type?a=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(a=i.elements[0].expression.value);const o=n.arguments?.[r.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t.getObjectPropValue(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(s,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,r=t.arguments,s=r[1]?.expression,i=r[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===i?.type?i.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const n=this.getFunctionName(e.callee);if(!n)return;const r=this.getVarFromScope(n),s=this.config.extract.functions||["t","*.t"];let i=void 0!==r;if(!i)for(const e of s)if(e.startsWith("*.")){if(n.endsWith(e.substring(1))){i=!0;break}}else if(e===n){i=!0;break}if(!i||0===e.arguments.length)return;const{keysToProcess:a,isSelectorAPI:o}=this.handleCallExpressionArgument(e,0);if(0===a.length)return;let l=!1;const u=this.config.extract.pluralSeparator??"_";for(let e=0;e<a.length;e++)a[e].endsWith(`${u}ordinal`)&&(l=!0,a[e]=a[e].slice(0,-8));let p,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(p=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const f=c?t.getObjectPropValue(c,"defaultValue"):void 0,g="string"==typeof f?f:p;for(let e=0;e<a.length;e++){let n,s=a[e];if(c){const e=t.getObjectPropValue(c,"ns");"string"==typeof e&&(n=e)}const i=this.config.extract.nsSeparator??":";if(!n&&i&&s.includes(i)){const e=s.split(i);n=e.shift(),s=e.join(i)}!n&&r?.defaultNs&&(n=r.defaultNs),n||(n=this.config.extract.defaultNS);let u=s;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";u=`${r.keyPrefix}${e}${s}`}const p=e===a.length-1&&g||s;if(c){const e=t.getObjectProperty(c,"context"),r=[];if("ConditionalExpression"===e?.value?.type){const t=this.resolvePossibleStringValues(e.value),s=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{r.push({key:`${u}${s}${e}`,ns:n,defaultValue:p})}),r.push({key:u,ns:n,defaultValue:p}))}else if("StringLiteral"===e?.value?.type){const t=e.value.value,s=this.config.extract.contextSeparator??"_";r.push({key:`${u}${s}${t}`,ns:n,defaultValue:p})}const s=void 0!==t.getObjectPropValue(c,"count"),i=!0===t.getObjectPropValue(c,"ordinal");if(s||l){if(r.length>0)for(const{key:e,ns:t}of r)this.handlePluralKeys(e,t,c,i||l);else this.handlePluralKeys(u,n,c,i||l);continue}if(r.length>0){r.forEach(this.pluginContext.addKey);continue}!0===t.getObjectPropValue(c,"returnObjects")&&this.objectKeys.add(u)}o&&this.objectKeys.add(u),this.pluginContext.addKey({key:u,ns:n,defaultValue:p})}}handleCallExpressionArgument(e,t){const n=e.arguments[t].expression,r=[];let s=!1;if("ArrowFunctionExpression"===n.type){const e=this.extractKeyFromSelector(n);e&&(r.push(e),s=!0)}else if("ArrayExpression"===n.type)for(const e of n.elements)e?.expression&&r.push(...this.resolvePossibleStringValues(e.expression));else r.push(...this.resolvePossibleStringValues(n));return{keysToProcess:r.filter(e=>!!e),isSelectorAPI:s}}handlePluralKeys(e,n,r,s){try{const i=s?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t.getObjectPropValue(r,"defaultValue"),u=t.getObjectPropValue(r,`defaultValue${o}other`),p=t.getObjectPropValue(r,`defaultValue${o}ordinal${o}other`);for(const i of a){const a=s?`defaultValue${o}ordinal${o}${i}`:`defaultValue${o}${i}`,c=t.getObjectPropValue(r,a);let f;f="string"==typeof c?c:"one"===i&&"string"==typeof l?l:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof l?l:e:u;const g=s?`${e}${o}ordinal${o}${i}`:`${e}${o}${i}`;this.pluginContext.addKey({key:g,ns:n,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(s){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=t.getObjectPropValue(r,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,n){try{const r=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,s=this.config.extract.pluralSeparator??"_";for(const i of r)this.pluginContext.addKey({key:`${e}${s}${i}`,ns:n,defaultValue:t,hasCount:!0})}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:n,defaultValue:t})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e.extractFromTransComponent(t,this.config),r=[];if(n){if(n.keyExpression){const e=this.resolvePossibleStringValues(n.keyExpression);r.push(...e)}else r.push(n.serializedChildren);let e;const{contextExpression:s,optionsNode:i,defaultValue:a,hasCount:o,isOrdinal:l,serializedChildren:u}=n;if(n.ns){const{ns:t}=n;e=r.map(e=>({key:e,ns:t,defaultValue:a||u,hasCount:o,isOrdinal:l}))}else{e=r.map(e=>{const t=this.config.extract.nsSeparator??":";let n;if(t&&e.includes(t)){let r;[n,...r]=e.split(t),e=r.join(t)}return{key:e,ns:n,defaultValue:a||u,hasCount:o,isOrdinal:l}});const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===n?.type&&"JSXExpressionContainer"===n.value?.type&&"Identifier"===n.value.expression.type){const t=n.value.expression.value,r=this.getVarFromScope(t);r?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=r.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),s&&o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!n,a=this.resolvePossibleStringValues(s),o=this.config.extract.contextSeparator??"_";if(a.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i));for(const t of a)for(const n of e){const e=`${n.key}${o}${t}`;this.generatePluralKeysForTrans(e,n.defaultValue,n.ns,r,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i))}else if(s){const t=this.resolvePossibleStringValues(s),n=this.config.extract.contextSeparator??"_";if(t.length>0){for(const r of t)for(const{key:t,ns:s,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${n}${r}`,ns:s,defaultValue:i});"StringLiteral"!==s.type&&e.forEach(this.pluginContext.addKey)}}else if(o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!n;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,n,r,s,i){try{const a=s?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t.getObjectPropValue(i,`defaultValue${l}other`),p=t.getObjectPropValue(i,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=s?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`,c=i?t.getObjectPropValue(i,o):void 0;let f;f="string"==typeof c?c:"one"===a&&"string"==typeof n?n:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof n?n:e:u;const g=s?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:g,ns:r,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:r,defaultValue:n})}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const r=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)r.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;r.unshift(e.expression.value)}n=n.object}if(r.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return r.join(t)}return null}resolvePossibleStringValues(e){if("StringLiteral"===e.type)return e.value?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent),...this.resolvePossibleStringValues(e.alternate)]}return"Identifier"===e.type&&e.value,[]}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}};
@@ -1 +1 @@
1
- "use strict";var e=require("./ast-utils.js");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," ")}exports.extractFromTransComponent=function(n,i){const r=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),a=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),u=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),p=!!u,s=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"tOptions"===e.name.value),l="JSXAttribute"===s?.type&&"JSXExpressionContainer"===s.value?.type&&"ObjectExpression"===s.value.expression.type?s.value.expression:void 0,o=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value);let y,v="JSXAttribute"===o?.type&&"JSXExpressionContainer"===o.value?.type?o.value.expression:void 0;if(y="JSXAttribute"===r?.type&&"StringLiteral"===r.value?.type?r.value.value:t(n.children,i),!y)return null;const f=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value);let c;if(c="JSXAttribute"===f?.type&&"StringLiteral"===f.value?.type?f.value.value:void 0,l&&(void 0===c&&(c=e.getObjectPropValue(l,"ns")),void 0===v)){const t=e.getObjectProperty(l,"context");t?.value&&(v=t.value)}let d=i.extract.defaultValue||"";return d="JSXAttribute"===a?.type&&"StringLiteral"===a.value?.type?a.value.value:t(n.children,i),{key:y,ns:c,defaultValue:d||y,hasCount:p,contextExpression:v,optionsNode:l}};
1
+ "use strict";var e=require("./ast-utils.js");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),s=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"values"===e.name.value);let p;a||"JSXAttribute"!==s?.type||"JSXExpressionContainer"!==s.value?.type||"ObjectExpression"!==s.value.expression.type||(p=e.getObjectProperty(s.value.expression,"count"));const u=!!a||!!p,o=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"tOptions"===e.name.value),l="JSXAttribute"===o?.type&&"JSXExpressionContainer"===o.value?.type&&"ObjectExpression"===o.value.expression.type?o.value.expression:void 0,y=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),v=!!y,d=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value);let c="JSXAttribute"===d?.type&&"JSXExpressionContainer"===d.value?.type?d.value.expression:void 0;const f=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value);let b;if(b="JSXAttribute"===f?.type&&"StringLiteral"===f.value?.type?f.value.value:void 0,l&&(void 0===b&&(b=e.getObjectPropValue(l,"ns")),void 0===c)){const t=e.getObjectProperty(l,"context");t?.value&&(c=t.value)}const S=function(e,t){const n=new Set(t.extract.transKeepBasicHtmlNodesFor??["br","strong","i","p"]);function i(e){let t="";return e.forEach((e,r)=>{if("JSXText"===e.type)t+=e.value;else if("JSXExpressionContainer"===e.type){const n=e.expression;if("StringLiteral"===n.type)t+=n.value;else if("Identifier"===n.type)t+=`{{${n.value}}}`;else if("ObjectExpression"===n.type){const e=n.properties[0];e&&"Identifier"===e.type&&(t+=`{{${e.value}}}`)}}else if("JSXElement"===e.type){let a;"Identifier"===e.opening.name.type&&(a=e.opening.name.value);const s=i(e.children);a&&n.has(a)?t+=`<${a}>${s}</${a}>`:t+=`<${r}>${s}</${r}>`}else"JSXFragment"===e.type&&(t+=i(e.children))}),t}return i(e).trim().replace(/\s{2,}/g," ")}(t.children,n);let x,m=n.extract.defaultValue||"";return"JSXAttribute"===r?.type&&"StringLiteral"===r.value?.type&&(m=r.value.value),"JSXAttribute"!==i?.type||("StringLiteral"===i.value?.type?x=i.value:"JSXExpressionContainer"===i.value?.type&&"JSXEmptyExpression"!==i.value.expression.type&&(x=i.value.expression),x)?{keyExpression:x,serializedChildren:S,ns:b,defaultValue:m,hasCount:u,isOrdinal:v,contextExpression:c,optionsNode:l}:null};
package/dist/esm/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as t}from"commander";import o from"chokidar";import{glob as e}from"glob";import n from"chalk";import{ensureConfig as i,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as g}from"./status.js";import{runLocizeSync as f,runLocizeDownload as u,runLocizeMigrate as h}from"./locize.js";const w=new t;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.5.8"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").option("--dry-run","Run the extractor without writing any files to disk.").action(async t=>{const a=await i(),c=async()=>{const o=await r(a,{isWatchMode:t.watch,isDryRun:t.dryRun});t.ci&&o&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),t.watch){console.log("\nWatching for changes...");o.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(t,o)=>{let e=await a();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await g(e,{detail:t,namespace:o.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async t=>{const n=await i(),a=()=>s(n);if(await a(),t.watch){console.log("\nWatching for changes...");o.watch(await e(n.types?.input||[]),{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=await i();await l(t)}),w.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await m(t)}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async t=>{const i=async()=>{let t=await a();if(!t){console.log(n.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),t=o}await d(t)};if(await i(),t.watch){console.log("\nWatching for changes...");const t=await a();if(t?.extract?.input){o.watch(await e(t.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),i()})}}}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async t=>{const o=await i();await f(o,t)}),w.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const o=await i();await u(o,t)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const o=await i();await h(o,t)}),w.parse(process.argv);
2
+ import{Command as t}from"commander";import o from"chokidar";import{glob as e}from"glob";import n from"chalk";import{ensureConfig as i,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as g}from"./status.js";import{runLocizeSync as f,runLocizeDownload as u,runLocizeMigrate as h}from"./locize.js";const w=new t;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.5.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.").option("--dry-run","Run the extractor without writing any files to disk.").action(async t=>{const a=await i(),c=async()=>{const o=await r(a,{isWatchMode:t.watch,isDryRun:t.dryRun});t.ci&&o&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),t.watch){console.log("\nWatching for changes...");o.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(t,o)=>{let e=await a();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await g(e,{detail:t,namespace:o.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async t=>{const n=await i(),a=()=>s(n);if(await a(),t.watch){console.log("\nWatching for changes...");o.watch(await e(n.types?.input||[]),{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=await i();await l(t)}),w.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await m(t)}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async t=>{const i=async()=>{let t=await a();if(!t){console.log(n.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),t=o}await d(t)};if(await i(),t.watch){console.log("\nWatching for changes...");const t=await a();if(t?.extract?.input){o.watch(await e(t.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),i()})}}}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async t=>{const o=await i();await f(o,t)}),w.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const o=await i();await u(o,t)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const o=await i();await h(o,t)}),w.parse(process.argv);
@@ -1 +1 @@
1
- function e(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))}function t(t,r){const y=e(t,r);if("KeyValueProperty"===y?.type){const e=y.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}}export{t as getObjectPropValue,e as getObjectProperty};
1
+ function e(e,t){return e.properties.filter(e=>"KeyValueProperty"===e.type).find(e=>"Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t)}function t(t,r){const i=e(t,r);if("KeyValueProperty"===i?.type){const e=i.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}}export{t as getObjectPropValue,e as getObjectProperty};
@@ -1 +1 @@
1
- import{extractFromTransComponent as e}from"./jsx-parser.js";import{getObjectPropValue as t,getObjectProperty as n}from"./ast-utils.js";class i{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&"object"==typeof n&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const i=n.callee;if("Identifier"===i.type){const t=this.getUseTranslationConfig(i.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===i.type&&"Identifier"===i.property.type&&"getFixedT"===i.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,n,i){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){r="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const s=n.arguments?.[i.nsArg]?.expression;let a;"StringLiteral"===s?.type?a=s.value:"ArrayExpression"===s?.type&&"StringLiteral"===s.elements[0]?.expression.type&&(a=s.elements[0].expression.value);const o=n.arguments?.[i.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,i=t.arguments,r=i[1]?.expression,s=i[2]?.expression,a="StringLiteral"===r?.type?r.value:void 0,o="StringLiteral"===s?.type?s.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const i=this.getFunctionName(e.callee);if(!i)return;const r=this.getVarFromScope(i),s=this.config.extract.functions||["t","*.t"];let a=void 0!==r;if(!a)for(const e of s)if(e.startsWith("*.")){if(i.endsWith(e.substring(1))){a=!0;break}}else if(e===i){a=!0;break}if(!a||0===e.arguments.length)return;const o=e.arguments[0].expression;let l=[],u=!1;if("StringLiteral"===o.type)l.push(o.value);else if("ArrowFunctionExpression"===o.type){const e=this.extractKeyFromSelector(o);e&&(l.push(e),u=!0)}else if("ArrayExpression"===o.type)for(const e of o.elements)"StringLiteral"===e?.expression.type&&l.push(e.expression.value);if(l=l.filter(e=>!!e),0===l.length)return;let p=!1;const f=this.config.extract.pluralSeparator??"_";for(let e=0;e<l.length;e++)l[e].endsWith(`${f}ordinal`)&&(p=!0,l[e]=l[e].slice(0,-8));let c,y;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?y=t:"StringLiteral"===t.type&&(c=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(y=t)}const g=y?t(y,"defaultValue"):void 0,d="string"==typeof g?g:c;for(let e=0;e<l.length;e++){let i,s=l[e];if(y){const e=t(y,"ns");"string"==typeof e&&(i=e)}const a=this.config.extract.nsSeparator??":";if(!i&&a&&s.includes(a)){const e=s.split(a);i=e.shift(),s=e.join(a)}!i&&r?.defaultNs&&(i=r.defaultNs),i||(i=this.config.extract.defaultNS);let o=s;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";o=`${r.keyPrefix}${e}${s}`}const f=e===l.length-1&&d||s;if(y){const e=n(y,"context");if("ConditionalExpression"===e?.value?.type){const t=this.resolvePossibleStringValues(e.value),n=this.config.extract.contextSeparator??"_";if(t.length>0){t.forEach(e=>{this.pluginContext.addKey({key:`${o}${n}${e}`,ns:i,defaultValue:f})}),this.pluginContext.addKey({key:o,ns:i,defaultValue:f});continue}}const r=t(y,"context");if("string"==typeof r&&r){const e=this.config.extract.contextSeparator??"_";this.pluginContext.addKey({key:`${o}${e}${r}`,ns:i,defaultValue:f});continue}const s=void 0!==t(y,"count"),a=!0===t(y,"ordinal");if(s||p){this.handlePluralKeys(o,i,y,a||p);continue}!0===t(y,"returnObjects")&&this.objectKeys.add(o)}u&&this.objectKeys.add(o),this.pluginContext.addKey({key:o,ns:i,defaultValue:f})}}handlePluralKeys(e,n,i,r){try{const s=r?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:s}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t(i,"defaultValue"),u=t(i,`defaultValue${o}other`),p=t(i,`defaultValue${o}ordinal${o}other`);for(const s of a){const a=t(i,r?`defaultValue${o}ordinal${o}${s}`:`defaultValue${o}${s}`);let f;f="string"==typeof a?a:"one"===s&&"string"==typeof l?l:r&&"string"==typeof p?p:r||"string"!=typeof u?"string"==typeof l?l:e:u;const c=r?`${e}${o}ordinal${o}${s}`:`${e}${o}${s}`;this.pluginContext.addKey({key:c,ns:n,defaultValue:f,hasCount:!0,isOrdinal:r})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const s=t(i,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof s?s:e})}}handleSimplePluralKeys(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}".`),this.pluginContext.addKey({key:e,ns:n,defaultValue:t})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e(t,this.config);if(n){if(!n.ns){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===e?.type&&"JSXExpressionContainer"===e.value?.type&&"Identifier"===e.value.expression.type){const t=e.value.expression.value,i=this.getVarFromScope(t);i?.defaultNs&&(n.ns=i.defaultNs)}}if(n.ns||(n.ns=this.config.extract.defaultNS),n.contextExpression&&n.hasCount){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),i=!!e,r=this.resolvePossibleStringValues(n.contextExpression),s=this.config.extract.contextSeparator??"_";if(r.length>0){this.generatePluralKeysForTrans(n.key,n.defaultValue,n.ns,i,n.optionsNode);for(const e of r){const t=`${n.key}${s}${e}`;this.generatePluralKeysForTrans(t,n.defaultValue,n.ns,i,n.optionsNode)}}else this.generatePluralKeysForTrans(n.key,n.defaultValue,n.ns,i,n.optionsNode)}else if(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});"StringLiteral"!==n.contextExpression.type&&this.pluginContext.addKey(n)}}else if(n.hasCount){const e=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),i=!!e;this.generatePluralKeysForTrans(n.key,n.defaultValue,n.ns,i,n.optionsNode)}else this.pluginContext.addKey(n)}}}generatePluralKeysForTrans(e,n,i,r,s){try{const a=r?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;s&&(u=t(s,`defaultValue${l}other`),p=t(s,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=s?t(s,r?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`):void 0;let f;f="string"==typeof o?o:"one"===a&&"string"==typeof n?n:r&&"string"==typeof p?p:r||"string"!=typeof u?"string"==typeof n?n:e:u;const c=r?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:c,ns:i,defaultValue:f,hasCount:!0,isOrdinal:r})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:i,defaultValue:n})}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const 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?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent),...this.resolvePossibleStringValues(e.alternate)]}return"Identifier"===e.type&&e.value,[]}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}}export{i as ASTVisitors};
1
+ import{extractFromTransComponent as e}from"./jsx-parser.js";import{getObjectPropValue as t,getObjectProperty as n}from"./ast-utils.js";class r{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&"object"==typeof n&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const r=n.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,n,r){let s;if("Identifier"===e.id.type&&(s=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(s=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){s="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){s=t.value.value;break}}if(!s)return;const i=n.arguments?.[r.nsArg]?.expression;let a;"StringLiteral"===i?.type?a=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(a=i.elements[0].expression.value);const o=n.arguments?.[r.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(s,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,r=t.arguments,s=r[1]?.expression,i=r[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===i?.type?i.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const r=this.getFunctionName(e.callee);if(!r)return;const s=this.getVarFromScope(r),i=this.config.extract.functions||["t","*.t"];let a=void 0!==s;if(!a)for(const e of i)if(e.startsWith("*.")){if(r.endsWith(e.substring(1))){a=!0;break}}else if(e===r){a=!0;break}if(!a||0===e.arguments.length)return;const{keysToProcess:o,isSelectorAPI:l}=this.handleCallExpressionArgument(e,0);if(0===o.length)return;let u=!1;const p=this.config.extract.pluralSeparator??"_";for(let e=0;e<o.length;e++)o[e].endsWith(`${p}ordinal`)&&(u=!0,o[e]=o[e].slice(0,-8));let f,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(f=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const y=c?t(c,"defaultValue"):void 0,g="string"==typeof y?y:f;for(let e=0;e<o.length;e++){let r,i=o[e];if(c){const e=t(c,"ns");"string"==typeof e&&(r=e)}const a=this.config.extract.nsSeparator??":";if(!r&&a&&i.includes(a)){const e=i.split(a);r=e.shift(),i=e.join(a)}!r&&s?.defaultNs&&(r=s.defaultNs),r||(r=this.config.extract.defaultNS);let p=i;if(s?.keyPrefix){const e=this.config.extract.keySeparator??".";p=`${s.keyPrefix}${e}${i}`}const f=e===o.length-1&&g||i;if(c){const e=n(c,"context"),s=[];if("ConditionalExpression"===e?.value?.type){const t=this.resolvePossibleStringValues(e.value),n=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{s.push({key:`${p}${n}${e}`,ns:r,defaultValue:f})}),s.push({key:p,ns:r,defaultValue:f}))}else if("StringLiteral"===e?.value?.type){const t=e.value.value,n=this.config.extract.contextSeparator??"_";s.push({key:`${p}${n}${t}`,ns:r,defaultValue:f})}const i=void 0!==t(c,"count"),a=!0===t(c,"ordinal");if(i||u){if(s.length>0)for(const{key:e,ns:t}of s)this.handlePluralKeys(e,t,c,a||u);else this.handlePluralKeys(p,r,c,a||u);continue}if(s.length>0){s.forEach(this.pluginContext.addKey);continue}!0===t(c,"returnObjects")&&this.objectKeys.add(p)}l&&this.objectKeys.add(p),this.pluginContext.addKey({key:p,ns:r,defaultValue:f})}}handleCallExpressionArgument(e,t){const n=e.arguments[t].expression,r=[];let s=!1;if("ArrowFunctionExpression"===n.type){const e=this.extractKeyFromSelector(n);e&&(r.push(e),s=!0)}else if("ArrayExpression"===n.type)for(const e of n.elements)e?.expression&&r.push(...this.resolvePossibleStringValues(e.expression));else r.push(...this.resolvePossibleStringValues(n));return{keysToProcess:r.filter(e=>!!e),isSelectorAPI:s}}handlePluralKeys(e,n,r,s){try{const i=s?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t(r,"defaultValue"),u=t(r,`defaultValue${o}other`),p=t(r,`defaultValue${o}ordinal${o}other`);for(const i of a){const a=t(r,s?`defaultValue${o}ordinal${o}${i}`:`defaultValue${o}${i}`);let f;f="string"==typeof a?a:"one"===i&&"string"==typeof l?l:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof l?l:e:u;const c=s?`${e}${o}ordinal${o}${i}`:`${e}${o}${i}`;this.pluginContext.addKey({key:c,ns:n,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(s){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=t(r,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,n){try{const r=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,s=this.config.extract.pluralSeparator??"_";for(const i of r)this.pluginContext.addKey({key:`${e}${s}${i}`,ns:n,defaultValue:t,hasCount:!0})}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:n,defaultValue:t})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e(t,this.config),r=[];if(n){if(n.keyExpression){const e=this.resolvePossibleStringValues(n.keyExpression);r.push(...e)}else r.push(n.serializedChildren);let e;const{contextExpression:s,optionsNode:i,defaultValue:a,hasCount:o,isOrdinal:l,serializedChildren:u}=n;if(n.ns){const{ns:t}=n;e=r.map(e=>({key:e,ns:t,defaultValue:a||u,hasCount:o,isOrdinal:l}))}else{e=r.map(e=>{const t=this.config.extract.nsSeparator??":";let n;if(t&&e.includes(t)){let r;[n,...r]=e.split(t),e=r.join(t)}return{key:e,ns:n,defaultValue:a||u,hasCount:o,isOrdinal:l}});const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===n?.type&&"JSXExpressionContainer"===n.value?.type&&"Identifier"===n.value.expression.type){const t=n.value.expression.value,r=this.getVarFromScope(t);r?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=r.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),s&&o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!n,a=this.resolvePossibleStringValues(s),o=this.config.extract.contextSeparator??"_";if(a.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i));for(const t of a)for(const n of e){const e=`${n.key}${o}${t}`;this.generatePluralKeysForTrans(e,n.defaultValue,n.ns,r,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i))}else if(s){const t=this.resolvePossibleStringValues(s),n=this.config.extract.contextSeparator??"_";if(t.length>0){for(const r of t)for(const{key:t,ns:s,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${n}${r}`,ns:s,defaultValue:i});"StringLiteral"!==s.type&&e.forEach(this.pluginContext.addKey)}}else if(o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!n;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,n,r,s,i){try{const a=s?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t(i,`defaultValue${l}other`),p=t(i,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=i?t(i,s?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`):void 0;let f;f="string"==typeof o?o:"one"===a&&"string"==typeof n?n:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof n?n:e:u;const c=s?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:c,ns:r,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:r,defaultValue:n})}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const r=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)r.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;r.unshift(e.expression.value)}n=n.object}if(r.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return r.join(t)}return null}resolvePossibleStringValues(e){if("StringLiteral"===e.type)return e.value?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent),...this.resolvePossibleStringValues(e.alternate)]}return"Identifier"===e.type&&e.value,[]}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}}export{r as ASTVisitors};
@@ -1 +1 @@
1
- import{getObjectPropValue as e,getObjectProperty as t}from"./ast-utils.js";function n(n,r){const a=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),u=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),p=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),l=!!p,s=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"tOptions"===e.name.value),o="JSXAttribute"===s?.type&&"JSXExpressionContainer"===s.value?.type&&"ObjectExpression"===s.value.expression.type?s.value.expression:void 0,y=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value);let v,f="JSXAttribute"===y?.type&&"JSXExpressionContainer"===y.value?.type?y.value.expression:void 0;if(v="JSXAttribute"===a?.type&&"StringLiteral"===a.value?.type?a.value.value:i(n.children,r),!v)return null;const d=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value);let c;if(c="JSXAttribute"===d?.type&&"StringLiteral"===d.value?.type?d.value.value:void 0,o&&(void 0===c&&(c=e(o,"ns")),void 0===f)){const e=t(o,"context");e?.value&&(f=e.value)}let S=r.extract.defaultValue||"";return S="JSXAttribute"===u?.type&&"StringLiteral"===u.value?.type?u.value.value:i(n.children,r),{key:v,ns:c,defaultValue:S||v,hasCount:l,contextExpression:f,optionsNode:o}}function i(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{n as extractFromTransComponent};
1
+ import{getObjectProperty as e,getObjectPropValue as t}from"./ast-utils.js";function n(n,i){const r=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),a=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),p=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),s=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"values"===e.name.value);let u;p||"JSXAttribute"!==s?.type||"JSXExpressionContainer"!==s.value?.type||"ObjectExpression"!==s.value.expression.type||(u=e(s.value.expression,"count"));const o=!!p||!!u,l=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"tOptions"===e.name.value),y="JSXAttribute"===l?.type&&"JSXExpressionContainer"===l.value?.type&&"ObjectExpression"===l.value.expression.type?l.value.expression:void 0,v=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),d=!!v,f=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value);let c="JSXAttribute"===f?.type&&"JSXExpressionContainer"===f.value?.type?f.value.expression:void 0;const S=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value);let b;if(b="JSXAttribute"===S?.type&&"StringLiteral"===S.value?.type?S.value.value:void 0,y&&(void 0===b&&(b=t(y,"ns")),void 0===c)){const t=e(y,"context");t?.value&&(c=t.value)}const x=function(e,t){const n=new Set(t.extract.transKeepBasicHtmlNodesFor??["br","strong","i","p"]);function i(e){let t="";return e.forEach((e,r)=>{if("JSXText"===e.type)t+=e.value;else if("JSXExpressionContainer"===e.type){const n=e.expression;if("StringLiteral"===n.type)t+=n.value;else if("Identifier"===n.type)t+=`{{${n.value}}}`;else if("ObjectExpression"===n.type){const e=n.properties[0];e&&"Identifier"===e.type&&(t+=`{{${e.value}}}`)}}else if("JSXElement"===e.type){let a;"Identifier"===e.opening.name.type&&(a=e.opening.name.value);const p=i(e.children);a&&n.has(a)?t+=`<${a}>${p}</${a}>`:t+=`<${r}>${p}</${r}>`}else"JSXFragment"===e.type&&(t+=i(e.children))}),t}return i(e).trim().replace(/\s{2,}/g," ")}(n.children,i);let m,J=i.extract.defaultValue||"";return"JSXAttribute"===a?.type&&"StringLiteral"===a.value?.type&&(J=a.value.value),"JSXAttribute"!==r?.type||("StringLiteral"===r.value?.type?m=r.value:"JSXExpressionContainer"===r.value?.type&&"JSXEmptyExpression"!==r.value.expression.type&&(m=r.value.expression),m)?{keyExpression:m,serializedChildren:x,ns:b,defaultValue:J,hasCount:o,isOrdinal:d,contextExpression:c,optionsNode:y}:null}export{n as extractFromTransComponent};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.5.8",
3
+ "version": "1.5.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('1.5.8')
24
+ .version('1.5.9')
25
25
 
26
26
  program
27
27
  .command('extract')
@@ -15,15 +15,16 @@ import type { ObjectExpression } from '@swc/core'
15
15
  * @param propName - The property name to locate
16
16
  * @returns The matching KeyValueProperty node if found, otherwise undefined.
17
17
  */
18
- export function getObjectProperty (object: ObjectExpression, propName: string): any {
19
- return (object.properties).find(
20
- (p) =>
21
- p.type === 'KeyValueProperty' &&
22
- (
23
- (p.key?.type === 'Identifier' && p.key.value === propName) ||
24
- (p.key?.type === 'StringLiteral' && p.key.value === propName)
25
- )
26
- )
18
+ export function getObjectProperty (object: ObjectExpression, propName: string) {
19
+ return (object.properties).filter(
20
+ (p) => p.type === 'KeyValueProperty')
21
+ .find(
22
+ (p) =>
23
+ (
24
+ (p.key?.type === 'Identifier' && p.key.value === propName) ||
25
+ (p.key?.type === 'StringLiteral' && p.key.value === propName)
26
+ )
27
+ )
27
28
  }
28
29
 
29
30
  /**
@@ -1,5 +1,5 @@
1
1
  import type { Module, Node, CallExpression, VariableDeclarator, JSXElement, ArrowFunctionExpression, ObjectExpression, Expression } from '@swc/core'
2
- import type { PluginContext, I18nextToolkitConfig, Logger } from '../../types'
2
+ import type { PluginContext, I18nextToolkitConfig, Logger, ExtractedKey } from '../../types'
3
3
  import { extractFromTransComponent } from './jsx-parser'
4
4
  import { getObjectProperty, getObjectPropValue } from './ast-utils'
5
5
 
@@ -218,11 +218,11 @@ export class ASTVisitors {
218
218
 
219
219
  // Determine the actual call expression, looking inside AwaitExpressions.
220
220
  const callExpr =
221
- init.type === 'AwaitExpression' && init.argument.type === 'CallExpression'
222
- ? init.argument
223
- : init.type === 'CallExpression'
224
- ? init
225
- : null
221
+ init.type === 'AwaitExpression' && init.argument.type === 'CallExpression'
222
+ ? init.argument
223
+ : init.type === 'CallExpression'
224
+ ? init
225
+ : null
226
226
 
227
227
  if (!callExpr) return
228
228
 
@@ -394,28 +394,8 @@ export class ASTVisitors {
394
394
  }
395
395
  if (!isFunctionToParse || node.arguments.length === 0) return
396
396
 
397
- const firstArg = node.arguments[0].expression
398
- let keysToProcess: string[] = []
399
- let isSelectorAPI = false
397
+ const { keysToProcess, isSelectorAPI } = this.handleCallExpressionArgument(node, 0)
400
398
 
401
- if (firstArg.type === 'StringLiteral') {
402
- keysToProcess.push(firstArg.value)
403
- } else if (firstArg.type === 'ArrowFunctionExpression') {
404
- const key = this.extractKeyFromSelector(firstArg)
405
- if (key) {
406
- keysToProcess.push(key)
407
- isSelectorAPI = true
408
- }
409
- } else if (firstArg.type === 'ArrayExpression') {
410
- for (const element of firstArg.elements) {
411
- // We only extract static string literals from the array
412
- if (element?.expression.type === 'StringLiteral') {
413
- keysToProcess.push(element.expression.value)
414
- }
415
- }
416
- }
417
-
418
- keysToProcess = keysToProcess.filter(key => !!key)
419
399
  if (keysToProcess.length === 0) return
420
400
 
421
401
  let isOrdinalByKey = false
@@ -484,56 +464,106 @@ export class ASTVisitors {
484
464
  if (options) {
485
465
  const contextProp = getObjectProperty(options, 'context')
486
466
 
487
- // 1. Handle Dynamic Context (Ternary) first
467
+ const keysWithContext: ExtractedKey[] = []
468
+
469
+ // 1. Handle Context
488
470
  if (contextProp?.value?.type === 'ConditionalExpression') {
489
471
  const contextValues = this.resolvePossibleStringValues(contextProp.value)
490
472
  const contextSeparator = this.config.extract.contextSeparator ?? '_'
491
473
 
492
474
  if (contextValues.length > 0) {
493
475
  contextValues.forEach(context => {
494
- this.pluginContext.addKey({ key: `${finalKey}${contextSeparator}${context}`, ns, defaultValue: dv })
476
+ keysWithContext.push({ key: `${finalKey}${contextSeparator}${context}`, ns, defaultValue: dv })
495
477
  })
496
478
  // For dynamic context, also add the base key as a fallback
497
- this.pluginContext.addKey({ key: finalKey, ns, defaultValue: dv })
498
- continue // This key is fully handled, move to the next in the array
479
+ keysWithContext.push({ key: finalKey, ns, defaultValue: dv })
499
480
  }
500
- }
481
+ } else if (contextProp?.value?.type === 'StringLiteral') {
482
+ const contextValue = contextProp.value.value
501
483
 
502
- // 2. Handle Static Context
503
- const contextValue = getObjectPropValue(options, 'context')
504
- if (typeof contextValue === 'string' && contextValue) {
505
484
  const contextSeparator = this.config.extract.contextSeparator ?? '_'
506
- this.pluginContext.addKey({ key: `${finalKey}${contextSeparator}${contextValue}`, ns, defaultValue: dv })
507
- continue // This key is fully handled
485
+ keysWithContext.push({ key: `${finalKey}${contextSeparator}${contextValue}`, ns, defaultValue: dv })
508
486
  }
509
487
 
510
- // 3. Handle Plurals
488
+ // 2. Handle Plurals
511
489
  const hasCount = getObjectPropValue(options, 'count') !== undefined
512
490
  const isOrdinalByOption = getObjectPropValue(options, 'ordinal') === true
513
491
  if (hasCount || isOrdinalByKey) {
514
- // Pass the combined ordinal flag to the handler
515
- this.handlePluralKeys(finalKey, ns, options, isOrdinalByOption || isOrdinalByKey)
516
- continue
492
+ // If we have keys with context pluralize them
493
+ if (keysWithContext.length > 0) {
494
+ for (const { key, ns } of keysWithContext) {
495
+ // Pass the combined ordinal flag to the handler
496
+ this.handlePluralKeys(key, ns, options, isOrdinalByOption || isOrdinalByKey)
497
+ }
498
+ } else {
499
+ // Otherwise pluralize the base key
500
+ this.handlePluralKeys(finalKey, ns, options, isOrdinalByOption || isOrdinalByKey)
501
+ }
502
+
503
+ continue // This key is fully handled
504
+ }
505
+
506
+ if (keysWithContext.length > 0) {
507
+ keysWithContext.forEach(this.pluginContext.addKey)
508
+
509
+ continue // This key is now fully handled
517
510
  }
518
511
 
519
- // 4. Handle returnObjects
512
+ // 3. Handle returnObjects
520
513
  if (getObjectPropValue(options, 'returnObjects') === true) {
521
514
  this.objectKeys.add(finalKey)
522
515
  // Fall through to add the base key itself
523
516
  }
524
517
  }
525
518
 
526
- // 5. Handle selector API as implicit returnObjects
519
+ // 4. Handle selector API as implicit returnObjects
527
520
  if (isSelectorAPI) {
528
521
  this.objectKeys.add(finalKey)
529
522
  // Fall through to add the base key itself
530
523
  }
531
524
 
532
- // 6. Default case: Add the simple key
525
+ // 5. Default case: Add the simple key
533
526
  this.pluginContext.addKey({ key: finalKey, ns, defaultValue: dv })
534
527
  }
535
528
  }
536
529
 
530
+ /**
531
+ * Processed a call expression to extract keys from the specified argument.
532
+ *
533
+ * @param node - The call expression node
534
+ * @param argIndex - The index of the argument to process
535
+ * @returns An object containing the keys to process and a flag indicating if the selector API is used
536
+ */
537
+ private handleCallExpressionArgument (
538
+ node: CallExpression,
539
+ argIndex: number
540
+ ): { keysToProcess: string[]; isSelectorAPI: boolean } {
541
+ const firstArg = node.arguments[argIndex].expression
542
+ const keysToProcess: string[] = []
543
+ let isSelectorAPI = false
544
+
545
+ if (firstArg.type === 'ArrowFunctionExpression') {
546
+ const key = this.extractKeyFromSelector(firstArg)
547
+ if (key) {
548
+ keysToProcess.push(key)
549
+ isSelectorAPI = true
550
+ }
551
+ } else if (firstArg.type === 'ArrayExpression') {
552
+ for (const element of firstArg.elements) {
553
+ if (element?.expression) {
554
+ keysToProcess.push(...this.resolvePossibleStringValues(element.expression))
555
+ }
556
+ }
557
+ } else {
558
+ keysToProcess.push(...this.resolvePossibleStringValues(firstArg))
559
+ }
560
+
561
+ return {
562
+ keysToProcess: keysToProcess.filter((key) => !!key),
563
+ isSelectorAPI,
564
+ }
565
+ }
566
+
537
567
  /**
538
568
  * Generates plural form keys based on the primary language's plural rules.
539
569
  *
@@ -649,10 +679,41 @@ export class ASTVisitors {
649
679
  const elementName = this.getElementName(node)
650
680
 
651
681
  if (elementName && (this.config.extract.transComponents || ['Trans']).includes(elementName)) {
652
- const extractedKey = extractFromTransComponent(node, this.config)
653
- if (extractedKey) {
654
- // If ns is not explicitly set on the component, try to find it from the `t` prop
655
- if (!extractedKey.ns) {
682
+ const extractedAttributes = extractFromTransComponent(node, this.config)
683
+
684
+ const keysToProcess: string[] = []
685
+
686
+ if (extractedAttributes) {
687
+ if (extractedAttributes.keyExpression) {
688
+ const keyValues = this.resolvePossibleStringValues(extractedAttributes.keyExpression)
689
+ keysToProcess.push(...keyValues)
690
+ } else {
691
+ keysToProcess.push(extractedAttributes.serializedChildren)
692
+ }
693
+
694
+ let extractedKeys: ExtractedKey[]
695
+
696
+ const { contextExpression, optionsNode, defaultValue, hasCount, isOrdinal, serializedChildren } = extractedAttributes
697
+
698
+ // If ns is not explicitly set on the component, try to find it from the key
699
+ // or the `t` prop
700
+ if (!extractedAttributes.ns) {
701
+ extractedKeys = keysToProcess.map(key => {
702
+ const nsSeparator = this.config.extract.nsSeparator ?? ':'
703
+ let ns: string | undefined
704
+
705
+ // If the key contains a namespace separator, it takes precedence
706
+ // over the default t ns value
707
+ if (nsSeparator && key.includes(nsSeparator)) {
708
+ let parts: string[]
709
+ ([ns, ...parts] = key.split(nsSeparator))
710
+
711
+ key = parts.join(nsSeparator)
712
+ }
713
+
714
+ return { key, ns, defaultValue: defaultValue || serializedChildren, hasCount, isOrdinal }
715
+ })
716
+
656
717
  const tProp = node.opening.attributes?.find(
657
718
  attr =>
658
719
  attr.type === 'JSXAttribute' &&
@@ -669,70 +730,86 @@ export class ASTVisitors {
669
730
  const tIdentifier = tProp.value.expression.value
670
731
  const scopeInfo = this.getVarFromScope(tIdentifier)
671
732
  if (scopeInfo?.defaultNs) {
672
- extractedKey.ns = scopeInfo.defaultNs
733
+ extractedKeys.forEach(key => {
734
+ if (!key.ns) {
735
+ key.ns = scopeInfo.defaultNs
736
+ }
737
+ })
673
738
  }
674
739
  }
740
+ } else {
741
+ const { ns } = extractedAttributes
742
+ extractedKeys = keysToProcess.map(key => {
743
+ return { key, ns, defaultValue: defaultValue || serializedChildren, hasCount, isOrdinal, }
744
+ })
675
745
  }
676
746
 
677
- // Apply defaultNS from config if no namespace was found on the component
678
- if (!extractedKey.ns) {
679
- extractedKey.ns = this.config.extract.defaultNS
680
- }
747
+ extractedKeys.forEach(key => {
748
+ // Apply defaultNS from config if no namespace was found on the component and
749
+ // the key does not contain a namespace prefix
750
+ if (!key.ns) {
751
+ key.ns = this.config.extract.defaultNS
752
+ }
753
+ })
681
754
 
682
755
  // Handle the combination of context and count
683
- if (extractedKey.contextExpression && extractedKey.hasCount) {
756
+ if (contextExpression && hasCount) {
684
757
  // Find isOrdinal prop on the <Trans> component
685
758
  const ordinalAttr = node.opening.attributes?.find(
686
759
  (attr) =>
687
760
  attr.type === 'JSXAttribute' &&
688
- attr.name.type === 'Identifier' &&
689
- attr.name.value === 'ordinal'
761
+ attr.name.type === 'Identifier' &&
762
+ attr.name.value === 'ordinal'
690
763
  )
691
764
  const isOrdinal = !!ordinalAttr
692
765
 
693
- const contextValues = this.resolvePossibleStringValues(extractedKey.contextExpression)
766
+ const contextValues = this.resolvePossibleStringValues(contextExpression)
694
767
  const contextSeparator = this.config.extract.contextSeparator ?? '_'
695
768
 
696
769
  // Generate all combinations of context and plural forms
697
770
  if (contextValues.length > 0) {
698
771
  // Generate base plural forms (no context)
699
- this.generatePluralKeysForTrans(extractedKey.key, extractedKey.defaultValue, extractedKey.ns, isOrdinal, extractedKey.optionsNode)
772
+ extractedKeys.forEach(extractedKey => this.generatePluralKeysForTrans(extractedKey.key, extractedKey.defaultValue, extractedKey.ns, isOrdinal, optionsNode))
700
773
 
701
774
  // Generate context + plural combinations
702
775
  for (const context of contextValues) {
703
- const contextKey = `${extractedKey.key}${contextSeparator}${context}`
704
- this.generatePluralKeysForTrans(contextKey, extractedKey.defaultValue, extractedKey.ns, isOrdinal, extractedKey.optionsNode)
776
+ for (const extractedKey of extractedKeys) {
777
+ const contextKey = `${extractedKey.key}${contextSeparator}${context}`
778
+ this.generatePluralKeysForTrans(contextKey, extractedKey.defaultValue, extractedKey.ns, isOrdinal, optionsNode)
779
+ }
705
780
  }
706
781
  } else {
707
782
  // Fallback to just plural forms if context resolution fails
708
- this.generatePluralKeysForTrans(extractedKey.key, extractedKey.defaultValue, extractedKey.ns, isOrdinal, extractedKey.optionsNode)
783
+ extractedKeys.forEach(extractedKey => this.generatePluralKeysForTrans(extractedKey.key, extractedKey.defaultValue, extractedKey.ns, isOrdinal, optionsNode))
709
784
  }
710
- } else if (extractedKey.contextExpression) {
711
- const contextValues = this.resolvePossibleStringValues(extractedKey.contextExpression)
785
+ } else if (contextExpression) {
786
+ const contextValues = this.resolvePossibleStringValues(contextExpression)
712
787
  const contextSeparator = this.config.extract.contextSeparator ?? '_'
713
788
 
714
789
  if (contextValues.length > 0) {
715
790
  for (const context of contextValues) {
716
- this.pluginContext.addKey({ key: `${extractedKey.key}${contextSeparator}${context}`, ns: extractedKey.ns, defaultValue: extractedKey.defaultValue })
791
+ for (const { key, ns, defaultValue } of extractedKeys) {
792
+ this.pluginContext.addKey({ key: `${key}${contextSeparator}${context}`, ns, defaultValue })
793
+ }
717
794
  }
718
795
  // Only add the base key as a fallback if the context is dynamic (i.e., not a simple string).
719
- if (extractedKey.contextExpression.type !== 'StringLiteral') {
720
- this.pluginContext.addKey(extractedKey)
796
+ if (contextExpression.type !== 'StringLiteral') {
797
+ extractedKeys.forEach(this.pluginContext.addKey)
721
798
  }
722
799
  }
723
- } else if (extractedKey.hasCount) {
800
+ } else if (hasCount) {
724
801
  // Find isOrdinal prop on the <Trans> component
725
802
  const ordinalAttr = node.opening.attributes?.find(
726
803
  (attr) =>
727
804
  attr.type === 'JSXAttribute' &&
728
- attr.name.type === 'Identifier' &&
729
- attr.name.value === 'ordinal'
805
+ attr.name.type === 'Identifier' &&
806
+ attr.name.value === 'ordinal'
730
807
  )
731
808
  const isOrdinal = !!ordinalAttr
732
809
 
733
- this.generatePluralKeysForTrans(extractedKey.key, extractedKey.defaultValue, extractedKey.ns, isOrdinal, extractedKey.optionsNode)
810
+ extractedKeys.forEach(extractedKey => this.generatePluralKeysForTrans(extractedKey.key, extractedKey.defaultValue, extractedKey.ns, isOrdinal, optionsNode))
734
811
  } else {
735
- this.pluginContext.addKey(extractedKey)
812
+ extractedKeys.forEach(this.pluginContext.addKey)
736
813
  }
737
814
  }
738
815
  }
@@ -1,7 +1,33 @@
1
- import type { JSXElement } from '@swc/core'
2
- import type { ExtractedKey, I18nextToolkitConfig } from '../../types'
1
+ import type { Expression, JSXElement, ObjectExpression, Property } from '@swc/core'
2
+ import type { I18nextToolkitConfig } from '../../types'
3
3
  import { getObjectProperty, getObjectPropValue } from './ast-utils'
4
4
 
5
+ export interface ExtractedJSXAttributes {
6
+ /** holds the raw key expression from the AST */
7
+ keyExpression?: Expression;
8
+
9
+ /** holds the serialized JSX children from the AST */
10
+ serializedChildren: string;
11
+
12
+ /** Default value to use in the primary language */
13
+ defaultValue?: string;
14
+
15
+ /** Namespace this key belongs to (if defined on <Trans />) */
16
+ ns?: string;
17
+
18
+ /** Whether this key is used with pluralization (count parameter) */
19
+ hasCount?: boolean;
20
+
21
+ /** Whether this key is used with ordinal pluralization */
22
+ isOrdinal?: boolean;
23
+
24
+ /** AST node for options object, used for advanced plural handling in Trans */
25
+ optionsNode?: ObjectExpression;
26
+
27
+ /** hold the raw context expression from the AST */
28
+ contextExpression?: Expression;
29
+ }
30
+
5
31
  /**
6
32
  * Extracts translation keys from JSX Trans components.
7
33
  *
@@ -27,13 +53,14 @@ import { getObjectProperty, getObjectPropValue } from './ast-utils'
27
53
  * const result = extractFromTransComponent(jsxNode, config)
28
54
  * // Returns: {
29
55
  * // key: 'welcome.title',
56
+ * // keyExpression: { ... },
30
57
  * // ns: 'home',
31
58
  * // defaultValue: 'Welcome!',
32
59
  * // hasCount: false
33
60
  * // }
34
61
  * ```
35
62
  */
36
- export function extractFromTransComponent (node: JSXElement, config: I18nextToolkitConfig): ExtractedKey | null {
63
+ export function extractFromTransComponent (node: JSXElement, config: I18nextToolkitConfig): ExtractedJSXAttributes | null {
37
64
  const i18nKeyAttr = node.opening.attributes?.find(
38
65
  (attr) =>
39
66
  attr.type === 'JSXAttribute' &&
@@ -54,7 +81,23 @@ export function extractFromTransComponent (node: JSXElement, config: I18nextTool
54
81
  attr.name.type === 'Identifier' &&
55
82
  attr.name.value === 'count'
56
83
  )
57
- const hasCount = !!countAttr
84
+
85
+ const valuesAttr = node.opening.attributes?.find(
86
+ (attr) => attr.type === 'JSXAttribute' && attr.name.type === 'Identifier' && attr.name.value === 'values'
87
+ )
88
+
89
+ // Find the 'count' property in the 'values' object if count={...} is not defined
90
+ let valuesCountProperty: Property | undefined
91
+ if (
92
+ !countAttr &&
93
+ valuesAttr?.type === 'JSXAttribute' &&
94
+ valuesAttr.value?.type === 'JSXExpressionContainer' &&
95
+ valuesAttr.value.expression.type === 'ObjectExpression'
96
+ ) {
97
+ valuesCountProperty = getObjectProperty(valuesAttr.value.expression, 'count')
98
+ }
99
+
100
+ const hasCount = !!countAttr || !!valuesCountProperty
58
101
 
59
102
  const tOptionsAttr = node.opening.attributes?.find(
60
103
  (attr) =>
@@ -66,27 +109,25 @@ export function extractFromTransComponent (node: JSXElement, config: I18nextTool
66
109
  ? tOptionsAttr.value.expression
67
110
  : undefined
68
111
 
112
+ // Find isOrdinal prop on the <Trans> component
113
+ const ordinalAttr = node.opening.attributes?.find(
114
+ (attr) =>
115
+ attr.type === 'JSXAttribute' &&
116
+ attr.name.type === 'Identifier' &&
117
+ attr.name.value === 'ordinal'
118
+ )
119
+ const isOrdinal = !!ordinalAttr
120
+
69
121
  const contextAttr = node.opening.attributes?.find(
70
122
  (attr) =>
71
123
  attr.type === 'JSXAttribute' &&
72
- attr.name.type === 'Identifier' &&
73
- attr.name.value === 'context'
124
+ attr.name.type === 'Identifier' &&
125
+ attr.name.value === 'context'
74
126
  )
75
127
  let contextExpression = (contextAttr?.type === 'JSXAttribute' && contextAttr.value?.type === 'JSXExpressionContainer')
76
128
  ? contextAttr.value.expression
77
129
  : undefined
78
130
 
79
- let key: string
80
- if (i18nKeyAttr?.type === 'JSXAttribute' && i18nKeyAttr.value?.type === 'StringLiteral') {
81
- key = i18nKeyAttr.value.value
82
- } else {
83
- key = serializeJSXChildren(node.children, config)
84
- }
85
-
86
- if (!key) {
87
- return null
88
- }
89
-
90
131
  // 1. Prioritize direct props for 'ns' and 'context'
91
132
  const nsAttr = node.opening.attributes?.find(attr => attr.type === 'JSXAttribute' && attr.name.type === 'Identifier' && attr.name.value === 'ns')
92
133
  let ns: string | undefined
@@ -109,14 +150,37 @@ export function extractFromTransComponent (node: JSXElement, config: I18nextTool
109
150
  }
110
151
  }
111
152
 
153
+ const serialized = serializeJSXChildren(node.children, config)
154
+
112
155
  let defaultValue = config.extract.defaultValue || ''
113
156
  if (defaultsAttr?.type === 'JSXAttribute' && defaultsAttr.value?.type === 'StringLiteral') {
114
157
  defaultValue = defaultsAttr.value.value
115
- } else {
116
- defaultValue = serializeJSXChildren(node.children, config)
117
158
  }
118
159
 
119
- return { key, ns, defaultValue: defaultValue || key, hasCount, contextExpression, optionsNode }
160
+ let keyExpression: Expression | undefined
161
+ if (i18nKeyAttr?.type === 'JSXAttribute') {
162
+ if (i18nKeyAttr.value?.type === 'StringLiteral') {
163
+ keyExpression = i18nKeyAttr.value
164
+ } else if (
165
+ i18nKeyAttr.value?.type === 'JSXExpressionContainer' &&
166
+ i18nKeyAttr.value.expression.type !== 'JSXEmptyExpression'
167
+ ) {
168
+ keyExpression = i18nKeyAttr.value.expression
169
+ }
170
+
171
+ if (!keyExpression) return null
172
+ }
173
+
174
+ return {
175
+ keyExpression,
176
+ serializedChildren: serialized,
177
+ ns,
178
+ defaultValue,
179
+ hasCount,
180
+ isOrdinal,
181
+ contextExpression,
182
+ optionsNode,
183
+ }
120
184
  }
121
185
 
122
186
  /**
@@ -14,7 +14,7 @@ import type { ObjectExpression } from '@swc/core';
14
14
  * @param propName - The property name to locate
15
15
  * @returns The matching KeyValueProperty node if found, otherwise undefined.
16
16
  */
17
- export declare function getObjectProperty(object: ObjectExpression, propName: string): any;
17
+ export declare function getObjectProperty(object: ObjectExpression, propName: string): import("@swc/types").KeyValueProperty | undefined;
18
18
  /**
19
19
  * Extracts string value from object property.
20
20
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAEjD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,CASlF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAWrH"}
1
+ {"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAEjD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,qDAU5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAWrH"}
@@ -152,6 +152,14 @@ export declare class ASTVisitors {
152
152
  * @private
153
153
  */
154
154
  private handleCallExpression;
155
+ /**
156
+ * Processed a call expression to extract keys from the specified argument.
157
+ *
158
+ * @param node - The call expression node
159
+ * @param argIndex - The index of the argument to process
160
+ * @returns An object containing the keys to process and a flag indicating if the selector API is used
161
+ */
162
+ private handleCallExpressionArgument;
155
163
  /**
156
164
  * Generates plural form keys based on the primary language's plural rules.
157
165
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA+G,MAAM,WAAW,CAAA;AACpJ,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAqB9E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,UAAU,CAAoC;IAE/C,UAAU,cAAoB;IAErC;;;;;;OAMG;gBAED,MAAM,EAAE,oBAAoB,EAC5B,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM;IAOhB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IAsDZ;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IASvB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,wBAAwB;IAmChC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,8BAA8B;IAwDtC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IAuK5B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,gBAAgB;IA4DxB;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAkB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IA6FxB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,0BAA0B;IA6DlC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,2BAA2B;IAoBnC;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA+G,MAAM,WAAW,CAAA;AACpJ,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,MAAM,aAAa,CAAA;AAqB5F;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,UAAU,CAAoC;IAE/C,UAAU,cAAoB;IAErC;;;;;;OAMG;gBAED,MAAM,EAAE,oBAAoB,EAC5B,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM;IAOhB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IAsDZ;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IASvB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,wBAAwB;IAmChC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,8BAA8B;IAwDtC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IAgK5B;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,gBAAgB;IA4DxB;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAkB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IA4IxB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,0BAA0B;IA6DlC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,2BAA2B;IAoBnC;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
@@ -1,5 +1,23 @@
1
- import type { JSXElement } from '@swc/core';
2
- import type { ExtractedKey, I18nextToolkitConfig } from '../../types';
1
+ import type { Expression, JSXElement, ObjectExpression } from '@swc/core';
2
+ import type { I18nextToolkitConfig } from '../../types';
3
+ export interface ExtractedJSXAttributes {
4
+ /** holds the raw key expression from the AST */
5
+ keyExpression?: Expression;
6
+ /** holds the serialized JSX children from the AST */
7
+ serializedChildren: string;
8
+ /** Default value to use in the primary language */
9
+ defaultValue?: string;
10
+ /** Namespace this key belongs to (if defined on <Trans />) */
11
+ ns?: string;
12
+ /** Whether this key is used with pluralization (count parameter) */
13
+ hasCount?: boolean;
14
+ /** Whether this key is used with ordinal pluralization */
15
+ isOrdinal?: boolean;
16
+ /** AST node for options object, used for advanced plural handling in Trans */
17
+ optionsNode?: ObjectExpression;
18
+ /** hold the raw context expression from the AST */
19
+ contextExpression?: Expression;
20
+ }
3
21
  /**
4
22
  * Extracts translation keys from JSX Trans components.
5
23
  *
@@ -25,11 +43,12 @@ import type { ExtractedKey, I18nextToolkitConfig } from '../../types';
25
43
  * const result = extractFromTransComponent(jsxNode, config)
26
44
  * // Returns: {
27
45
  * // key: 'welcome.title',
46
+ * // keyExpression: { ... },
28
47
  * // ns: 'home',
29
48
  * // defaultValue: 'Welcome!',
30
49
  * // hasCount: false
31
50
  * // }
32
51
  * ```
33
52
  */
34
- export declare function extractFromTransComponent(node: JSXElement, config: I18nextToolkitConfig): ExtractedKey | null;
53
+ export declare function extractFromTransComponent(node: JSXElement, config: I18nextToolkitConfig): ExtractedJSXAttributes | null;
35
54
  //# sourceMappingURL=jsx-parser.d.ts.map
@@ -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;AAGrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,yBAAyB,CAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,GAAG,YAAY,GAAG,IAAI,CAoF9G"}
1
+ {"version":3,"file":"jsx-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAY,MAAM,WAAW,CAAA;AACnF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAGvD,MAAM,WAAW,sBAAsB;IACrC,gDAAgD;IAChD,aAAa,CAAC,EAAE,UAAU,CAAC;IAE3B,qDAAqD;IACrD,kBAAkB,EAAE,MAAM,CAAC;IAE3B,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,8DAA8D;IAC9D,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAE/B,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,UAAU,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,yBAAyB,CAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,GAAG,IAAI,CAyHxH"}