i18next-cli 1.5.10 → 1.5.11

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,10 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.5.11](https://github.com/i18next/i18next-cli/compare/v1.5.10...v1.5.11) - 2025-10-05
9
+
10
+ - **Extractor:** Fixed handling of empty strings in template literals where conditional expressions could result in empty string concatenation (e.g., `` t(`key${condition ? '.suffix' : ''}`) ``). Empty strings are now properly filtered out during template literal resolution, ensuring only valid key variants are generated. [#41](https://github.com/i18next/i18next-cli/pull/41)
11
+
8
12
  ## [1.5.10](https://github.com/i18next/i18next-cli/compare/v1.5.9...v1.5.10) - 2025-10-05
9
13
 
10
14
  - **Extractor:** Added support for template literals as translation keys in both `t()` functions and `<Trans>` components. The extractor can now resolve complex template strings with nested expressions, ternary operators, and mixed data types to extract all possible key variants (e.g., `` t(`state.${isDone ? 'done' : 'notDone'}.title`) `` generates both `state.done.title` and `state.notDone.title`). [#39](https://github.com/i18next/i18next-cli/pull/39)
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.10"),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.11"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").option("--dry-run","Run the extractor without writing any files to disk.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a,{isWatchMode:e.watch,isDryRun:e.dryRun});e.ci&&t&&(console.error(o.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(o.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${o.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let n=await i.loadConfig();if(!n){console.log(o.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),n=e}await g.runStatus(n,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const o=await i.ensureConfig(),a=()=>c.runTypesGenerator(o);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(o.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await l.runMigrator(e)}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const r=async()=>{let e=await i.loadConfig();if(!e){console.log(o.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),e=t}await d.runLinter(e)};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await i.loadConfig();if(e?.extract?.input){t.watch(await n.glob(e.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
@@ -1 +1 @@
1
- "use strict";var e=require("./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&&"undefined"===e.value?[]:"TemplateLiteral"===e.type?this.resolvePossibleStringValuesFromTemplateString(e):"NumericLiteral"===e.type||"BooleanLiteral"===e.type?[`${e.value}`]:[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...n]=e.quasis;return e.expressions.reduce((e,t,r)=>e.flatMap(e=>{const s=n[r]?.cooked??"";return this.resolvePossibleStringValues(t).map(t=>`${e}${t}${s}`)}),[t.cooked??""])}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}};
1
+ "use strict";var e=require("./jsx-parser.js"),t=require("./ast-utils.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&"object"==typeof n&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const r=n.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,n,r){let s;if("Identifier"===e.id.type&&(s=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(s=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){s="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){s=t.value.value;break}}if(!s)return;const i=n.arguments?.[r.nsArg]?.expression;let a;"StringLiteral"===i?.type?a=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(a=i.elements[0].expression.value);const o=n.arguments?.[r.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t.getObjectPropValue(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(s,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,r=t.arguments,s=r[1]?.expression,i=r[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===i?.type?i.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const n=this.getFunctionName(e.callee);if(!n)return;const r=this.getVarFromScope(n),s=this.config.extract.functions||["t","*.t"];let i=void 0!==r;if(!i)for(const e of s)if(e.startsWith("*.")){if(n.endsWith(e.substring(1))){i=!0;break}}else if(e===n){i=!0;break}if(!i||0===e.arguments.length)return;const{keysToProcess:a,isSelectorAPI:o}=this.handleCallExpressionArgument(e,0);if(0===a.length)return;let l=!1;const u=this.config.extract.pluralSeparator??"_";for(let e=0;e<a.length;e++)a[e].endsWith(`${u}ordinal`)&&(l=!0,a[e]=a[e].slice(0,-8));let p,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(p=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const f=c?t.getObjectPropValue(c,"defaultValue"):void 0,g="string"==typeof f?f:p;for(let e=0;e<a.length;e++){let n,s=a[e];if(c){const e=t.getObjectPropValue(c,"ns");"string"==typeof e&&(n=e)}const i=this.config.extract.nsSeparator??":";if(!n&&i&&s.includes(i)){const e=s.split(i);n=e.shift(),s=e.join(i)}!n&&r?.defaultNs&&(n=r.defaultNs),n||(n=this.config.extract.defaultNS);let u=s;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";u=`${r.keyPrefix}${e}${s}`}const p=e===a.length-1&&g||s;if(c){const e=t.getObjectProperty(c,"context"),r=[];if("ConditionalExpression"===e?.value?.type){const t=this.resolvePossibleStringValues(e.value),s=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{r.push({key:`${u}${s}${e}`,ns:n,defaultValue:p})}),r.push({key:u,ns:n,defaultValue:p}))}else if("StringLiteral"===e?.value?.type){const t=e.value.value,s=this.config.extract.contextSeparator??"_";r.push({key:`${u}${s}${t}`,ns:n,defaultValue:p})}const s=void 0!==t.getObjectPropValue(c,"count"),i=!0===t.getObjectPropValue(c,"ordinal");if(s||l){if(r.length>0)for(const{key:e,ns:t}of r)this.handlePluralKeys(e,t,c,i||l);else this.handlePluralKeys(u,n,c,i||l);continue}if(r.length>0){r.forEach(this.pluginContext.addKey);continue}!0===t.getObjectPropValue(c,"returnObjects")&&this.objectKeys.add(u)}o&&this.objectKeys.add(u),this.pluginContext.addKey({key:u,ns:n,defaultValue:p})}}handleCallExpressionArgument(e,t){const n=e.arguments[t].expression,r=[];let s=!1;if("ArrowFunctionExpression"===n.type){const e=this.extractKeyFromSelector(n);e&&(r.push(e),s=!0)}else if("ArrayExpression"===n.type)for(const e of n.elements)e?.expression&&r.push(...this.resolvePossibleStringValues(e.expression));else r.push(...this.resolvePossibleStringValues(n));return{keysToProcess:r.filter(e=>!!e),isSelectorAPI:s}}handlePluralKeys(e,n,r,s){try{const i=s?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t.getObjectPropValue(r,"defaultValue"),u=t.getObjectPropValue(r,`defaultValue${o}other`),p=t.getObjectPropValue(r,`defaultValue${o}ordinal${o}other`);for(const i of a){const a=s?`defaultValue${o}ordinal${o}${i}`:`defaultValue${o}${i}`,c=t.getObjectPropValue(r,a);let f;f="string"==typeof c?c:"one"===i&&"string"==typeof l?l:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof l?l:e:u;const g=s?`${e}${o}ordinal${o}${i}`:`${e}${o}${i}`;this.pluginContext.addKey({key:g,ns:n,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(s){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=t.getObjectPropValue(r,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,n){try{const r=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,s=this.config.extract.pluralSeparator??"_";for(const i of r)this.pluginContext.addKey({key:`${e}${s}${i}`,ns:n,defaultValue:t,hasCount:!0})}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:n,defaultValue:t})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e.extractFromTransComponent(t,this.config),r=[];if(n){if(n.keyExpression){const e=this.resolvePossibleStringValues(n.keyExpression);r.push(...e)}else r.push(n.serializedChildren);let e;const{contextExpression:s,optionsNode:i,defaultValue:a,hasCount:o,isOrdinal:l,serializedChildren:u}=n;if(n.ns){const{ns:t}=n;e=r.map(e=>({key:e,ns:t,defaultValue:a||u,hasCount:o,isOrdinal:l}))}else{e=r.map(e=>{const t=this.config.extract.nsSeparator??":";let n;if(t&&e.includes(t)){let r;[n,...r]=e.split(t),e=r.join(t)}return{key:e,ns:n,defaultValue:a||u,hasCount:o,isOrdinal:l}});const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===n?.type&&"JSXExpressionContainer"===n.value?.type&&"Identifier"===n.value.expression.type){const t=n.value.expression.value,r=this.getVarFromScope(t);r?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=r.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),s&&o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!n,a=this.resolvePossibleStringValues(s),o=this.config.extract.contextSeparator??"_";if(a.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i));for(const t of a)for(const n of e){const e=`${n.key}${o}${t}`;this.generatePluralKeysForTrans(e,n.defaultValue,n.ns,r,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i))}else if(s){const t=this.resolvePossibleStringValues(s),n=this.config.extract.contextSeparator??"_";if(t.length>0){for(const r of t)for(const{key:t,ns:s,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${n}${r}`,ns:s,defaultValue:i});"StringLiteral"!==s.type&&e.forEach(this.pluginContext.addKey)}}else if(o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!n;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,n,r,s,i){try{const a=s?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t.getObjectPropValue(i,`defaultValue${l}other`),p=t.getObjectPropValue(i,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=s?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`,c=i?t.getObjectPropValue(i,o):void 0;let f;f="string"==typeof c?c:"one"===a&&"string"==typeof n?n:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof n?n:e:u;const g=s?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:g,ns:r,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:r,defaultValue:n})}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const r=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)r.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;r.unshift(e.expression.value)}n=n.object}if(r.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return r.join(t)}return null}resolvePossibleStringValues(e,t=!1){if("StringLiteral"===e.type)return e.value||t?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent,t),...this.resolvePossibleStringValues(e.alternate,t)]}return"Identifier"===e.type&&"undefined"===e.value?[]:"TemplateLiteral"===e.type?this.resolvePossibleStringValuesFromTemplateString(e):"NumericLiteral"===e.type||"BooleanLiteral"===e.type?[`${e.value}`]:[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...n]=e.quasis;return e.expressions.reduce((e,t,r)=>e.flatMap(e=>{const s=n[r]?.cooked??"";return this.resolvePossibleStringValues(t,!0).map(t=>`${e}${t}${s}`)}),[t.cooked??""])}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}};
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.10"),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.11"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").option("--dry-run","Run the extractor without writing any files to disk.").action(async t=>{const a=await i(),c=async()=>{const o=await r(a,{isWatchMode:t.watch,isDryRun:t.dryRun});t.ci&&o&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),t.watch){console.log("\nWatching for changes...");o.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(t,o)=>{let e=await a();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await g(e,{detail:t,namespace:o.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async t=>{const n=await i(),a=()=>s(n);if(await a(),t.watch){console.log("\nWatching for changes...");o.watch(await e(n.types?.input||[]),{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=await i();await l(t)}),w.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await m(t)}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async t=>{const i=async()=>{let t=await a();if(!t){console.log(n.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),t=o}await d(t)};if(await i(),t.watch){console.log("\nWatching for changes...");const t=await a();if(t?.extract?.input){o.watch(await e(t.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),i()})}}}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async t=>{const o=await i();await f(o,t)}),w.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const o=await i();await u(o,t)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const o=await i();await h(o,t)}),w.parse(process.argv);
@@ -1 +1 @@
1
- import{extractFromTransComponent as e}from"./jsx-parser.js";import{getObjectPropValue as t,getObjectProperty as n}from"./ast-utils.js";class s{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&"object"==typeof n&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const s=n.callee;if("Identifier"===s.type){const t=this.getUseTranslationConfig(s.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===s.type&&"Identifier"===s.property.type&&"getFixedT"===s.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,n,s){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){r="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const i=n.arguments?.[s.nsArg]?.expression;let a;"StringLiteral"===i?.type?a=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(a=i.elements[0].expression.value);const o=n.arguments?.[s.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,s=t.arguments,r=s[1]?.expression,i=s[2]?.expression,a="StringLiteral"===r?.type?r.value:void 0,o="StringLiteral"===i?.type?i.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const s=this.getFunctionName(e.callee);if(!s)return;const r=this.getVarFromScope(s),i=this.config.extract.functions||["t","*.t"];let a=void 0!==r;if(!a)for(const e of i)if(e.startsWith("*.")){if(s.endsWith(e.substring(1))){a=!0;break}}else if(e===s){a=!0;break}if(!a||0===e.arguments.length)return;const{keysToProcess:o,isSelectorAPI:l}=this.handleCallExpressionArgument(e,0);if(0===o.length)return;let u=!1;const p=this.config.extract.pluralSeparator??"_";for(let e=0;e<o.length;e++)o[e].endsWith(`${p}ordinal`)&&(u=!0,o[e]=o[e].slice(0,-8));let f,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(f=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const g=c?t(c,"defaultValue"):void 0,y="string"==typeof g?g:f;for(let e=0;e<o.length;e++){let s,i=o[e];if(c){const e=t(c,"ns");"string"==typeof e&&(s=e)}const a=this.config.extract.nsSeparator??":";if(!s&&a&&i.includes(a)){const e=i.split(a);s=e.shift(),i=e.join(a)}!s&&r?.defaultNs&&(s=r.defaultNs),s||(s=this.config.extract.defaultNS);let p=i;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";p=`${r.keyPrefix}${e}${i}`}const f=e===o.length-1&&y||i;if(c){const e=n(c,"context"),r=[];if("ConditionalExpression"===e?.value?.type){const t=this.resolvePossibleStringValues(e.value),n=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{r.push({key:`${p}${n}${e}`,ns:s,defaultValue:f})}),r.push({key:p,ns:s,defaultValue:f}))}else if("StringLiteral"===e?.value?.type){const t=e.value.value,n=this.config.extract.contextSeparator??"_";r.push({key:`${p}${n}${t}`,ns:s,defaultValue:f})}const i=void 0!==t(c,"count"),a=!0===t(c,"ordinal");if(i||u){if(r.length>0)for(const{key:e,ns:t}of r)this.handlePluralKeys(e,t,c,a||u);else this.handlePluralKeys(p,s,c,a||u);continue}if(r.length>0){r.forEach(this.pluginContext.addKey);continue}!0===t(c,"returnObjects")&&this.objectKeys.add(p)}l&&this.objectKeys.add(p),this.pluginContext.addKey({key:p,ns:s,defaultValue:f})}}handleCallExpressionArgument(e,t){const n=e.arguments[t].expression,s=[];let r=!1;if("ArrowFunctionExpression"===n.type){const e=this.extractKeyFromSelector(n);e&&(s.push(e),r=!0)}else if("ArrayExpression"===n.type)for(const e of n.elements)e?.expression&&s.push(...this.resolvePossibleStringValues(e.expression));else s.push(...this.resolvePossibleStringValues(n));return{keysToProcess:s.filter(e=>!!e),isSelectorAPI:r}}handlePluralKeys(e,n,s,r){try{const i=r?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t(s,"defaultValue"),u=t(s,`defaultValue${o}other`),p=t(s,`defaultValue${o}ordinal${o}other`);for(const i of a){const a=t(s,r?`defaultValue${o}ordinal${o}${i}`:`defaultValue${o}${i}`);let f;f="string"==typeof a?a:"one"===i&&"string"==typeof l?l:r&&"string"==typeof p?p:r||"string"!=typeof u?"string"==typeof l?l:e:u;const c=r?`${e}${o}ordinal${o}${i}`:`${e}${o}${i}`;this.pluginContext.addKey({key:c,ns:n,defaultValue:f,hasCount:!0,isOrdinal:r})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=t(s,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,n){try{const s=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,r=this.config.extract.pluralSeparator??"_";for(const i of s)this.pluginContext.addKey({key:`${e}${r}${i}`,ns:n,defaultValue:t,hasCount:!0})}catch(s){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:n,defaultValue:t})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e(t,this.config),s=[];if(n){if(n.keyExpression){const e=this.resolvePossibleStringValues(n.keyExpression);s.push(...e)}else s.push(n.serializedChildren);let e;const{contextExpression:r,optionsNode:i,defaultValue:a,hasCount:o,isOrdinal:l,serializedChildren:u}=n;if(n.ns){const{ns:t}=n;e=s.map(e=>({key:e,ns:t,defaultValue:a||u,hasCount:o,isOrdinal:l}))}else{e=s.map(e=>{const t=this.config.extract.nsSeparator??":";let n;if(t&&e.includes(t)){let s;[n,...s]=e.split(t),e=s.join(t)}return{key:e,ns:n,defaultValue:a||u,hasCount:o,isOrdinal:l}});const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===n?.type&&"JSXExpressionContainer"===n.value?.type&&"Identifier"===n.value.expression.type){const t=n.value.expression.value,s=this.getVarFromScope(t);s?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=s.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),r&&o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),s=!!n,a=this.resolvePossibleStringValues(r),o=this.config.extract.contextSeparator??"_";if(a.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,s,i));for(const t of a)for(const n of e){const e=`${n.key}${o}${t}`;this.generatePluralKeysForTrans(e,n.defaultValue,n.ns,s,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,s,i))}else if(r){const t=this.resolvePossibleStringValues(r),n=this.config.extract.contextSeparator??"_";if(t.length>0){for(const s of t)for(const{key:t,ns:r,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${n}${s}`,ns:r,defaultValue:i});"StringLiteral"!==r.type&&e.forEach(this.pluginContext.addKey)}}else if(o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),s=!!n;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,s,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,n,s,r,i){try{const a=r?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t(i,`defaultValue${l}other`),p=t(i,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=i?t(i,r?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`):void 0;let f;f="string"==typeof o?o:"one"===a&&"string"==typeof n?n:r&&"string"==typeof p?p:r||"string"!=typeof u?"string"==typeof n?n:e:u;const c=r?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:c,ns:s,defaultValue:f,hasCount:!0,isOrdinal:r})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:s,defaultValue:n})}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const s=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)s.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;s.unshift(e.expression.value)}n=n.object}if(s.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return s.join(t)}return null}resolvePossibleStringValues(e){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&&"undefined"===e.value?[]:"TemplateLiteral"===e.type?this.resolvePossibleStringValuesFromTemplateString(e):"NumericLiteral"===e.type||"BooleanLiteral"===e.type?[`${e.value}`]:[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...n]=e.quasis;return e.expressions.reduce((e,t,s)=>e.flatMap(e=>{const r=n[s]?.cooked??"";return this.resolvePossibleStringValues(t).map(t=>`${e}${t}${r}`)}),[t.cooked??""])}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}}export{s as ASTVisitors};
1
+ import{extractFromTransComponent as e}from"./jsx-parser.js";import{getObjectPropValue as t,getObjectProperty as n}from"./ast-utils.js";class s{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&"object"==typeof n&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const s=n.callee;if("Identifier"===s.type){const t=this.getUseTranslationConfig(s.value);if(t)return void this.handleUseTranslationDeclarator(e,n,t)}"MemberExpression"===s.type&&"Identifier"===s.property.type&&"getFixedT"===s.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationDeclarator(e,n,s){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){r="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const i=n.arguments?.[s.nsArg]?.expression;let a;"StringLiteral"===i?.type?a=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(a=i.elements[0].expression.value);const o=n.arguments?.[s.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,s=t.arguments,r=s[1]?.expression,i=s[2]?.expression,a="StringLiteral"===r?.type?r.value:void 0,o="StringLiteral"===i?.type?i.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const s=this.getFunctionName(e.callee);if(!s)return;const r=this.getVarFromScope(s),i=this.config.extract.functions||["t","*.t"];let a=void 0!==r;if(!a)for(const e of i)if(e.startsWith("*.")){if(s.endsWith(e.substring(1))){a=!0;break}}else if(e===s){a=!0;break}if(!a||0===e.arguments.length)return;const{keysToProcess:o,isSelectorAPI:l}=this.handleCallExpressionArgument(e,0);if(0===o.length)return;let u=!1;const p=this.config.extract.pluralSeparator??"_";for(let e=0;e<o.length;e++)o[e].endsWith(`${p}ordinal`)&&(u=!0,o[e]=o[e].slice(0,-8));let f,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(f=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const g=c?t(c,"defaultValue"):void 0,y="string"==typeof g?g:f;for(let e=0;e<o.length;e++){let s,i=o[e];if(c){const e=t(c,"ns");"string"==typeof e&&(s=e)}const a=this.config.extract.nsSeparator??":";if(!s&&a&&i.includes(a)){const e=i.split(a);s=e.shift(),i=e.join(a)}!s&&r?.defaultNs&&(s=r.defaultNs),s||(s=this.config.extract.defaultNS);let p=i;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";p=`${r.keyPrefix}${e}${i}`}const f=e===o.length-1&&y||i;if(c){const e=n(c,"context"),r=[];if("ConditionalExpression"===e?.value?.type){const t=this.resolvePossibleStringValues(e.value),n=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{r.push({key:`${p}${n}${e}`,ns:s,defaultValue:f})}),r.push({key:p,ns:s,defaultValue:f}))}else if("StringLiteral"===e?.value?.type){const t=e.value.value,n=this.config.extract.contextSeparator??"_";r.push({key:`${p}${n}${t}`,ns:s,defaultValue:f})}const i=void 0!==t(c,"count"),a=!0===t(c,"ordinal");if(i||u){if(r.length>0)for(const{key:e,ns:t}of r)this.handlePluralKeys(e,t,c,a||u);else this.handlePluralKeys(p,s,c,a||u);continue}if(r.length>0){r.forEach(this.pluginContext.addKey);continue}!0===t(c,"returnObjects")&&this.objectKeys.add(p)}l&&this.objectKeys.add(p),this.pluginContext.addKey({key:p,ns:s,defaultValue:f})}}handleCallExpressionArgument(e,t){const n=e.arguments[t].expression,s=[];let r=!1;if("ArrowFunctionExpression"===n.type){const e=this.extractKeyFromSelector(n);e&&(s.push(e),r=!0)}else if("ArrayExpression"===n.type)for(const e of n.elements)e?.expression&&s.push(...this.resolvePossibleStringValues(e.expression));else s.push(...this.resolvePossibleStringValues(n));return{keysToProcess:s.filter(e=>!!e),isSelectorAPI:r}}handlePluralKeys(e,n,s,r){try{const i=r?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t(s,"defaultValue"),u=t(s,`defaultValue${o}other`),p=t(s,`defaultValue${o}ordinal${o}other`);for(const i of a){const a=t(s,r?`defaultValue${o}ordinal${o}${i}`:`defaultValue${o}${i}`);let f;f="string"==typeof a?a:"one"===i&&"string"==typeof l?l:r&&"string"==typeof p?p:r||"string"!=typeof u?"string"==typeof l?l:e:u;const c=r?`${e}${o}ordinal${o}${i}`:`${e}${o}${i}`;this.pluginContext.addKey({key:c,ns:n,defaultValue:f,hasCount:!0,isOrdinal:r})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=t(s,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,n){try{const s=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,r=this.config.extract.pluralSeparator??"_";for(const i of s)this.pluginContext.addKey({key:`${e}${r}${i}`,ns:n,defaultValue:t,hasCount:!0})}catch(s){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:n,defaultValue:t})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e(t,this.config),s=[];if(n){if(n.keyExpression){const e=this.resolvePossibleStringValues(n.keyExpression);s.push(...e)}else s.push(n.serializedChildren);let e;const{contextExpression:r,optionsNode:i,defaultValue:a,hasCount:o,isOrdinal:l,serializedChildren:u}=n;if(n.ns){const{ns:t}=n;e=s.map(e=>({key:e,ns:t,defaultValue:a||u,hasCount:o,isOrdinal:l}))}else{e=s.map(e=>{const t=this.config.extract.nsSeparator??":";let n;if(t&&e.includes(t)){let s;[n,...s]=e.split(t),e=s.join(t)}return{key:e,ns:n,defaultValue:a||u,hasCount:o,isOrdinal:l}});const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===n?.type&&"JSXExpressionContainer"===n.value?.type&&"Identifier"===n.value.expression.type){const t=n.value.expression.value,s=this.getVarFromScope(t);s?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=s.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),r&&o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),s=!!n,a=this.resolvePossibleStringValues(r),o=this.config.extract.contextSeparator??"_";if(a.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,s,i));for(const t of a)for(const n of e){const e=`${n.key}${o}${t}`;this.generatePluralKeysForTrans(e,n.defaultValue,n.ns,s,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,s,i))}else if(r){const t=this.resolvePossibleStringValues(r),n=this.config.extract.contextSeparator??"_";if(t.length>0){for(const s of t)for(const{key:t,ns:r,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${n}${s}`,ns:r,defaultValue:i});"StringLiteral"!==r.type&&e.forEach(this.pluginContext.addKey)}}else if(o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),s=!!n;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,s,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,n,s,r,i){try{const a=r?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t(i,`defaultValue${l}other`),p=t(i,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=i?t(i,r?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`):void 0;let f;f="string"==typeof o?o:"one"===a&&"string"==typeof n?n:r&&"string"==typeof p?p:r||"string"!=typeof u?"string"==typeof n?n:e:u;const c=r?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:c,ns:s,defaultValue:f,hasCount:!0,isOrdinal:r})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:s,defaultValue:n})}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const s=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)s.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;s.unshift(e.expression.value)}n=n.object}if(s.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return s.join(t)}return null}resolvePossibleStringValues(e,t=!1){if("StringLiteral"===e.type)return e.value||t?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent,t),...this.resolvePossibleStringValues(e.alternate,t)]}return"Identifier"===e.type&&"undefined"===e.value?[]:"TemplateLiteral"===e.type?this.resolvePossibleStringValuesFromTemplateString(e):"NumericLiteral"===e.type||"BooleanLiteral"===e.type?[`${e.value}`]:[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...n]=e.quasis;return e.expressions.reduce((e,t,s)=>e.flatMap(e=>{const r=n[s]?.cooked??"";return this.resolvePossibleStringValues(t,!0).map(t=>`${e}${t}${r}`)}),[t.cooked??""])}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}}export{s as ASTVisitors};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.5.10",
3
+ "version": "1.5.11",
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.10')
24
+ .version('1.5.11')
25
25
 
