i18next-cli 0.9.17 → 0.9.18
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 +7 -1
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/core/translation-manager.js +1 -1
- package/dist/cjs/extractor/parsers/ast-visitors.js +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/core/translation-manager.js +1 -1
- package/dist/esm/extractor/parsers/ast-visitors.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/extractor/core/translation-manager.ts +23 -35
- package/src/extractor/parsers/ast-visitors.ts +59 -4
- package/types/extractor/core/translation-manager.d.ts.map +1 -1
- package/types/extractor/parsers/ast-visitors.d.ts +30 -0
- package/types/extractor/parsers/ast-visitors.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,10 +5,16 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [1.0.0](https://github.com/i18next/i18next-cli/compare/v0.9.
|
|
8
|
+
## [1.0.0](https://github.com/i18next/i18next-cli/compare/v0.9.18...v1.0.0) - 2025-xx-yy
|
|
9
9
|
|
|
10
10
|
- not yet released
|
|
11
11
|
|
|
12
|
+
## [0.9.18](https://github.com/i18next/i18next-cli/compare/v0.9.17...v0.9.18) - 2025-09-30
|
|
13
|
+
|
|
14
|
+
- **Extractor:** Fixed a bug where translation keys were not found in custom functions that were part of an object (e.g., `i18n.t(...)`). The `functions` configuration option now correctly handles member expressions in addition to simple function names. [#10](https://github.com/i18next/i18next-cli/issues/10)
|
|
15
|
+
- **Extractor:** Fixed a critical bug where the `extract` command would incorrectly overwrite existing translations in secondary languages when using the `mergeNamespaces: true` option. The fix also resolves a related issue where unused keys were not being correctly pruned from the primary language file in the same scenario. The translation manager logic is now more robust for both merged and non-merged configurations. [#11](https://github.com/i18next/i18next-cli/issues/11)
|
|
16
|
+
|
|
17
|
+
|
|
12
18
|
## [0.9.17](https://github.com/i18next/i18next-cli/compare/v0.9.16...v0.9.17) - 2025-09-29
|
|
13
19
|
|
|
14
20
|
- **Extractor:** Fixed a bug where namespace and `keyPrefix` information from custom `useTranslationNames` hooks was ignored when the `t` function was assigned directly to a variable (e.g., `let t = myHook()`). The extractor now correctly handles this pattern in addition to destructuring assignments. [#9](https://github.com/i18next/i18next-cli/issues/9)
|
package/dist/cjs/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node: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("0.9.
|
|
2
|
+
"use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node: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("0.9.18"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a);e.ci&&t&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let o=await i.loadConfig();if(!o){console.log(n.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),o=e}await g.runStatus(o,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const n=await i.ensureConfig(),a=()=>c.runTypesGenerator(n);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await o.glob(n.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await l.runMigrator()}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let e=await i.loadConfig();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await d.runLinter(e)}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t=require("node:path"),e=require("../../utils/nested-object.js"),s=require("../../utils/file-utils.js");function a(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}exports.getTranslations=async function(n,
|
|
1
|
+
"use strict";var t=require("node:path"),e=require("../../utils/nested-object.js"),s=require("../../utils/file-utils.js");function a(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}exports.getTranslations=async function(r,n,o){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(t=>t!==o?.extract?.primaryLanguage);const c=o.extract.primaryLanguage,l=o.extract.defaultNS??"translation",u=o.extract.keySeparator??".",i=[...o.extract.preservePatterns||[]],p=o.extract.mergeNamespaces??!1,f=o.extract.indentation??2;for(const t of n)i.push(`${t}.*`);const g=i.map(a),d=new Map;for(const t of r.values()){const e=t.ns||l;d.has(e)||d.set(e,[]),d.get(e).push(t)}const x=[];for(const a of o.locales){const r=p?await s.loadTranslationFile(t.resolve(process.cwd(),s.getOutputPath(o.extract.output,a)))||{}:null,n={};for(const[l,i]of d.entries()){const d=s.getOutputPath(o.extract.output,a,p?void 0:l),y=t.resolve(process.cwd(),d),h=p?r?.[l]||{}:await s.loadTranslationFile(y)||{},N={},w=e.getNestedKeys(h,u);for(const t of w)if(g.some(e=>e.test(t))){const s=e.getNestedValue(h,t,u);e.setNestedValue(N,t,s,u)}const m=!1===o.extract.sort?i:[...i].sort((t,e)=>t.key.localeCompare(e.key));for(const{key:t,defaultValue:s}of m){const r=e.getNestedValue(h,t,u)??(a===c?s:o.extract.defaultValue??"");e.setNestedValue(N,t,r,u)}if(p)n[l]=N;else{const t=JSON.stringify(h,null,f),e=JSON.stringify(N,null,f);x.push({path:y,updated:e!==t,newTranslations:N,existingTranslations:h})}}if(p){const e=s.getOutputPath(o.extract.output,a),c=t.resolve(process.cwd(),e),l=JSON.stringify(r,null,f),u=JSON.stringify(n,null,f);x.push({path:c,updated:u!==l,newTranslations:n,existingTranslations:r||{}})}}return x};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("./jsx-parser.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){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,t,n){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){r="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const i=t.arguments?.[n.nsArg]?.expression;let s;"StringLiteral"===i?.type?s=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(s=i.elements[0].expression.value);const a=t.arguments?.[n.keyPrefixArg]?.expression;let o;if("ObjectExpression"===a?.type){const e=this.getObjectPropValue(a,"keyPrefix");o="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:s,keyPrefix:o})}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 t=e.callee;if(
|
|
1
|
+
"use strict";var e=require("./jsx-parser.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){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,t,n){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){r="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const i=t.arguments?.[n.nsArg]?.expression;let s;"StringLiteral"===i?.type?s=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(s=i.elements[0].expression.value);const a=t.arguments?.[n.keyPrefixArg]?.expression;let o;if("ObjectExpression"===a?.type){const e=this.getObjectPropValue(a,"keyPrefix");o="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:s,keyPrefix:o})}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 t=this.getFunctionName(e.callee);if(!t)return;const n=this.getVarFromScope(t);if(!((this.config.extract.functions||["t"]).includes(t)||void 0!==n)||0===e.arguments.length)return;const r=e.arguments[0].expression,i=[];if("StringLiteral"===r.type)i.push(r.value);else if("ArrowFunctionExpression"===r.type){const e=this.extractKeyFromSelector(r);e&&i.push(e)}else if("ArrayExpression"===r.type)for(const e of r.elements)"StringLiteral"===e?.expression.type&&i.push(e.expression.value);if(0===i.length)return;let s,a;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?a=t:"StringLiteral"===t.type&&(s=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(a=t)}const o=a?this.getObjectPropValue(a,"defaultValue"):void 0,l="string"==typeof o?o:s;for(let e=0;e<i.length;e++){let t,r=i[e];if(a){const e=this.getObjectPropValue(a,"ns");"string"==typeof e&&(t=e)}!t&&n?.defaultNs&&(t=n.defaultNs);const s=this.config.extract.nsSeparator??":";if(!t&&s&&r.includes(s)){const e=r.split(s);t=e.shift(),r=e.join(s)}t||(t=this.config.extract.defaultNS);let o=r;if(n?.keyPrefix){const e=this.config.extract.keySeparator??".";o=`${n.keyPrefix}${e}${r}`}const p=e===i.length-1&&l||r;if(a){const e=this.getObjectProperty(a,"context");if("ConditionalExpression"===e?.value?.type){const n=this.resolvePossibleStringValues(e.value),r=this.config.extract.contextSeparator??"_";if(n.length>0){n.forEach(e=>{this.pluginContext.addKey({key:`${o}${r}${e}`,ns:t,defaultValue:p})}),this.pluginContext.addKey({key:o,ns:t,defaultValue:p});continue}}const n=this.getObjectPropValue(a,"context");if("string"==typeof n&&n){const e=this.config.extract.contextSeparator??"_";this.pluginContext.addKey({key:`${o}${e}${n}`,ns:t,defaultValue:p});continue}if(void 0!==this.getObjectPropValue(a,"count")){this.handlePluralKeys(o,t,a);continue}!0===this.getObjectPropValue(a,"returnObjects")&&this.objectKeys.add(o)}this.pluginContext.addKey({key:o,ns:t,defaultValue:p})}}handlePluralKeys(e,t,n){try{const r=!0===this.getObjectPropValue(n,"ordinal"),i=r?"ordinal":"cardinal",s=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,a=this.config.extract.pluralSeparator??"_",o=this.getObjectPropValue(n,"defaultValue"),l=this.getObjectPropValue(n,"defaultValue_other");for(const i of s){const s=r?`defaultValue_ordinal_${i}`:`defaultValue_${i}`,p=this.getObjectPropValue(n,s);let u;u="string"==typeof p?p:"one"===i&&"string"==typeof o?o:"string"==typeof l?l:"string"==typeof o?o:e;const c=r?`${e}${a}ordinal${a}${i}`:`${e}${a}${i}`;this.pluginContext.addKey({key:c,ns:t,defaultValue:u,hasCount:!0})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=this.getObjectPropValue(n,"defaultValue");this.pluginContext.addKey({key:e,ns:t,defaultValue:"string"==typeof i?i: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){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});this.pluginContext.addKey(n)}}else n.hasCount?this.handleSimplePluralKeys(n.key,n.defaultValue,n.ns):this.pluginContext.addKey(n)}}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}getObjectPropValue(e,t){const n=e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t));if("KeyValueProperty"===n?.type){const e=n.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const 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];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent),...this.resolvePossibleStringValues(e.alternate)]}return"Identifier"===e.type&&e.value,[]}getObjectProperty(e,t){return e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t))}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}return"Identifier"!==n.type?null:(t.unshift(n.value),t.join("."))}return null}};
|
package/dist/esm/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as o}from"commander";import t from"chokidar";import{glob as e}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node: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 f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.
|
|
2
|
+
import{Command as o}from"commander";import t from"chokidar";import{glob as e}from"glob";import i from"chalk";import{ensureConfig as n,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node: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 f}from"./status.js";import{runLocizeSync as g,runLocizeDownload as u,runLocizeMigrate as y}from"./locize.js";const w=new o;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("0.9.18"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").action(async o=>{const a=await n(),c=async()=>{const t=await r(a);o.ci&&t&&(console.error(i.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(i.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${i.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),o.watch){console.log("\nWatching for changes...");t.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(o,t)=>{let e=await a();if(!e){console.log(i.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),e=o}await f(e,{detail:o,namespace:t.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async o=>{const i=await n(),a=()=>s(i);if(await a(),o.watch){console.log("\nWatching for changes...");t.watch(await e(i.types?.input||[]),{persistent:!0}).on("change",o=>{console.log(`\nFile changed: ${o}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const o=await n();await l(o)}),w.command("migrate-config").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async()=>{await m()}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").action(async()=>{let o=await a();if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i1e-toolkit init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),o=t}await d(o)}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async o=>{const t=await n();await g(t,o)}),w.command("locize-download").description("Download all translations from your locize project.").action(async o=>{const t=await n();await u(t,o)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async o=>{const t=await n();await y(t,o)}),w.parse(process.argv);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolve as t}from"node:path";import{getNestedKeys as e,getNestedValue as
|
|
1
|
+
import{resolve as t}from"node:path";import{getNestedKeys as e,getNestedValue as s,setNestedValue as a}from"../../utils/nested-object.js";import{loadTranslationFile as n,getOutputPath as o}from"../../utils/file-utils.js";function r(t){const e=`^${t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*")}$`;return new RegExp(e)}async function c(c,i,l){l.extract.primaryLanguage||=l.locales[0]||"en",l.extract.secondaryLanguages||=l.locales.filter(t=>t!==l?.extract?.primaryLanguage);const u=l.extract.primaryLanguage,p=l.extract.defaultNS??"translation",f=l.extract.keySeparator??".",g=[...l.extract.preservePatterns||[]],x=l.extract.mergeNamespaces??!1,d=l.extract.indentation??2;for(const t of i)g.push(`${t}.*`);const m=g.map(r),y=new Map;for(const t of c.values()){const e=t.ns||p;y.has(e)||y.set(e,[]),y.get(e).push(t)}const w=[];for(const r of l.locales){const c=x?await n(t(process.cwd(),o(l.extract.output,r)))||{}:null,i={};for(const[p,g]of y.entries()){const y=o(l.extract.output,r,x?void 0:p),h=t(process.cwd(),y),N=x?c?.[p]||{}:await n(h)||{},S={},$=e(N,f);for(const t of $)if(m.some(e=>e.test(t))){const e=s(N,t,f);a(S,t,e,f)}const k=!1===l.extract.sort?g:[...g].sort((t,e)=>t.key.localeCompare(e.key));for(const{key:t,defaultValue:e}of k){const n=s(N,t,f)??(r===u?e:l.extract.defaultValue??"");a(S,t,n,f)}if(x)i[p]=S;else{const t=JSON.stringify(N,null,d),e=JSON.stringify(S,null,d);w.push({path:h,updated:e!==t,newTranslations:S,existingTranslations:N})}}if(x){const e=o(l.extract.output,r),s=t(process.cwd(),e),a=JSON.stringify(c,null,d),n=JSON.stringify(i,null,d);w.push({path:s,updated:n!==a,newTranslations:i,existingTranslations:c||{}})}}return w}export{c as getTranslations};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{extractFromTransComponent as e}from"./jsx-parser.js";class t{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){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
|
|
1
|
+
import{extractFromTransComponent as e}from"./jsx-parser.js";class t{pluginContext;config;logger;scopeStack=[];objectKeys=new Set;constructor(e,t,n){this.pluginContext=t,this.config=e,this.logger=n}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&n.type&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e))return this.scopeStack[t].get(e)}handleVariableDeclarator(e){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,t,n){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){r="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const i=t.arguments?.[n.nsArg]?.expression;let s;"StringLiteral"===i?.type?s=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(s=i.elements[0].expression.value);const a=t.arguments?.[n.keyPrefixArg]?.expression;let o;if("ObjectExpression"===a?.type){const e=this.getObjectPropValue(a,"keyPrefix");o="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:s,keyPrefix:o})}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 t=this.getFunctionName(e.callee);if(!t)return;const n=this.getVarFromScope(t);if(!((this.config.extract.functions||["t"]).includes(t)||void 0!==n)||0===e.arguments.length)return;const r=e.arguments[0].expression,i=[];if("StringLiteral"===r.type)i.push(r.value);else if("ArrowFunctionExpression"===r.type){const e=this.extractKeyFromSelector(r);e&&i.push(e)}else if("ArrayExpression"===r.type)for(const e of r.elements)"StringLiteral"===e?.expression.type&&i.push(e.expression.value);if(0===i.length)return;let s,a;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?a=t:"StringLiteral"===t.type&&(s=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(a=t)}const o=a?this.getObjectPropValue(a,"defaultValue"):void 0,l="string"==typeof o?o:s;for(let e=0;e<i.length;e++){let t,r=i[e];if(a){const e=this.getObjectPropValue(a,"ns");"string"==typeof e&&(t=e)}!t&&n?.defaultNs&&(t=n.defaultNs);const s=this.config.extract.nsSeparator??":";if(!t&&s&&r.includes(s)){const e=r.split(s);t=e.shift(),r=e.join(s)}t||(t=this.config.extract.defaultNS);let o=r;if(n?.keyPrefix){const e=this.config.extract.keySeparator??".";o=`${n.keyPrefix}${e}${r}`}const p=e===i.length-1&&l||r;if(a){const e=this.getObjectProperty(a,"context");if("ConditionalExpression"===e?.value?.type){const n=this.resolvePossibleStringValues(e.value),r=this.config.extract.contextSeparator??"_";if(n.length>0){n.forEach(e=>{this.pluginContext.addKey({key:`${o}${r}${e}`,ns:t,defaultValue:p})}),this.pluginContext.addKey({key:o,ns:t,defaultValue:p});continue}}const n=this.getObjectPropValue(a,"context");if("string"==typeof n&&n){const e=this.config.extract.contextSeparator??"_";this.pluginContext.addKey({key:`${o}${e}${n}`,ns:t,defaultValue:p});continue}if(void 0!==this.getObjectPropValue(a,"count")){this.handlePluralKeys(o,t,a);continue}!0===this.getObjectPropValue(a,"returnObjects")&&this.objectKeys.add(o)}this.pluginContext.addKey({key:o,ns:t,defaultValue:p})}}handlePluralKeys(e,t,n){try{const r=!0===this.getObjectPropValue(n,"ordinal"),i=r?"ordinal":"cardinal",s=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,a=this.config.extract.pluralSeparator??"_",o=this.getObjectPropValue(n,"defaultValue"),l=this.getObjectPropValue(n,"defaultValue_other");for(const i of s){const s=r?`defaultValue_ordinal_${i}`:`defaultValue_${i}`,p=this.getObjectPropValue(n,s);let u;u="string"==typeof p?p:"one"===i&&"string"==typeof o?o:"string"==typeof l?l:"string"==typeof o?o:e;const c=r?`${e}${a}ordinal${a}${i}`:`${e}${a}${i}`;this.pluginContext.addKey({key:c,ns:t,defaultValue:u,hasCount:!0})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=this.getObjectPropValue(n,"defaultValue");this.pluginContext.addKey({key:e,ns:t,defaultValue:"string"==typeof i?i: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(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){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});this.pluginContext.addKey(n)}}else n.hasCount?this.handleSimplePluralKeys(n.key,n.defaultValue,n.ns):this.pluginContext.addKey(n)}}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}getObjectPropValue(e,t){const n=e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t));if("KeyValueProperty"===n?.type){const e=n.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const 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];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent),...this.resolvePossibleStringValues(e.alternate)]}return"Identifier"===e.type&&e.value,[]}getObjectProperty(e,t){return e.properties.find(e=>"KeyValueProperty"===e.type&&("Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t))}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}return"Identifier"!==n.type?null:(t.unshift(n.value),t.join("."))}return null}}export{t as ASTVisitors};
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -47,40 +47,46 @@ export async function getTranslations (
|
|
|
47
47
|
objectKeys: Set<string>,
|
|
48
48
|
config: I18nextToolkitConfig
|
|
49
49
|
): Promise<TranslationResult[]> {
|
|
50
|
+
config.extract.primaryLanguage ||= config.locales[0] || 'en'
|
|
51
|
+
config.extract.secondaryLanguages ||= config.locales.filter((l: string) => l !== config?.extract?.primaryLanguage)
|
|
52
|
+
const primaryLanguage = config.extract.primaryLanguage
|
|
50
53
|
const defaultNS = config.extract.defaultNS ?? 'translation'
|
|
51
54
|
const keySeparator = config.extract.keySeparator ?? '.'
|
|
52
55
|
const patternsToPreserve = [...(config.extract.preservePatterns || [])]
|
|
53
56
|
const mergeNamespaces = config.extract.mergeNamespaces ?? false
|
|
57
|
+
const indentation = config.extract.indentation ?? 2
|
|
58
|
+
|
|
54
59
|
for (const key of objectKeys) {
|
|
55
60
|
// Convert the object key to a glob pattern to preserve all its children
|
|
56
61
|
patternsToPreserve.push(`${key}.*`)
|
|
57
62
|
}
|
|
58
63
|
const preservePatterns = patternsToPreserve.map(globToRegex)
|
|
59
|
-
config.extract.primaryLanguage ||= config.locales[0] || 'en'
|
|
60
|
-
config.extract.secondaryLanguages ||= config.locales.filter((l: string) => l !== config?.extract?.primaryLanguage)
|
|
61
|
-
const primaryLanguage = config.extract.primaryLanguage
|
|
62
64
|
|
|
63
65
|
// Group keys by namespace
|
|
64
66
|
const keysByNS = new Map<string, ExtractedKey[]>()
|
|
65
67
|
for (const key of keys.values()) {
|
|
66
68
|
const ns = key.ns || defaultNS
|
|
67
|
-
if (!keysByNS.has(ns))
|
|
68
|
-
keysByNS.set(ns, [])
|
|
69
|
-
}
|
|
69
|
+
if (!keysByNS.has(ns)) keysByNS.set(ns, [])
|
|
70
70
|
keysByNS.get(ns)!.push(key)
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const results: TranslationResult[] = []
|
|
74
74
|
|
|
75
75
|
for (const locale of config.locales) {
|
|
76
|
-
const
|
|
77
|
-
|
|
76
|
+
const existingMergedFile = mergeNamespaces
|
|
77
|
+
? await loadTranslationFile(resolve(process.cwd(), getOutputPath(config.extract.output, locale))) || {}
|
|
78
|
+
: null
|
|
79
|
+
|
|
80
|
+
const newMergedTranslations: Record<string, any> = {}
|
|
78
81
|
|
|
79
82
|
for (const [ns, nsKeys] of keysByNS.entries()) {
|
|
80
83
|
const outputPath = getOutputPath(config.extract.output, locale, mergeNamespaces ? undefined : ns)
|
|
81
84
|
const fullPath = resolve(process.cwd(), outputPath)
|
|
82
85
|
|
|
83
|
-
const existingTranslations =
|
|
86
|
+
const existingTranslations = mergeNamespaces
|
|
87
|
+
? existingMergedFile?.[ns] || {}
|
|
88
|
+
: await loadTranslationFile(fullPath) || {}
|
|
89
|
+
|
|
84
90
|
const newTranslations: Record<string, any> = {}
|
|
85
91
|
|
|
86
92
|
const existingKeys = getNestedKeys(existingTranslations, keySeparator)
|
|
@@ -91,10 +97,7 @@ export async function getTranslations (
|
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
99
|
|
|
94
|
-
const sortedKeys = (config.extract.sort === false)
|
|
95
|
-
? nsKeys
|
|
96
|
-
: [...nsKeys].sort((a, b) => a.key.localeCompare(b.key))
|
|
97
|
-
|
|
100
|
+
const sortedKeys = (config.extract.sort === false) ? nsKeys : [...nsKeys].sort((a, b) => a.key.localeCompare(b.key))
|
|
98
101
|
for (const { key, defaultValue } of sortedKeys) {
|
|
99
102
|
const existingValue = getNestedValue(existingTranslations, key, keySeparator)
|
|
100
103
|
const valueToSet = existingValue ?? (locale === primaryLanguage ? defaultValue : (config.extract.defaultValue ?? ''))
|
|
@@ -102,35 +105,20 @@ export async function getTranslations (
|
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
if (mergeNamespaces) {
|
|
105
|
-
|
|
106
|
-
if (Object.keys(existingTranslations).length > 0) {
|
|
107
|
-
mergedExisting[ns] = existingTranslations
|
|
108
|
-
}
|
|
108
|
+
newMergedTranslations[ns] = newTranslations
|
|
109
109
|
} else {
|
|
110
|
-
const oldContent =
|
|
111
|
-
const newContent = JSON.stringify(newTranslations, null,
|
|
112
|
-
|
|
113
|
-
results.push({
|
|
114
|
-
path: fullPath,
|
|
115
|
-
updated: newContent !== oldContent,
|
|
116
|
-
newTranslations,
|
|
117
|
-
existingTranslations,
|
|
118
|
-
})
|
|
110
|
+
const oldContent = JSON.stringify(existingTranslations, null, indentation)
|
|
111
|
+
const newContent = JSON.stringify(newTranslations, null, indentation)
|
|
112
|
+
results.push({ path: fullPath, updated: newContent !== oldContent, newTranslations, existingTranslations })
|
|
119
113
|
}
|
|
120
114
|
}
|
|
121
115
|
|
|
122
116
|
if (mergeNamespaces) {
|
|
123
117
|
const outputPath = getOutputPath(config.extract.output, locale)
|
|
124
118
|
const fullPath = resolve(process.cwd(), outputPath)
|
|
125
|
-
const oldContent =
|
|
126
|
-
const newContent = JSON.stringify(
|
|
127
|
-
|
|
128
|
-
results.push({
|
|
129
|
-
path: fullPath,
|
|
130
|
-
updated: newContent !== oldContent,
|
|
131
|
-
newTranslations: mergedTranslations,
|
|
132
|
-
existingTranslations: mergedExisting,
|
|
133
|
-
})
|
|
119
|
+
const oldContent = JSON.stringify(existingMergedFile, null, indentation)
|
|
120
|
+
const newContent = JSON.stringify(newMergedTranslations, null, indentation)
|
|
121
|
+
results.push({ path: fullPath, updated: newContent !== oldContent, newTranslations: newMergedTranslations, existingTranslations: existingMergedFile || {} })
|
|
134
122
|
}
|
|
135
123
|
}
|
|
136
124
|
|
|
@@ -365,11 +365,12 @@ export class ASTVisitors {
|
|
|
365
365
|
* @private
|
|
366
366
|
*/
|
|
367
367
|
private handleCallExpression (node: CallExpression): void {
|
|
368
|
-
const
|
|
369
|
-
if (
|
|
368
|
+
const functionName = this.getFunctionName(node.callee)
|
|
369
|
+
if (!functionName) return
|
|
370
370
|
|
|
371
|
-
|
|
372
|
-
const
|
|
371
|
+
// The scope lookup will only work for simple identifiers, which is okay for this fix.
|
|
372
|
+
const scopeInfo = this.getVarFromScope(functionName)
|
|
373
|
+
const isFunctionToParse = (this.config.extract.functions || ['t']).includes(functionName) || scopeInfo !== undefined
|
|
373
374
|
if (!isFunctionToParse || node.arguments.length === 0) return
|
|
374
375
|
|
|
375
376
|
const firstArg = node.arguments[0].expression
|
|
@@ -858,4 +859,58 @@ export class ASTVisitors {
|
|
|
858
859
|
}
|
|
859
860
|
return undefined
|
|
860
861
|
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Serializes a callee node (Identifier or MemberExpression) into a string.
|
|
865
|
+
*
|
|
866
|
+
* Produces a dotted name for simple callees that can be used for scope lookups
|
|
867
|
+
* or configuration matching.
|
|
868
|
+
*
|
|
869
|
+
* Supported inputs:
|
|
870
|
+
* - Identifier: returns the identifier name (e.g., `t` -> "t")
|
|
871
|
+
* - MemberExpression with Identifier parts: returns a dotted path of identifiers
|
|
872
|
+
* (e.g., `i18n.t` -> "i18n.t", `i18n.getFixedT` -> "i18n.getFixedT")
|
|
873
|
+
*
|
|
874
|
+
* Behavior notes:
|
|
875
|
+
* - Computed properties are not supported and cause this function to return null
|
|
876
|
+
* (e.g., `i18n['t']` -> null).
|
|
877
|
+
* - The base of a MemberExpression must be a simple Identifier. More complex bases
|
|
878
|
+
* (other expressions, `this`, etc.) will result in null.
|
|
879
|
+
* - This function does not attempt to resolve or evaluate expressions — it only
|
|
880
|
+
* serializes static identifier/member chains.
|
|
881
|
+
*
|
|
882
|
+
* Examples:
|
|
883
|
+
* - Identifier callee: { type: 'Identifier', value: 't' } -> "t"
|
|
884
|
+
* - Member callee: { type: 'MemberExpression', object: { type: 'Identifier', value: 'i18n' }, property: { type: 'Identifier', value: 't' } } -> "i18n.t"
|
|
885
|
+
*
|
|
886
|
+
* @param callee - The CallExpression callee node to serialize
|
|
887
|
+
* @returns A dotted string name for supported callees, or null when the callee
|
|
888
|
+
* is a computed/unsupported expression.
|
|
889
|
+
*
|
|
890
|
+
* @private
|
|
891
|
+
*/
|
|
892
|
+
private getFunctionName (callee: CallExpression['callee']): string | null {
|
|
893
|
+
if (callee.type === 'Identifier') {
|
|
894
|
+
return callee.value
|
|
895
|
+
}
|
|
896
|
+
if (callee.type === 'MemberExpression') {
|
|
897
|
+
const parts: string[] = []
|
|
898
|
+
let current: any = callee
|
|
899
|
+
while (current.type === 'MemberExpression') {
|
|
900
|
+
if (current.property.type === 'Identifier') {
|
|
901
|
+
parts.unshift(current.property.value)
|
|
902
|
+
} else {
|
|
903
|
+
return null // Cannot handle computed properties like i18n['t']
|
|
904
|
+
}
|
|
905
|
+
current = current.object
|
|
906
|
+
}
|
|
907
|
+
if (current.type === 'Identifier') {
|
|
908
|
+
parts.unshift(current.value)
|
|
909
|
+
} else {
|
|
910
|
+
return null // Base of the expression is not a simple identifier
|
|
911
|
+
}
|
|
912
|
+
return parts.join('.')
|
|
913
|
+
}
|
|
914
|
+
return null
|
|
915
|
+
}
|
|
861
916
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAgBnF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,iBAAiB,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"translation-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/translation-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAgBnF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,EAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,iBAAiB,EAAE,CAAC,CA6E9B"}
|
|
@@ -271,5 +271,35 @@ export declare class ASTVisitors {
|
|
|
271
271
|
* @returns The resolved UseTranslationHookConfig when a match is found, otherwise undefined
|
|
272
272
|
*/
|
|
273
273
|
private getUseTranslationConfig;
|
|
274
|
+
/**
|
|
275
|
+
* Serializes a callee node (Identifier or MemberExpression) into a string.
|
|
276
|
+
*
|
|
277
|
+
* Produces a dotted name for simple callees that can be used for scope lookups
|
|
278
|
+
* or configuration matching.
|
|
279
|
+
*
|
|
280
|
+
* Supported inputs:
|
|
281
|
+
* - Identifier: returns the identifier name (e.g., `t` -> "t")
|
|
282
|
+
* - MemberExpression with Identifier parts: returns a dotted path of identifiers
|
|
283
|
+
* (e.g., `i18n.t` -> "i18n.t", `i18n.getFixedT` -> "i18n.getFixedT")
|
|
284
|
+
*
|
|
285
|
+
* Behavior notes:
|
|
286
|
+
* - Computed properties are not supported and cause this function to return null
|
|
287
|
+
* (e.g., `i18n['t']` -> null).
|
|
288
|
+
* - The base of a MemberExpression must be a simple Identifier. More complex bases
|
|
289
|
+
* (other expressions, `this`, etc.) will result in null.
|
|
290
|
+
* - This function does not attempt to resolve or evaluate expressions — it only
|
|
291
|
+
* serializes static identifier/member chains.
|
|
292
|
+
*
|
|
293
|
+
* Examples:
|
|
294
|
+
* - Identifier callee: { type: 'Identifier', value: 't' } -> "t"
|
|
295
|
+
* - Member callee: { type: 'MemberExpression', object: { type: 'Identifier', value: 'i18n' }, property: { type: 'Identifier', value: 't' } } -> "i18n.t"
|
|
296
|
+
*
|
|
297
|
+
* @param callee - The CallExpression callee node to serialize
|
|
298
|
+
* @returns A dotted string name for supported callees, or null when the callee
|
|
299
|
+
* is a computed/unsupported expression.
|
|
300
|
+
*
|
|
301
|
+
* @private
|
|
302
|
+
*/
|
|
303
|
+
private getFunctionName;
|
|
274
304
|
}
|
|
275
305
|
//# sourceMappingURL=ast-visitors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA+G,MAAM,WAAW,CAAA;AACpJ,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAoB9E;;;;;;;;;;;;;;;;;;;;;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;IAoDZ;;;;;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;
|
|
1
|
+
{"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAA+G,MAAM,WAAW,CAAA;AACpJ,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAoB9E;;;;;;;;;;;;;;;;;;;;;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;IAoDZ;;;;;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;IA0H5B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,gBAAgB;IAqDxB;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAkB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IAuDxB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,kBAAkB;IA6B1B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,2BAA2B;IAmBnC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,OAAO,CAAC,eAAe;CAwBxB"}
|