i18next-cli 1.14.0 → 1.16.0
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 +8 -0
- package/README.md +13 -0
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/config.js +1 -1
- package/dist/cjs/extractor/parsers/scope-manager.js +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/config.js +1 -1
- package/dist/esm/extractor/parsers/scope-manager.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +22 -10
- package/src/config.ts +31 -35
- package/src/extractor/parsers/scope-manager.ts +122 -51
- package/types/config.d.ts +6 -24
- package/types/config.d.ts.map +1 -1
- package/types/extractor/parsers/scope-manager.d.ts +17 -15
- package/types/extractor/parsers/scope-manager.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ 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.16.0](https://github.com/i18next/i18next-cli/compare/v1.15.0...v1.16.0) - 2025-10-28
|
|
9
|
+
|
|
10
|
+
- feat(extractor): better support for custom translation hooks returning t/getFixedT [#78](https://github.com/i18next/i18next-cli/issues/78)
|
|
11
|
+
|
|
12
|
+
## [1.15.0](https://github.com/i18next/i18next-cli/compare/v1.14.0...v1.15.0) - 2025-10-27
|
|
13
|
+
|
|
14
|
+
- feat(cli): add global `-c, --config <path>` option to override automatic config detection and point the CLI at a specific i18next config file. [#77](https://github.com/i18next/i18next-cli/issues/77)
|
|
15
|
+
|
|
8
16
|
## [1.14.0](https://github.com/i18next/i18next-cli/compare/v1.13.1...v1.14.0) - 2025-10-27
|
|
9
17
|
|
|
10
18
|
- Parse simple template strings when extracting defaultValue-s [#76](https://github.com/i18next/i18next-cli/pull/76)
|
package/README.md
CHANGED
|
@@ -251,6 +251,19 @@ npx i18next-cli locize-sync [options]
|
|
|
251
251
|
|
|
252
252
|
**Interactive Setup:** If your locize credentials are missing or invalid, the toolkit will guide you through an interactive setup process to configure your Project ID, API Key, and version.
|
|
253
253
|
|
|
254
|
+
## Global Options
|
|
255
|
+
|
|
256
|
+
- `-c, --config <path>` — Override automatic config detection and use the specified config file (relative to cwd or absolute). This option is forwarded to commands that load or ensure a config (e.g. extract, status, types, sync, locize-*).
|
|
257
|
+
|
|
258
|
+
Examples:
|
|
259
|
+
```bash
|
|
260
|
+
# Use a config file stored in a package subfolder (monorepo)
|
|
261
|
+
npx i18next-cli extract --config ./packages/my-package/config/i18next.config.ts
|
|
262
|
+
|
|
263
|
+
# Short flag variant, for status
|
|
264
|
+
npx i18next-cli status de -c ./packages/my-package/config/i18next.config.ts
|
|
265
|
+
```
|
|
266
|
+
|
|
254
267
|
## Configuration
|
|
255
268
|
|
|
256
269
|
The configuration file supports both TypeScript (`.ts`) and JavaScript (`.js`) formats. Use the `defineConfig` helper for type safety and IntelliSense.
|
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"),
|
|
2
|
+
"use strict";var e=require("commander"),t=require("chokidar"),o=require("glob"),n=require("minimatch"),i=require("chalk"),a=require("./config.js"),r=require("./heuristic-config.js"),c=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var s=require("./types-generator.js"),l=require("./syncer.js"),u=require("./migrator.js"),g=require("./init.js"),d=require("./linter.js"),p=require("./status.js"),f=require("./locize.js");const m=new e.Command;m.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.16.0"),m.option("-c, --config <path>","Path to i18next-cli config file (overrides detection)"),m.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").option("--dry-run","Run the extractor without writing any files to disk.").option("--sync-primary","Sync primary language values with default values from code.").action(async e=>{try{const o=m.opts().config,i=await a.ensureConfig(o),r=async()=>{const t=await c.runExtractor(i,{isWatchMode:!!e.watch,isDryRun:!!e.dryRun,syncPrimaryWithDefaults:!!e.syncPrimary});return e.ci&&!t?(console.log("✅ No files were updated."),process.exit(0)):e.ci&&t&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1)),t};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await w(i.extract.input),o=y(i.extract.ignore),a=h(i.extract.output),c=[...o,...a].filter(Boolean),s=e.filter(e=>!c.some(t=>n.minimatch(e,t,{dot:!0})));t.watch(s,{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}catch(e){console.error("Error running extractor:",e),process.exit(1)}}),m.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)=>{const o=m.opts().config;let n=await a.loadConfig(o);if(!n){console.log(i.blue("No config file found. Attempting to detect project structure..."));const e=await r.detectConfig();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 i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),n=e}await p.runStatus(n,{detail:e,namespace:t.namespace})}),m.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const o=m.opts().config,i=await a.ensureConfig(o),r=()=>s.runTypesGenerator(i);if(await r(),e.watch){console.log("\nWatching for changes...");const e=await w(i.types?.input||[]),o=[...y(i.extract?.ignore)].filter(Boolean),a=e.filter(e=>!o.some(t=>n.minimatch(e,t,{dot:!0})));t.watch(a,{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}),m.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=m.opts().config,t=await a.ensureConfig(e);await l.runSyncer(t)}),m.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await u.runMigrator(e)}),m.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(g.runInit),m.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const o=m.opts().config,c=async()=>{let e=await a.loadConfig(o);if(!e){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await r.detectConfig();t||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),e=t}await d.runLinter(e)};if(await c(),e.watch){console.log("\nWatching for changes...");const e=await a.loadConfig(o);if(e?.extract?.input){const o=await w(e.extract.input),i=[...y(e.extract.ignore),...h(e.extract.output)].filter(Boolean),a=o.filter(e=>!i.some(t=>n.minimatch(e,t,{dot:!0})));t.watch(a,{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}}),m.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=m.opts().config,o=await a.ensureConfig(t);await f.runLocizeSync(o,e)}),m.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=m.opts().config,o=await a.ensureConfig(t);await f.runLocizeDownload(o,e)}),m.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=m.opts().config,o=await a.ensureConfig(t);await f.runLocizeMigrate(o,e)}),m.parse(process.argv);const y=e=>Array.isArray(e)?e:e?[e]:[],h=e=>e&&"string"==typeof e?[e.replace(/\{\{[^}]+\}\}/g,"*")]:[],w=async(e=[])=>{const t=y(e),n=await Promise.all(t.map(e=>o.glob(e||"",{nodir:!0})));return Array.from(new Set(n.flat()))};
|
package/dist/cjs/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("node:path"),r=require("node:url"),t=require("node:fs/promises"),n=require("jiti"),o=require("jsonc-parser"),i=require("inquirer"),a=require("chalk"),s=require("./init.js"),c=require("./utils/logger.js");const u=["i18next.config.ts","i18next.config.js","i18next.config.mjs","i18next.config.cjs"];async function l(o=new c.ConsoleLogger){const
|
|
1
|
+
"use strict";var e=require("node:path"),r=require("node:url"),t=require("node:fs/promises"),n=require("jiti"),o=require("jsonc-parser"),i=require("inquirer"),a=require("chalk"),s=require("./init.js"),c=require("./utils/logger.js");const u=["i18next.config.ts","i18next.config.js","i18next.config.mjs","i18next.config.cjs"];async function l(o,i=new c.ConsoleLogger){const a=await async function(r){if(r){const n=e.resolve(process.cwd(),r);try{return await t.access(n),n}catch{return null}}for(const r of u){const n=e.resolve(process.cwd(),r);try{return await t.access(n),n}catch{}}return null}(o);if(!a)return o&&i.error(`Error: Config file not found at "${o}"`),null;try{let e;if(a.endsWith(".ts")){const r=await f(),t=n.createJiti(process.cwd(),{alias:r,interopDefault:!1}),o=await t.import(a,{default:!0});e=o}else{const t=r.pathToFileURL(a).href,n=await import(`${t}?t=${Date.now()}`);e=n.default}return e?(e.extract||={},e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(r=>r!==e.extract.primaryLanguage),e):(i.error(`Error: No default export found in ${a}`),null)}catch(e){return i.error(`Error loading configuration from ${a}`),i.error(e),null}}async function f(){try{const r=await async function(){let r=process.cwd();for(;;){const n=e.join(r,"tsconfig.json");try{return await t.access(n),n}catch{const t=e.dirname(r);if(t===r)return null;r=t}}}();if(!r)return{};const n=await t.readFile(r,"utf-8"),i=o.parse(n),a=i.compilerOptions?.paths,s=i.compilerOptions?.baseUrl||".";if(!a)return{};const c={};for(const[r,t]of Object.entries(a))if(Array.isArray(t)&&t.length>0){const n=r.replace("/*",""),o=e.resolve(process.cwd(),s,t[0].replace("/*",""));c[n]=o}return c}catch(e){return{}}}exports.defineConfig=function(e){return e},exports.ensureConfig=async function(e,r=new c.ConsoleLogger){let t=await l(e,r);if(t)return t;const{shouldInit:n}=await i.prompt([{type:"confirm",name:"shouldInit",message:a.yellow("Configuration file not found. Would you like to create one now?"),default:!0}]);if(n){if(await s.runInit(),r.info(a.green("Configuration created. Resuming command...")),t=await l(e,r),t)return t;r.error(a.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)},exports.getTsConfigAliases=f,exports.loadConfig=l;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("./ast-utils.js");exports.ScopeManager=class{scopeStack=[];config;scope=new Map;constructor(e){this.config=e}reset(){this.scopeStack=[],this.scope=new Map}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0
|
|
1
|
+
"use strict";var e=require("./ast-utils.js");exports.ScopeManager=class{scopeStack=[];config;scope=new Map;simpleConstants=new Map;constructor(e){this.config=e}reset(){this.scopeStack=[],this.scope=new Map,this.simpleConstants.clear()}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):this.scope.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)}const t=this.scope.get(e);if(t)return t}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const i of t){if("string"==typeof i&&i===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof i&&i.name===e)return{name:i.name,nsArg:i.nsArg??0,keyPrefixArg:i.keyPrefixArg??1}}}resolveSimpleStringIdentifier(e){return this.simpleConstants.get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;"Identifier"===e.id.type&&"StringLiteral"===t.type&&this.simpleConstants.set(e.id.value,t.value);const i="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!i)return;const r=i.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return this.handleUseTranslationDeclarator(e,i,t),void this.handleUseTranslationForComments(e,i,t)}if("Identifier"===r.type){if(this.getVarFromScope(r.value))return void this.handleGetFixedTFromVariableDeclarator(e,i,r.value)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,i)}handleUseTranslationForComments(e,t,i){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&("t"===t.key.value||"getFixedT"===t.key.value)){r=t.key.value;break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&("t"===t.key.value||"getFixedT"===t.key.value)&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const s=-1===i.nsArg?void 0:t.arguments?.[i.nsArg??0]?.expression,n=t.arguments?.[i.keyPrefixArg??1]?.expression;let a,o;if("StringLiteral"===s?.type?a=s.value:"ArrayExpression"===s?.type&&"StringLiteral"===s.elements[0]?.expression.type&&(a=s.elements[0].expression.value),"ObjectExpression"===n?.type){const e=n.properties.find(e=>"KeyValueProperty"===e.type&&"Identifier"===e.key.type&&"keyPrefix"===e.key.value);"KeyValueProperty"===e?.type&&"StringLiteral"===e.value.type&&(o=e.value.value)}else if("StringLiteral"===n?.type)o=n.value;else if("Identifier"===n?.type)o=this.resolveSimpleStringIdentifier(n.value);else if("TemplateLiteral"===n?.type){const e=n;0===(e.expressions||[]).length&&(o=e.quasis?.[0]?.cooked??void 0)}(a||o)&&this.scope.set(r,{defaultNs:a,keyPrefix:o})}handleUseTranslationDeclarator(t,i,r){let s;if("Identifier"===t.id.type&&(s=t.id.value),"ArrayPattern"===t.id.type){const e=t.id.elements[0];"Identifier"===e?.type&&(s=e.value)}if("ObjectPattern"===t.id.type)for(const e of t.id.properties){if("AssignmentPatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)){s=e.key.value;break}if("KeyValuePatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)&&"Identifier"===e.value.type){s=e.value.value;break}}if(!s)return;const n=-1===r.nsArg?void 0:i.arguments?.[r.nsArg??0]?.expression;let a;"StringLiteral"===n?.type?a=n.value:"ArrayExpression"===n?.type&&"StringLiteral"===n.elements[0]?.expression.type&&(a=n.elements[0].expression.value);const o=i.arguments?.[r.keyPrefixArg??1]?.expression;let l;if("ObjectExpression"===o?.type){const t=e.getObjectPropValue(o,"keyPrefix");l="string"==typeof t?t:void 0}else if("StringLiteral"===o?.type)l=o.value;else if("Identifier"===o?.type)l=this.resolveSimpleStringIdentifier(o.value);else if("TemplateLiteral"===o?.type){const e=o;0===(e.expressions||[]).length&&(l=e.quasis?.[0]?.cooked??void 0)}this.setVarInScope(s,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const i=e.id.value,r=t.arguments,s=r[1]?.expression,n=r[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===n?.type?n.value:void 0;(a||o)&&this.setVarInScope(i,{defaultNs:a,keyPrefix:o})}handleGetFixedTFromVariableDeclarator(e,t,i){if("Identifier"!==e.id.type)return;const r=e.id.value,s=this.getVarFromScope(i);if(!s)return;const n=t.arguments,a=n[1]?.expression,o=n[2]?.expression,l="StringLiteral"===a?.type?a.value:void 0,p="StringLiteral"===o?.type?o.value:void 0,y=l??s.defaultNs,f=p??s.keyPrefix;(y||f)&&this.setVarInScope(r,{defaultNs:y,keyPrefix:f})}};
|
package/dist/esm/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as t}from"commander";import
|
|
2
|
+
import{Command as t}from"commander";import o from"chokidar";import{glob as e}from"glob";import{minimatch as i}from"minimatch";import n from"chalk";import{ensureConfig as a,loadConfig as r}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as s}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as l}from"./types-generator.js";import{runSyncer as p}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as f}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as g}from"./status.js";import{runLocizeSync as u,runLocizeDownload as y,runLocizeMigrate as h}from"./locize.js";const w=new t;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.16.0"),w.option("-c, --config <path>","Path to i18next-cli config file (overrides detection)"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").option("--dry-run","Run the extractor without writing any files to disk.").option("--sync-primary","Sync primary language values with default values from code.").action(async t=>{try{const e=w.opts().config,n=await a(e),r=async()=>{const o=await s(n,{isWatchMode:!!t.watch,isDryRun:!!t.dryRun,syncPrimaryWithDefaults:!!t.syncPrimary});return t.ci&&!o?(console.log("✅ No files were updated."),process.exit(0)):t.ci&&o&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1)),o};if(await r(),t.watch){console.log("\nWatching for changes...");const t=await z(n.extract.input),e=x(n.extract.ignore),a=j(n.extract.output),c=[...e,...a].filter(Boolean),s=t.filter(t=>!c.some(o=>i(t,o,{dot:!0})));o.watch(s,{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),r()})}}catch(t){console.error("Error running extractor:",t),process.exit(1)}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(t,o)=>{const e=w.opts().config;let i=await r(e);if(!i){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),i=t}await g(i,{detail:t,namespace:o.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async t=>{const e=w.opts().config,n=await a(e),r=()=>l(n);if(await r(),t.watch){console.log("\nWatching for changes...");const t=await z(n.types?.input||[]),e=[...x(n.extract?.ignore)].filter(Boolean),a=t.filter(t=>!e.some(o=>i(t,o,{dot:!0})));o.watch(a,{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),r()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=w.opts().config,o=await a(t);await p(o)}),w.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await m(t)}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(f),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async t=>{const e=w.opts().config,a=async()=>{let t=await r(e);if(!t){console.log(n.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),t=o}await d(t)};if(await a(),t.watch){console.log("\nWatching for changes...");const t=await r(e);if(t?.extract?.input){const e=await z(t.extract.input),n=[...x(t.extract.ignore),...j(t.extract.output)].filter(Boolean),r=e.filter(t=>!n.some(o=>i(t,o,{dot:!0})));o.watch(r,{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async t=>{const o=w.opts().config,e=await a(o);await u(e,t)}),w.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const o=w.opts().config,e=await a(o);await y(e,t)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const o=w.opts().config,e=await a(o);await h(e,t)}),w.parse(process.argv);const x=t=>Array.isArray(t)?t:t?[t]:[],j=t=>t&&"string"==typeof t?[t.replace(/\{\{[^}]+\}\}/g,"*")]:[],z=async(t=[])=>{const o=x(t),i=await Promise.all(o.map(t=>e(t||"",{nodir:!0})));return Array.from(new Set(i.flat()))};
|
package/dist/esm/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolve as r,join as t,dirname as o}from"node:path";import{pathToFileURL as n}from"node:url";import{access as e,readFile as i}from"node:fs/promises";import{createJiti as a}from"jiti";import{parse as c}from"jsonc-parser";import s from"inquirer";import f from"chalk";import{runInit as l}from"./init.js";import{ConsoleLogger as u}from"./utils/logger.js";const p=["i18next.config.ts","i18next.config.js","i18next.config.mjs","i18next.config.cjs"];function m(r){return r}async function d(t=new u){const
|
|
1
|
+
import{resolve as r,join as t,dirname as o}from"node:path";import{pathToFileURL as n}from"node:url";import{access as e,readFile as i}from"node:fs/promises";import{createJiti as a}from"jiti";import{parse as c}from"jsonc-parser";import s from"inquirer";import f from"chalk";import{runInit as l}from"./init.js";import{ConsoleLogger as u}from"./utils/logger.js";const p=["i18next.config.ts","i18next.config.js","i18next.config.mjs","i18next.config.cjs"];function m(r){return r}async function d(t,o=new u){const i=await async function(t){if(t){const o=r(process.cwd(),t);try{return await e(o),o}catch{return null}}for(const t of p){const o=r(process.cwd(),t);try{return await e(o),o}catch{}}return null}(t);if(!i)return t&&o.error(`Error: Config file not found at "${t}"`),null;try{let r;if(i.endsWith(".ts")){const t=await w(),o=a(process.cwd(),{alias:t,interopDefault:!1}),n=await o.import(i,{default:!0});r=n}else{const t=n(i).href,o=await import(`${t}?t=${Date.now()}`);r=o.default}return r?(r.extract||={},r.extract.primaryLanguage||=r.locales[0]||"en",r.extract.secondaryLanguages||=r.locales.filter(t=>t!==r.extract.primaryLanguage),r):(o.error(`Error: No default export found in ${i}`),null)}catch(r){return o.error(`Error loading configuration from ${i}`),o.error(r),null}}async function g(r,t=new u){let o=await d(r,t);if(o)return o;const{shouldInit:n}=await s.prompt([{type:"confirm",name:"shouldInit",message:f.yellow("Configuration file not found. Would you like to create one now?"),default:!0}]);if(n){if(await l(),t.info(f.green("Configuration created. Resuming command...")),o=await d(r,t),o)return o;t.error(f.red("Error: Failed to load configuration after creation. Please try running the command again.")),process.exit(1)}else t.info("Operation cancelled. Please create a configuration file to proceed."),process.exit(0)}async function w(){try{const n=await async function(){let r=process.cwd();for(;;){const n=t(r,"tsconfig.json");try{return await e(n),n}catch{const t=o(r);if(t===r)return null;r=t}}}();if(!n)return{};const a=await i(n,"utf-8"),s=c(a),f=s.compilerOptions?.paths,l=s.compilerOptions?.baseUrl||".";if(!f)return{};const u={};for(const[t,o]of Object.entries(f))if(Array.isArray(o)&&o.length>0){const n=t.replace("/*",""),e=r(process.cwd(),l,o[0].replace("/*",""));u[n]=e}return u}catch(r){return{}}}export{m as defineConfig,g as ensureConfig,w as getTsConfigAliases,d as loadConfig};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getObjectPropValue as e}from"./ast-utils.js";class t{scopeStack=[];config;scope=new Map;constructor(e){this.config=e}reset(){this.scopeStack=[],this.scope=new Map}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0
|
|
1
|
+
import{getObjectPropValue as e}from"./ast-utils.js";class t{scopeStack=[];config;scope=new Map;simpleConstants=new Map;constructor(e){this.config=e}reset(){this.scopeStack=[],this.scope=new Map,this.simpleConstants.clear()}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):this.scope.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)}const t=this.scope.get(e);if(t)return t}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const i of t){if("string"==typeof i&&i===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof i&&i.name===e)return{name:i.name,nsArg:i.nsArg??0,keyPrefixArg:i.keyPrefixArg??1}}}resolveSimpleStringIdentifier(e){return this.simpleConstants.get(e)}handleVariableDeclarator(e){const t=e.init;if(!t)return;"Identifier"===e.id.type&&"StringLiteral"===t.type&&this.simpleConstants.set(e.id.value,t.value);const i="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!i)return;const r=i.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return this.handleUseTranslationDeclarator(e,i,t),void this.handleUseTranslationForComments(e,i,t)}if("Identifier"===r.type){if(this.getVarFromScope(r.value))return void this.handleGetFixedTFromVariableDeclarator(e,i,r.value)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,i)}handleUseTranslationForComments(e,t,i){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&("t"===t.key.value||"getFixedT"===t.key.value)){r=t.key.value;break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&("t"===t.key.value||"getFixedT"===t.key.value)&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const s=-1===i.nsArg?void 0:t.arguments?.[i.nsArg??0]?.expression,n=t.arguments?.[i.keyPrefixArg??1]?.expression;let a,o;if("StringLiteral"===s?.type?a=s.value:"ArrayExpression"===s?.type&&"StringLiteral"===s.elements[0]?.expression.type&&(a=s.elements[0].expression.value),"ObjectExpression"===n?.type){const e=n.properties.find(e=>"KeyValueProperty"===e.type&&"Identifier"===e.key.type&&"keyPrefix"===e.key.value);"KeyValueProperty"===e?.type&&"StringLiteral"===e.value.type&&(o=e.value.value)}else if("StringLiteral"===n?.type)o=n.value;else if("Identifier"===n?.type)o=this.resolveSimpleStringIdentifier(n.value);else if("TemplateLiteral"===n?.type){const e=n;0===(e.expressions||[]).length&&(o=e.quasis?.[0]?.cooked??void 0)}(a||o)&&this.scope.set(r,{defaultNs:a,keyPrefix:o})}handleUseTranslationDeclarator(t,i,r){let s;if("Identifier"===t.id.type&&(s=t.id.value),"ArrayPattern"===t.id.type){const e=t.id.elements[0];"Identifier"===e?.type&&(s=e.value)}if("ObjectPattern"===t.id.type)for(const e of t.id.properties){if("AssignmentPatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)){s=e.key.value;break}if("KeyValuePatternProperty"===e.type&&"Identifier"===e.key.type&&("t"===e.key.value||"getFixedT"===e.key.value)&&"Identifier"===e.value.type){s=e.value.value;break}}if(!s)return;const n=-1===r.nsArg?void 0:i.arguments?.[r.nsArg??0]?.expression;let a;"StringLiteral"===n?.type?a=n.value:"ArrayExpression"===n?.type&&"StringLiteral"===n.elements[0]?.expression.type&&(a=n.elements[0].expression.value);const o=i.arguments?.[r.keyPrefixArg??1]?.expression;let l;if("ObjectExpression"===o?.type){const t=e(o,"keyPrefix");l="string"==typeof t?t:void 0}else if("StringLiteral"===o?.type)l=o.value;else if("Identifier"===o?.type)l=this.resolveSimpleStringIdentifier(o.value);else if("TemplateLiteral"===o?.type){const e=o;0===(e.expressions||[]).length&&(l=e.quasis?.[0]?.cooked??void 0)}this.setVarInScope(s,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const i=e.id.value,r=t.arguments,s=r[1]?.expression,n=r[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===n?.type?n.value:void 0;(a||o)&&this.setVarInScope(i,{defaultNs:a,keyPrefix:o})}handleGetFixedTFromVariableDeclarator(e,t,i){if("Identifier"!==e.id.type)return;const r=e.id.value,s=this.getVarFromScope(i);if(!s)return;const n=t.arguments,a=n[1]?.expression,o=n[2]?.expression,l="StringLiteral"===a?.type?a.value:void 0,p="StringLiteral"===o?.type?o.value:void 0,y=l??s.defaultNs,f=p??s.keyPrefix;(y||f)&&this.setVarInScope(r,{defaultNs:y,keyPrefix:f})}}export{t as ScopeManager};
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -22,7 +22,10 @@ const program = new Command()
|
|
|
22
22
|
program
|
|
23
23
|
.name('i18next-cli')
|
|
24
24
|
.description('A unified, high-performance i18next CLI.')
|
|
25
|
-
.version('1.
|
|
25
|
+
.version('1.16.0')
|
|
26
|
+
|
|
27
|
+
// new: global config override option
|
|
28
|
+
program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)')
|
|
26
29
|
|
|
27
30
|
program
|
|
28
31
|
.command('extract')
|
|
@@ -33,7 +36,8 @@ program
|
|
|
33
36
|
.option('--sync-primary', 'Sync primary language values with default values from code.')
|
|
34
37
|
.action(async (options) => {
|
|
35
38
|
try {
|
|
36
|
-
const
|
|
39
|
+
const cfgPath = program.opts().config
|
|
40
|
+
const config = await ensureConfig(cfgPath)
|
|
37
41
|
|
|
38
42
|
const runExtract = async () => {
|
|
39
43
|
const success = await runExtractor(config, {
|
|
@@ -88,7 +92,8 @@ program
|
|
|
88
92
|
.description('Display translation status. Provide a locale for a detailed key-by-key view.')
|
|
89
93
|
.option('-n, --namespace <ns>', 'Filter the status report by a specific namespace')
|
|
90
94
|
.action(async (locale, options) => {
|
|
91
|
-
|
|
95
|
+
const cfgPath = program.opts().config
|
|
96
|
+
let config = await loadConfig(cfgPath)
|
|
92
97
|
if (!config) {
|
|
93
98
|
console.log(chalk.blue('No config file found. Attempting to detect project structure...'))
|
|
94
99
|
const detected = await detectConfig()
|
|
@@ -108,7 +113,8 @@ program
|
|
|
108
113
|
.description('Generate TypeScript definitions from translation resource files.')
|
|
109
114
|
.option('-w, --watch', 'Watch for file changes and re-run the type generator.')
|
|
110
115
|
.action(async (options) => {
|
|
111
|
-
const
|
|
116
|
+
const cfgPath = program.opts().config
|
|
117
|
+
const config = await ensureConfig(cfgPath)
|
|
112
118
|
|
|
113
119
|
const run = () => runTypesGenerator(config)
|
|
114
120
|
await run()
|
|
@@ -130,7 +136,8 @@ program
|
|
|
130
136
|
.command('sync')
|
|
131
137
|
.description('Synchronize secondary language files with the primary language file.')
|
|
132
138
|
.action(async () => {
|
|
133
|
-
const
|
|
139
|
+
const cfgPath = program.opts().config
|
|
140
|
+
const config = await ensureConfig(cfgPath)
|
|
134
141
|
await runSyncer(config)
|
|
135
142
|
})
|
|
136
143
|
|
|
@@ -151,9 +158,11 @@ program
|
|
|
151
158
|
.description('Find potential issues like hardcoded strings in your codebase.')
|
|
152
159
|
.option('-w, --watch', 'Watch for file changes and re-run the linter.')
|
|
153
160
|
.action(async (options) => {
|
|
161
|
+
const cfgPath = program.opts().config
|
|
162
|
+
|
|
154
163
|
const loadAndRunLinter = async () => {
|
|
155
164
|
// The existing logic for loading the config or detecting it is now inside this function
|
|
156
|
-
let config = await loadConfig()
|
|
165
|
+
let config = await loadConfig(cfgPath)
|
|
157
166
|
if (!config) {
|
|
158
167
|
console.log(chalk.blue('No config file found. Attempting to detect project structure...'))
|
|
159
168
|
const detected = await detectConfig()
|
|
@@ -175,7 +184,7 @@ program
|
|
|
175
184
|
if (options.watch) {
|
|
176
185
|
console.log('\nWatching for changes...')
|
|
177
186
|
// Re-load the config to get the correct input paths for the watcher
|
|
178
|
-
const config = await loadConfig()
|
|
187
|
+
const config = await loadConfig(cfgPath)
|
|
179
188
|
if (config?.extract?.input) {
|
|
180
189
|
const expandedLint = await expandGlobs(config.extract.input)
|
|
181
190
|
const configuredIgnore2 = toArray(config.extract.ignore)
|
|
@@ -203,7 +212,8 @@ program
|
|
|
203
212
|
.option('--compare-mtime', 'Compare modification times when syncing.')
|
|
204
213
|
.option('--dry-run', 'Run the command without making any changes.')
|
|
205
214
|
.action(async (options) => {
|
|
206
|
-
const
|
|
215
|
+
const cfgPath = program.opts().config
|
|
216
|
+
const config = await ensureConfig(cfgPath)
|
|
207
217
|
await runLocizeSync(config, options)
|
|
208
218
|
})
|
|
209
219
|
|
|
@@ -211,7 +221,8 @@ program
|
|
|
211
221
|
.command('locize-download')
|
|
212
222
|
.description('Download all translations from your locize project.')
|
|
213
223
|
.action(async (options) => {
|
|
214
|
-
const
|
|
224
|
+
const cfgPath = program.opts().config
|
|
225
|
+
const config = await ensureConfig(cfgPath)
|
|
215
226
|
await runLocizeDownload(config, options)
|
|
216
227
|
})
|
|
217
228
|
|
|
@@ -219,7 +230,8 @@ program
|
|
|
219
230
|
.command('locize-migrate')
|
|
220
231
|
.description('Migrate local translation files to a new locize project.')
|
|
221
232
|
.action(async (options) => {
|
|
222
|
-
const
|
|
233
|
+
const cfgPath = program.opts().config
|
|
234
|
+
const config = await ensureConfig(cfgPath)
|
|
223
235
|
await runLocizeMigrate(config, options)
|
|
224
236
|
})
|
|
225
237
|
|
package/src/config.ts
CHANGED
|
@@ -46,7 +46,18 @@ export function defineConfig (config: I18nextToolkitConfig): I18nextToolkitConfi
|
|
|
46
46
|
*
|
|
47
47
|
* @returns Promise that resolves to the full path of the found config file, or null if none found
|
|
48
48
|
*/
|
|
49
|
-
async function findConfigFile (): Promise<string | null> {
|
|
49
|
+
async function findConfigFile (configPath?: string): Promise<string | null> {
|
|
50
|
+
if (configPath) {
|
|
51
|
+
// Allow relative or absolute path provided by the user
|
|
52
|
+
const resolved = resolve(process.cwd(), configPath)
|
|
53
|
+
try {
|
|
54
|
+
await access(resolved)
|
|
55
|
+
return resolved
|
|
56
|
+
} catch {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
50
61
|
for (const file of CONFIG_FILES) {
|
|
51
62
|
const fullPath = resolve(process.cwd(), file)
|
|
52
63
|
try {
|
|
@@ -60,30 +71,18 @@ async function findConfigFile (): Promise<string | null> {
|
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
/**
|
|
63
|
-
* Loads and validates the i18next toolkit configuration from the project root.
|
|
64
|
-
*
|
|
65
|
-
* This function:
|
|
66
|
-
* 1. Searches for a config file using findConfigFile()
|
|
67
|
-
* 2. Dynamically imports the config file using ESM import()
|
|
68
|
-
* 3. Validates the configuration structure
|
|
69
|
-
* 4. Sets default values for sync options
|
|
70
|
-
* 5. Adds cache busting for watch mode
|
|
74
|
+
* Loads and validates the i18next toolkit configuration from the project root or a provided path.
|
|
71
75
|
*
|
|
72
|
-
* @
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* ```typescript
|
|
76
|
-
* const config = await loadConfig()
|
|
77
|
-
* if (!config) {
|
|
78
|
-
* console.error('Failed to load configuration')
|
|
79
|
-
* process.exit(1)
|
|
80
|
-
* }
|
|
81
|
-
* ```
|
|
76
|
+
* @param configPath - Optional explicit path to a config file (relative to cwd or absolute)
|
|
77
|
+
* @param logger - Optional logger instance
|
|
82
78
|
*/
|
|
83
|
-
export async function loadConfig (logger: Logger = new ConsoleLogger()): Promise<I18nextToolkitConfig | null> {
|
|
84
|
-
const
|
|
79
|
+
export async function loadConfig (configPath?: string, logger: Logger = new ConsoleLogger()): Promise<I18nextToolkitConfig | null> {
|
|
80
|
+
const configPathFound = await findConfigFile(configPath)
|
|
85
81
|
|
|
86
|
-
if (!
|
|
82
|
+
if (!configPathFound) {
|
|
83
|
+
if (configPath) {
|
|
84
|
+
logger.error(`Error: Config file not found at "${configPath}"`)
|
|
85
|
+
}
|
|
87
86
|
// QUIETLY RETURN NULL: The caller will handle the "not found" case.
|
|
88
87
|
return null
|
|
89
88
|
}
|
|
@@ -92,23 +91,23 @@ export async function loadConfig (logger: Logger = new ConsoleLogger()): Promise
|
|
|
92
91
|
let config: any
|
|
93
92
|
|
|
94
93
|
// Use jiti for TypeScript files, native import for JavaScript
|
|
95
|
-
if (
|
|
94
|
+
if (configPathFound.endsWith('.ts')) {
|
|
96
95
|
const aliases = await getTsConfigAliases()
|
|
97
96
|
const jiti = createJiti(process.cwd(), {
|
|
98
97
|
alias: aliases,
|
|
99
98
|
interopDefault: false,
|
|
100
99
|
})
|
|
101
100
|
|
|
102
|
-
const configModule = await jiti.import(
|
|
101
|
+
const configModule = await jiti.import(configPathFound, { default: true })
|
|
103
102
|
config = configModule
|
|
104
103
|
} else {
|
|
105
|
-
const configUrl = pathToFileURL(
|
|
104
|
+
const configUrl = pathToFileURL(configPathFound).href
|
|
106
105
|
const configModule = await import(`${configUrl}?t=${Date.now()}`)
|
|
107
106
|
config = configModule.default
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
if (!config) {
|
|
111
|
-
logger.error(`Error: No default export found in ${
|
|
110
|
+
logger.error(`Error: No default export found in ${configPathFound}`)
|
|
112
111
|
return null
|
|
113
112
|
}
|
|
114
113
|
|
|
@@ -119,7 +118,7 @@ export async function loadConfig (logger: Logger = new ConsoleLogger()): Promise
|
|
|
119
118
|
|
|
120
119
|
return config
|
|
121
120
|
} catch (error) {
|
|
122
|
-
logger.error(`Error loading configuration from ${
|
|
121
|
+
logger.error(`Error loading configuration from ${configPathFound}`)
|
|
123
122
|
logger.error(error)
|
|
124
123
|
return null
|
|
125
124
|
}
|
|
@@ -127,13 +126,10 @@ export async function loadConfig (logger: Logger = new ConsoleLogger()): Promise
|
|
|
127
126
|
|
|
128
127
|
/**
|
|
129
128
|
* Ensures a configuration exists, prompting the user to create one if necessary.
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* @returns A promise that resolves to a valid configuration object.
|
|
133
|
-
* @throws Exits the process if the user declines to create a config or if loading fails after creation.
|
|
129
|
+
* Accepts an optional configPath which will be used when loading the config.
|
|
134
130
|
*/
|
|
135
|
-
export async function ensureConfig (logger: Logger = new ConsoleLogger()): Promise<I18nextToolkitConfig> {
|
|
136
|
-
let config = await loadConfig()
|
|
131
|
+
export async function ensureConfig (configPath?: string, logger: Logger = new ConsoleLogger()): Promise<I18nextToolkitConfig> {
|
|
132
|
+
let config = await loadConfig(configPath, logger)
|
|
137
133
|
|
|
138
134
|
if (config) {
|
|
139
135
|
return config
|
|
@@ -148,9 +144,9 @@ export async function ensureConfig (logger: Logger = new ConsoleLogger()): Promi
|
|
|
148
144
|
}])
|
|
149
145
|
|
|
150
146
|
if (shouldInit) {
|
|
151
|
-
await runInit() // Run the interactive setup wizard
|
|
147
|
+
await runInit() // Run the interactive setup wizard (keeps existing behavior)
|
|
152
148
|
logger.info(chalk.green('Configuration created. Resuming command...'))
|
|
153
|
-
config = await loadConfig() // Try loading the newly created config
|
|
149
|
+
config = await loadConfig(configPath, logger) // Try loading the newly created config
|
|
154
150
|
|
|
155
151
|
if (config) {
|
|
156
152
|
return config
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { VariableDeclarator, CallExpression } from '@swc/core'
|
|
1
|
+
import type { VariableDeclarator, CallExpression, TemplateLiteral } from '@swc/core'
|
|
2
2
|
import type { ScopeInfo, UseTranslationHookConfig, I18nextToolkitConfig } from '../../types'
|
|
3
3
|
import { getObjectPropValue } from './ast-utils'
|
|
4
4
|
|
|
@@ -7,6 +7,9 @@ export class ScopeManager {
|
|
|
7
7
|
private config: Omit<I18nextToolkitConfig, 'plugins'>
|
|
8
8
|
private scope: Map<string, { defaultNs?: string; keyPrefix?: string }> = new Map()
|
|
9
9
|
|
|
10
|
+
// Track simple local constants with string literal values to resolve identifier args
|
|
11
|
+
private simpleConstants: Map<string, string> = new Map()
|
|
12
|
+
|
|
10
13
|
constructor (config: Omit<I18nextToolkitConfig, 'plugins'>) {
|
|
11
14
|
this.config = config
|
|
12
15
|
}
|
|
@@ -21,6 +24,7 @@ export class ScopeManager {
|
|
|
21
24
|
public reset (): void {
|
|
22
25
|
this.scopeStack = []
|
|
23
26
|
this.scope = new Map()
|
|
27
|
+
this.simpleConstants.clear()
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
/**
|
|
@@ -49,6 +53,10 @@ export class ScopeManager {
|
|
|
49
53
|
setVarInScope (name: string, info: ScopeInfo): void {
|
|
50
54
|
if (this.scopeStack.length > 0) {
|
|
51
55
|
this.scopeStack[this.scopeStack.length - 1].set(name, info)
|
|
56
|
+
} else {
|
|
57
|
+
// No active scope (top-level). Preserve in legacy scope map so lookups work
|
|
58
|
+
// for top-level variables (e.g., const { getFixedT } = useTranslate(...))
|
|
59
|
+
this.scope.set(name, info)
|
|
52
60
|
}
|
|
53
61
|
}
|
|
54
62
|
|
|
@@ -77,6 +85,33 @@ export class ScopeManager {
|
|
|
77
85
|
return undefined
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
private getUseTranslationConfig (name: string): UseTranslationHookConfig | undefined {
|
|
89
|
+
const useTranslationNames = this.config.extract.useTranslationNames || ['useTranslation']
|
|
90
|
+
|
|
91
|
+
for (const item of useTranslationNames) {
|
|
92
|
+
if (typeof item === 'string' && item === name) {
|
|
93
|
+
// Default behavior for simple string entries
|
|
94
|
+
return { name, nsArg: 0, keyPrefixArg: 1 }
|
|
95
|
+
}
|
|
96
|
+
if (typeof item === 'object' && item.name === name) {
|
|
97
|
+
// Custom configuration with specified or default argument positions
|
|
98
|
+
return {
|
|
99
|
+
name: item.name,
|
|
100
|
+
nsArg: item.nsArg ?? 0,
|
|
101
|
+
keyPrefixArg: item.keyPrefixArg ?? 1,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return undefined
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resolve simple identifier declared in-file to its string literal value, if known.
|
|
110
|
+
*/
|
|
111
|
+
private resolveSimpleStringIdentifier (name: string): string | undefined {
|
|
112
|
+
return this.simpleConstants.get(name)
|
|
113
|
+
}
|
|
114
|
+
|
|
80
115
|
/**
|
|
81
116
|
* Handles variable declarations that might define translation functions.
|
|
82
117
|
*
|
|
@@ -92,6 +127,12 @@ export class ScopeManager {
|
|
|
92
127
|
const init = node.init
|
|
93
128
|
if (!init) return
|
|
94
129
|
|
|
130
|
+
// Record simple const/let string initializers for later resolution
|
|
131
|
+
if (node.id.type === 'Identifier' && init.type === 'StringLiteral') {
|
|
132
|
+
this.simpleConstants.set(node.id.value, init.value)
|
|
133
|
+
// continue processing; still may be a useTranslation/getFixedT call below
|
|
134
|
+
}
|
|
135
|
+
|
|
95
136
|
// Determine the actual call expression, looking inside AwaitExpressions.
|
|
96
137
|
const callExpr =
|
|
97
138
|
init.type === 'AwaitExpression' && init.argument.type === 'CallExpression'
|
|
@@ -116,6 +157,18 @@ export class ScopeManager {
|
|
|
116
157
|
}
|
|
117
158
|
}
|
|
118
159
|
|
|
160
|
+
// Handle: const t = getFixedT(...) where getFixedT is a previously declared variable
|
|
161
|
+
// (e.g., `const { getFixedT } = useTranslate('helloservice')`)
|
|
162
|
+
if (callee.type === 'Identifier') {
|
|
163
|
+
const sourceScope = this.getVarFromScope(callee.value)
|
|
164
|
+
if (sourceScope) {
|
|
165
|
+
// Propagate the source scope (keyPrefix/defaultNs) and augment it with
|
|
166
|
+
// arguments passed to this call (e.g., namespace argument).
|
|
167
|
+
this.handleGetFixedTFromVariableDeclarator(node, callExpr, callee.value)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
119
172
|
// Handle: const t = i18next.getFixedT(...)
|
|
120
173
|
if (
|
|
121
174
|
callee.type === 'MemberExpression' &&
|
|
@@ -135,40 +188,6 @@ export class ScopeManager {
|
|
|
135
188
|
* @param callExpr - The CallExpression node representing the useTranslation invocation
|
|
136
189
|
* @param hookConfig - Configuration describing argument positions for namespace and keyPrefix
|
|
137
190
|
*/
|
|
138
|
-
private getUseTranslationConfig (name: string): UseTranslationHookConfig | undefined {
|
|
139
|
-
const useTranslationNames = this.config.extract.useTranslationNames || ['useTranslation']
|
|
140
|
-
|
|
141
|
-
for (const item of useTranslationNames) {
|
|
142
|
-
if (typeof item === 'string' && item === name) {
|
|
143
|
-
// Default behavior for simple string entries
|
|
144
|
-
return { name, nsArg: 0, keyPrefixArg: 1 }
|
|
145
|
-
}
|
|
146
|
-
if (typeof item === 'object' && item.name === name) {
|
|
147
|
-
// Custom configuration with specified or default argument positions
|
|
148
|
-
return {
|
|
149
|
-
name: item.name,
|
|
150
|
-
nsArg: item.nsArg ?? 0,
|
|
151
|
-
keyPrefixArg: item.keyPrefixArg ?? 1,
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return undefined
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Processes useTranslation hook declarations to extract scope information.
|
|
160
|
-
*
|
|
161
|
-
* Handles various destructuring patterns:
|
|
162
|
-
* - `const [t] = useTranslation('ns')` - Array destructuring
|
|
163
|
-
* - `const { t } = useTranslation('ns')` - Object destructuring
|
|
164
|
-
* - `const { t: myT } = useTranslation('ns')` - Aliased destructuring
|
|
165
|
-
*
|
|
166
|
-
* Extracts namespace from the first argument and keyPrefix from options.
|
|
167
|
-
*
|
|
168
|
-
* @param node - Variable declarator with useTranslation call
|
|
169
|
-
* @param callExpr - The CallExpression node representing the useTranslation invocation
|
|
170
|
-
* @param hookConfig - Configuration describing argument positions for namespace and keyPrefix
|
|
171
|
-
*/
|
|
172
191
|
private handleUseTranslationForComments (node: VariableDeclarator, callExpr: CallExpression, hookConfig: UseTranslationHookConfig): void {
|
|
173
192
|
let variableName: string | undefined
|
|
174
193
|
|
|
@@ -188,13 +207,12 @@ export class ScopeManager {
|
|
|
188
207
|
// Handle object destructuring: const { t } or { t: t1 } = useTranslation()
|
|
189
208
|
if (node.id.type === 'ObjectPattern') {
|
|
190
209
|
for (const prop of node.id.properties) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
variableName =
|
|
210
|
+
// Support both 't' and 'getFixedT' (and preserve existing behavior for 't').
|
|
211
|
+
if (prop.type === 'AssignmentPatternProperty' && prop.key.type === 'Identifier' && (prop.key.value === 't' || prop.key.value === 'getFixedT')) {
|
|
212
|
+
variableName = prop.key.value
|
|
194
213
|
break
|
|
195
214
|
}
|
|
196
|
-
if (prop.type === 'KeyValuePatternProperty' && prop.key.type === 'Identifier' && prop.key.value === 't' && prop.value.type === 'Identifier') {
|
|
197
|
-
// This handles { t: myT }
|
|
215
|
+
if (prop.type === 'KeyValuePatternProperty' && prop.key.type === 'Identifier' && (prop.key.value === 't' || prop.key.value === 'getFixedT') && prop.value.type === 'Identifier') {
|
|
198
216
|
variableName = prop.value.value
|
|
199
217
|
break
|
|
200
218
|
}
|
|
@@ -205,20 +223,20 @@ export class ScopeManager {
|
|
|
205
223
|
if (!variableName) return
|
|
206
224
|
|
|
207
225
|
// Extract namespace from useTranslation arguments
|
|
208
|
-
const nsArg = callExpr.arguments?.[hookConfig.nsArg]?.expression
|
|
209
|
-
const optionsArg = callExpr.arguments?.[hookConfig.keyPrefixArg]?.expression
|
|
226
|
+
const nsArg = hookConfig.nsArg === -1 ? undefined : callExpr.arguments?.[hookConfig.nsArg ?? 0]?.expression
|
|
227
|
+
const optionsArg = callExpr.arguments?.[hookConfig.keyPrefixArg ?? 1]?.expression
|
|
210
228
|
|
|
211
229
|
let defaultNs: string | undefined
|
|
212
230
|
let keyPrefix: string | undefined
|
|
213
231
|
|
|
214
|
-
// Parse namespace argument
|
|
232
|
+
// Parse namespace argument (only when nsArg wasn't explicitly disabled)
|
|
215
233
|
if (nsArg?.type === 'StringLiteral') {
|
|
216
234
|
defaultNs = nsArg.value
|
|
217
235
|
} else if (nsArg?.type === 'ArrayExpression' && nsArg.elements[0]?.expression.type === 'StringLiteral') {
|
|
218
236
|
defaultNs = nsArg.elements[0].expression.value
|
|
219
237
|
}
|
|
220
238
|
|
|
221
|
-
// Parse keyPrefix
|
|
239
|
+
// Parse keyPrefix: accept either { keyPrefix: 'x' } or a plain string arg or simple identifier/template literal
|
|
222
240
|
if (optionsArg?.type === 'ObjectExpression') {
|
|
223
241
|
const keyPrefixProp = optionsArg.properties.find(
|
|
224
242
|
prop => prop.type === 'KeyValueProperty' &&
|
|
@@ -228,6 +246,16 @@ export class ScopeManager {
|
|
|
228
246
|
if (keyPrefixProp?.type === 'KeyValueProperty' && keyPrefixProp.value.type === 'StringLiteral') {
|
|
229
247
|
keyPrefix = keyPrefixProp.value.value
|
|
230
248
|
}
|
|
249
|
+
} else if (optionsArg?.type === 'StringLiteral') {
|
|
250
|
+
// allow keyPrefix as direct string argument
|
|
251
|
+
keyPrefix = optionsArg.value
|
|
252
|
+
} else if (optionsArg?.type === 'Identifier') {
|
|
253
|
+
keyPrefix = this.resolveSimpleStringIdentifier(optionsArg.value)
|
|
254
|
+
} else if (optionsArg?.type === 'TemplateLiteral') {
|
|
255
|
+
const tpl = optionsArg as TemplateLiteral
|
|
256
|
+
if ((tpl.expressions || []).length === 0) {
|
|
257
|
+
keyPrefix = tpl.quasis?.[0]?.cooked ?? undefined
|
|
258
|
+
}
|
|
231
259
|
}
|
|
232
260
|
|
|
233
261
|
// Store in the legacy scope map for comment parsing
|
|
@@ -269,13 +297,12 @@ export class ScopeManager {
|
|
|
269
297
|
// Handle object destructuring: const { t } or { t: t1 } = useTranslation()
|
|
270
298
|
if (node.id.type === 'ObjectPattern') {
|
|
271
299
|
for (const prop of node.id.properties) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
variableName =
|
|
300
|
+
// Also consider getFixedT so scope info is attached to that identifier
|
|
301
|
+
if (prop.type === 'AssignmentPatternProperty' && prop.key.type === 'Identifier' && (prop.key.value === 't' || prop.key.value === 'getFixedT')) {
|
|
302
|
+
variableName = prop.key.value
|
|
275
303
|
break
|
|
276
304
|
}
|
|
277
|
-
if (prop.type === 'KeyValuePatternProperty' && prop.key.type === 'Identifier' && prop.key.value === 't' && prop.value.type === 'Identifier') {
|
|
278
|
-
// This handles { t: myT }
|
|
305
|
+
if (prop.type === 'KeyValuePatternProperty' && prop.key.type === 'Identifier' && (prop.key.value === 't' || prop.key.value === 'getFixedT') && prop.value.type === 'Identifier') {
|
|
279
306
|
variableName = prop.value.value
|
|
280
307
|
break
|
|
281
308
|
}
|
|
@@ -286,7 +313,7 @@ export class ScopeManager {
|
|
|
286
313
|
if (!variableName) return
|
|
287
314
|
|
|
288
315
|
// Use the configured argument indices from hookConfig
|
|
289
|
-
const nsArg = callExpr.arguments?.[hookConfig.nsArg]?.expression
|
|
316
|
+
const nsArg = hookConfig.nsArg === -1 ? undefined : callExpr.arguments?.[hookConfig.nsArg ?? 0]?.expression
|
|
290
317
|
|
|
291
318
|
let defaultNs: string | undefined
|
|
292
319
|
if (nsArg?.type === 'StringLiteral') {
|
|
@@ -295,11 +322,21 @@ export class ScopeManager {
|
|
|
295
322
|
defaultNs = nsArg.elements[0].expression.value
|
|
296
323
|
}
|
|
297
324
|
|
|
298
|
-
const optionsArg = callExpr.arguments?.[hookConfig.keyPrefixArg]?.expression
|
|
325
|
+
const optionsArg = callExpr.arguments?.[hookConfig.keyPrefixArg ?? 1]?.expression
|
|
299
326
|
let keyPrefix: string | undefined
|
|
300
327
|
if (optionsArg?.type === 'ObjectExpression') {
|
|
301
328
|
const kp = getObjectPropValue(optionsArg, 'keyPrefix')
|
|
302
329
|
keyPrefix = typeof kp === 'string' ? kp : undefined
|
|
330
|
+
} else if (optionsArg?.type === 'StringLiteral') {
|
|
331
|
+
// allow keyPrefix as a direct string argument
|
|
332
|
+
keyPrefix = optionsArg.value
|
|
333
|
+
} else if (optionsArg?.type === 'Identifier') {
|
|
334
|
+
keyPrefix = this.resolveSimpleStringIdentifier(optionsArg.value)
|
|
335
|
+
} else if (optionsArg?.type === 'TemplateLiteral') {
|
|
336
|
+
const tpl = optionsArg as TemplateLiteral
|
|
337
|
+
if ((tpl.expressions || []).length === 0) {
|
|
338
|
+
keyPrefix = tpl.quasis?.[0]?.cooked ?? undefined
|
|
339
|
+
}
|
|
303
340
|
}
|
|
304
341
|
|
|
305
342
|
// Store the scope info for the declared variable
|
|
@@ -336,4 +373,38 @@ export class ScopeManager {
|
|
|
336
373
|
this.setVarInScope(variableName, { defaultNs, keyPrefix })
|
|
337
374
|
}
|
|
338
375
|
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Handles cases where a getFixedT-like function is a variable (from a custom hook)
|
|
379
|
+
* and is invoked to produce a bound `t` function, e.g.:
|
|
380
|
+
* const { getFixedT } = useTranslate('prefix')
|
|
381
|
+
* const t = getFixedT('en', 'ns')
|
|
382
|
+
*
|
|
383
|
+
* We combine the original source variable's scope (keyPrefix/defaultNs) with
|
|
384
|
+
* any namespace/keyPrefix arguments provided to this call and attach the
|
|
385
|
+
* resulting scope to the newly declared variable.
|
|
386
|
+
*/
|
|
387
|
+
private handleGetFixedTFromVariableDeclarator (node: VariableDeclarator, callExpr: CallExpression, sourceVarName: string): void {
|
|
388
|
+
if (node.id.type !== 'Identifier') return
|
|
389
|
+
|
|
390
|
+
const targetVarName = node.id.value
|
|
391
|
+
const sourceScope = this.getVarFromScope(sourceVarName)
|
|
392
|
+
if (!sourceScope) return
|
|
393
|
+
|
|
394
|
+
const args = callExpr.arguments
|
|
395
|
+
// getFixedT(lng, ns, keyPrefix)
|
|
396
|
+
const nsArg = args[1]?.expression
|
|
397
|
+
const keyPrefixArg = args[2]?.expression
|
|
398
|
+
|
|
399
|
+
const nsFromCall = (nsArg?.type === 'StringLiteral') ? nsArg.value : undefined
|
|
400
|
+
const keyPrefixFromCall = (keyPrefixArg?.type === 'StringLiteral') ? keyPrefixArg.value : undefined
|
|
401
|
+
|
|
402
|
+
// Merge: call args take precedence over source scope values
|
|
403
|
+
const finalNs = nsFromCall ?? sourceScope.defaultNs
|
|
404
|
+
const finalKeyPrefix = keyPrefixFromCall ?? sourceScope.keyPrefix
|
|
405
|
+
|
|
406
|
+
if (finalNs || finalKeyPrefix) {
|
|
407
|
+
this.setVarInScope(targetVarName, { defaultNs: finalNs, keyPrefix: finalKeyPrefix })
|
|
408
|
+
}
|
|
409
|
+
}
|
|
339
410
|
}
|
package/types/config.d.ts
CHANGED
|
@@ -18,35 +18,17 @@ import type { I18nextToolkitConfig, Logger } from './types';
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function defineConfig(config: I18nextToolkitConfig): I18nextToolkitConfig;
|
|
20
20
|
/**
|
|
21
|
-
* Loads and validates the i18next toolkit configuration from the project root.
|
|
21
|
+
* Loads and validates the i18next toolkit configuration from the project root or a provided path.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* 2. Dynamically imports the config file using ESM import()
|
|
26
|
-
* 3. Validates the configuration structure
|
|
27
|
-
* 4. Sets default values for sync options
|
|
28
|
-
* 5. Adds cache busting for watch mode
|
|
29
|
-
*
|
|
30
|
-
* @returns Promise that resolves to the loaded configuration object, or null if loading failed
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* ```typescript
|
|
34
|
-
* const config = await loadConfig()
|
|
35
|
-
* if (!config) {
|
|
36
|
-
* console.error('Failed to load configuration')
|
|
37
|
-
* process.exit(1)
|
|
38
|
-
* }
|
|
39
|
-
* ```
|
|
23
|
+
* @param configPath - Optional explicit path to a config file (relative to cwd or absolute)
|
|
24
|
+
* @param logger - Optional logger instance
|
|
40
25
|
*/
|
|
41
|
-
export declare function loadConfig(logger?: Logger): Promise<I18nextToolkitConfig | null>;
|
|
26
|
+
export declare function loadConfig(configPath?: string, logger?: Logger): Promise<I18nextToolkitConfig | null>;
|
|
42
27
|
/**
|
|
43
28
|
* Ensures a configuration exists, prompting the user to create one if necessary.
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* @returns A promise that resolves to a valid configuration object.
|
|
47
|
-
* @throws Exits the process if the user declines to create a config or if loading fails after creation.
|
|
29
|
+
* Accepts an optional configPath which will be used when loading the config.
|
|
48
30
|
*/
|
|
49
|
-
export declare function ensureConfig(logger?: Logger): Promise<I18nextToolkitConfig>;
|
|
31
|
+
export declare function ensureConfig(configPath?: string, logger?: Logger): Promise<I18nextToolkitConfig>;
|
|
50
32
|
/**
|
|
51
33
|
* Parses the project's tsconfig.json to extract path aliases for jiti.
|
|
52
34
|
* @returns A record of aliases for jiti's configuration.
|
package/types/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAc3D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAE,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,CAEhF;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAc3D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAE,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,CAEhF;AAgCD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAE,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,GAAE,MAA4B,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA8CjI;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAE,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,GAAE,MAA4B,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA8B5H;AAyBD;;;GAGG;AACH,wBAAsB,kBAAkB,IAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAyB3E"}
|
|
@@ -4,6 +4,7 @@ export declare class ScopeManager {
|
|
|
4
4
|
private scopeStack;
|
|
5
5
|
private config;
|
|
6
6
|
private scope;
|
|
7
|
+
private simpleConstants;
|
|
7
8
|
constructor(config: Omit<I18nextToolkitConfig, 'plugins'>);
|
|
8
9
|
/**
|
|
9
10
|
* Reset per-file scope state.
|
|
@@ -39,6 +40,11 @@ export declare class ScopeManager {
|
|
|
39
40
|
* @returns Scope information if found, undefined otherwise
|
|
40
41
|
*/
|
|
41
42
|
getVarFromScope(name: string): ScopeInfo | undefined;
|
|
43
|
+
private getUseTranslationConfig;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve simple identifier declared in-file to its string literal value, if known.
|
|
46
|
+
*/
|
|
47
|
+
private resolveSimpleStringIdentifier;
|
|
42
48
|
/**
|
|
43
49
|
* Handles variable declarations that might define translation functions.
|
|
44
50
|
*
|
|
@@ -60,21 +66,6 @@ export declare class ScopeManager {
|
|
|
60
66
|
* @param callExpr - The CallExpression node representing the useTranslation invocation
|
|
61
67
|
* @param hookConfig - Configuration describing argument positions for namespace and keyPrefix
|
|
62
68
|
*/
|
|
63
|
-
private getUseTranslationConfig;
|
|
64
|
-
/**
|
|
65
|
-
* Processes useTranslation hook declarations to extract scope information.
|
|
66
|
-
*
|
|
67
|
-
* Handles various destructuring patterns:
|
|
68
|
-
* - `const [t] = useTranslation('ns')` - Array destructuring
|
|
69
|
-
* - `const { t } = useTranslation('ns')` - Object destructuring
|
|
70
|
-
* - `const { t: myT } = useTranslation('ns')` - Aliased destructuring
|
|
71
|
-
*
|
|
72
|
-
* Extracts namespace from the first argument and keyPrefix from options.
|
|
73
|
-
*
|
|
74
|
-
* @param node - Variable declarator with useTranslation call
|
|
75
|
-
* @param callExpr - The CallExpression node representing the useTranslation invocation
|
|
76
|
-
* @param hookConfig - Configuration describing argument positions for namespace and keyPrefix
|
|
77
|
-
*/
|
|
78
69
|
private handleUseTranslationForComments;
|
|
79
70
|
/**
|
|
80
71
|
* Processes useTranslation hook declarations to extract scope information.
|
|
@@ -103,5 +94,16 @@ export declare class ScopeManager {
|
|
|
103
94
|
* @param callExpr - The CallExpression node representing the getFixedT invocation
|
|
104
95
|
*/
|
|
105
96
|
private handleGetFixedTDeclarator;
|
|
97
|
+
/**
|
|
98
|
+
* Handles cases where a getFixedT-like function is a variable (from a custom hook)
|
|
99
|
+
* and is invoked to produce a bound `t` function, e.g.:
|
|
100
|
+
* const { getFixedT } = useTranslate('prefix')
|
|
101
|
+
* const t = getFixedT('en', 'ns')
|
|
102
|
+
*
|
|
103
|
+
* We combine the original source variable's scope (keyPrefix/defaultNs) with
|
|
104
|
+
* any namespace/keyPrefix arguments provided to this call and attach the
|
|
105
|
+
* resulting scope to the newly declared variable.
|
|
106
|
+
*/
|
|
107
|
+
private handleGetFixedTFromVariableDeclarator;
|
|
106
108
|
}
|
|
107
109
|
//# sourceMappingURL=scope-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/scope-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"scope-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/scope-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAmC,MAAM,WAAW,CAAA;AACpF,OAAO,KAAK,EAAE,SAAS,EAA4B,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAG5F,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,KAAK,CAAqE;IAGlF,OAAO,CAAC,eAAe,CAAiC;gBAE3C,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC;IAI1D;;;;;;OAMG;IACI,KAAK,IAAK,IAAI;IAMrB;;;OAGG;IACH,UAAU,IAAK,IAAI;IAInB;;;OAGG;IACH,SAAS,IAAK,IAAI;IAIlB;;;;;;OAMG;IACH,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAUnD;;;;;;OAMG;IACH,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkBrD,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAIrC;;;;;;;;;;OAUG;IACH,wBAAwB,CAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAwDzD;;;;;;;;OAQG;IACH,OAAO,CAAC,+BAA+B;IA4EvC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,8BAA8B;IAiEtC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;OASG;IACH,OAAO,CAAC,qCAAqC;CAuB9C"}
|