26
26
  program
27
27
  .command('extract')
@@ -758,8 +758,8 @@ export class ASTVisitors {
758
758
  const ordinalAttr = node.opening.attributes?.find(
759
759
  (attr) =>
760
760
  attr.type === 'JSXAttribute' &&
761
- attr.name.type === 'Identifier' &&
762
- attr.name.value === 'ordinal'
761
+ attr.name.type === 'Identifier' &&
762
+ attr.name.value === 'ordinal'
763
763
  )
764
764
  const isOrdinal = !!ordinalAttr
765
765
 
@@ -802,8 +802,8 @@ export class ASTVisitors {
802
802
  const ordinalAttr = node.opening.attributes?.find(
803
803
  (attr) =>
804
804
  attr.type === 'JSXAttribute' &&
805
- attr.name.type === 'Identifier' &&
806
- attr.name.value === 'ordinal'
805
+ attr.name.type === 'Identifier' &&
806
+ attr.name.value === 'ordinal'
807
807
  )
808
808
  const isOrdinal = !!ordinalAttr
809
809
 
@@ -988,17 +988,18 @@ export class ASTVisitors {
988
988
  *
989
989
  * @private
990
990
  * @param expression - The SWC AST expression node to resolve
991
+ * @param returnEmptyStrings - Whether to include empty strings in the result
991
992
  * @returns An array of possible string values that the expression may produce.
992
993
  */
993
- private resolvePossibleStringValues (expression: Expression): string[] {
994
+ private resolvePossibleStringValues (expression: Expression, returnEmptyStrings = false): string[] {
994
995
  if (expression.type === 'StringLiteral') {
995
996
  // Filter out empty strings as they should be treated as "no context" like i18next does
996
- return expression.value ? [expression.value] : []
997
+ return expression.value || returnEmptyStrings ? [expression.value] : []
997
998
  }
998
999
 
999
1000
  if (expression.type === 'ConditionalExpression') { // This is a ternary operator
1000
- const consequentValues = this.resolvePossibleStringValues(expression.consequent)
1001
- const alternateValues = this.resolvePossibleStringValues(expression.alternate)
1001
+ const consequentValues = this.resolvePossibleStringValues(expression.consequent, returnEmptyStrings)
1002
+ const alternateValues = this.resolvePossibleStringValues(expression.alternate, returnEmptyStrings)
1002
1003
  return [...consequentValues, ...alternateValues]
1003
1004
  }
1004
1005
 
@@ -1040,7 +1041,7 @@ export class ASTVisitors {
1040
1041
  (heads, expression, i) => {
1041
1042
  return heads.flatMap((head) => {
1042
1043
  const tail = tails[i]?.cooked ?? ''
1043
- return this.resolvePossibleStringValues(expression).map(
1044
+ return this.resolvePossibleStringValues(expression, true).map(
1044
1045
  (expressionValue) => `${head}${expressionValue}${tail}`
1045
1046
  )
1046
1047
  })
@@ -252,6 +252,7 @@ export declare class ASTVisitors {
252
252
  *
253
253
  * @private
254
254
  * @param expression - The SWC AST expression node to resolve
255
+ * @param returnEmptyStrings - Whether to include empty strings in the result
255
256
  * @returns An array of possible string values that the expression may produce.
256
257
  */
257
258
  private resolvePossibleStringValues;
@@ -1 +1 @@
1
- {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAgI,MAAM,WAAW,CAAA;AACrK,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,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;IA4BnC;;;;;;;OAOG;IACH,OAAO,CAAC,6CAA6C;IAyBrD;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAgI,MAAM,WAAW,CAAA;AACrK,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;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,2BAA2B;IA4BnC;;;;;;;OAOG;IACH,OAAO,CAAC,6CAA6C;IAyBrD;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,OAAO,CAAC,eAAe;CA2BxB"}