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