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 +9 -0
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/parsers/ast-utils.js +1 -1
- package/dist/cjs/extractor/parsers/ast-visitors.js +1 -1
- package/dist/cjs/extractor/parsers/jsx-parser.js +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/parsers/ast-utils.js +1 -1
- package/dist/esm/extractor/parsers/ast-visitors.js +1 -1
- package/dist/esm/extractor/parsers/jsx-parser.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/extractor/parsers/ast-utils.ts +10 -9
- package/src/extractor/parsers/ast-visitors.ts +148 -71
- package/src/extractor/parsers/jsx-parser.ts +84 -20
- package/types/extractor/parsers/ast-utils.d.ts +1 -1
- package/types/extractor/parsers/ast-utils.d.ts.map +1 -1
- package/types/extractor/parsers/ast-visitors.d.ts +8 -0
- package/types/extractor/parsers/ast-visitors.d.ts.map +1 -1
- package/types/extractor/parsers/jsx-parser.d.ts +22 -3
- package/types/extractor/parsers/jsx-parser.d.ts.map +1 -1
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.
|
|
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.
|
|
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");
|
|
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.
|
|
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.
|
|
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{
|
|
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
package/src/cli.ts
CHANGED
|
@@ -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)
|
|
19
|
-
return (object.properties).
|
|
20
|
-
(p) =>
|
|
21
|
-
|
|
22
|
-
(
|
|
23
|
-
(
|
|
24
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
507
|
-
continue // This key is fully handled
|
|
485
|
+
keysWithContext.push({ key: `${finalKey}${contextSeparator}${contextValue}`, ns, defaultValue: dv })
|
|
508
486
|
}
|
|
509
487
|
|
|
510
|
-
//
|
|
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
|
-
//
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
-
|
|
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
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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 (
|
|
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
|
-
|
|
689
|
-
|
|
761
|
+
attr.name.type === 'Identifier' &&
|
|
762
|
+
attr.name.value === 'ordinal'
|
|
690
763
|
)
|
|
691
764
|
const isOrdinal = !!ordinalAttr
|
|
692
765
|
|
|
693
|
-
const contextValues = this.resolvePossibleStringValues(
|
|
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,
|
|
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
|
|
704
|
-
|
|
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,
|
|
783
|
+
extractedKeys.forEach(extractedKey => this.generatePluralKeysForTrans(extractedKey.key, extractedKey.defaultValue, extractedKey.ns, isOrdinal, optionsNode))
|
|
709
784
|
}
|
|
710
|
-
} else if (
|
|
711
|
-
const contextValues = this.resolvePossibleStringValues(
|
|
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
|
-
|
|
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 (
|
|
720
|
-
this.pluginContext.addKey
|
|
796
|
+
if (contextExpression.type !== 'StringLiteral') {
|
|
797
|
+
extractedKeys.forEach(this.pluginContext.addKey)
|
|
721
798
|
}
|
|
722
799
|
}
|
|
723
|
-
} else if (
|
|
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
|
-
|
|
729
|
-
|
|
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,
|
|
810
|
+
extractedKeys.forEach(extractedKey => this.generatePluralKeysForTrans(extractedKey.key, extractedKey.defaultValue, extractedKey.ns, isOrdinal, optionsNode))
|
|
734
811
|
} else {
|
|
735
|
-
this.pluginContext.addKey
|
|
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 {
|
|
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):
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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):
|
|
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,
|
|
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,
|
|
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 {
|
|
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):
|
|
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;
|
|
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"}
|