i18next-cli 1.13.1 → 1.15.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 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.15.0](https://github.com/i18next/i18next-cli/compare/v1.14.0...v1.15.0) - 2025-10-27
9
+
10
+ - 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)
11
+
12
+ ## [1.14.0](https://github.com/i18next/i18next-cli/compare/v1.13.1...v1.14.0) - 2025-10-27
13
+
14
+ - Parse simple template strings when extracting defaultValue-s [#76](https://github.com/i18next/i18next-cli/pull/76)
15
+
8
16
  ## [1.13.1](https://github.com/i18next/i18next-cli/compare/v1.13.0...v1.13.1) - 2025-10-27
9
17
 
10
18
  - fix: Self Closing Tags are not ignored by linter even when put in ignoredTags [#75](https://github.com/i18next/i18next-cli/issues/75)
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"),n=require("glob"),o=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"),d=require("./init.js"),g=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.13.1"),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 n=await a.ensureConfig(),i=async()=>{const t=await c.runExtractor(n,{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 i(),e.watch){console.log("\nWatching for changes...");const e=await w(n.extract.input),a=y(n.extract.ignore),r=h(n.extract.output),c=[...a,...r].filter(Boolean),s=e.filter(e=>!c.some(t=>o.minimatch(e,t,{dot:!0})));t.watch(s,{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),i()})}}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)=>{let n=await a.loadConfig();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 n=await a.ensureConfig(),i=()=>s.runTypesGenerator(n);if(await i(),e.watch){console.log("\nWatching for changes...");const e=await w(n.types?.input||[]),a=[...y(n.extract?.ignore)].filter(Boolean),r=e.filter(e=>!a.some(t=>o.minimatch(e,t,{dot:!0})));t.watch(r,{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),i()})}}),m.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await a.ensureConfig();await l.runSyncer(e)}),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(d.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 n=async()=>{let e=await a.loadConfig();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 g.runLinter(e)};if(await n(),e.watch){console.log("\nWatching for changes...");const e=await a.loadConfig();if(e?.extract?.input){const i=await w(e.extract.input),a=[...y(e.extract.ignore),...h(e.extract.output)].filter(Boolean),r=i.filter(e=>!a.some(t=>o.minimatch(e,t,{dot:!0})));t.watch(r,{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),n()})}}}),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=await a.ensureConfig();await f.runLocizeSync(t,e)}),m.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await a.ensureConfig();await f.runLocizeDownload(t,e)}),m.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await a.ensureConfig();await f.runLocizeMigrate(t,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),o=await Promise.all(t.map(e=>n.glob(e||"",{nodir:!0})));return Array.from(new Set(o.flat()))};
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.15.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()))};
@@ -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 i=await async function(){for(const r of u){const n=e.resolve(process.cwd(),r);try{return await t.access(n),n}catch{}}return null}();if(!i)return null;try{let e;if(i.endsWith(".ts")){const r=await f(),t=n.createJiti(process.cwd(),{alias:r,interopDefault:!1}),o=await t.import(i,{default:!0});e=o}else{const t=r.pathToFileURL(i).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):(o.error(`Error: No default export found in ${i}`),null)}catch(e){return o.error(`Error loading configuration from ${i}`),o.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=new c.ConsoleLogger){let r=await l();if(r)return r;const{shouldInit:t}=await i.prompt([{type:"confirm",name:"shouldInit",message:a.yellow("Configuration file not found. Would you like to create one now?"),default:!0}]);if(t){if(await s.runInit(),e.info(a.green("Configuration created. Resuming command...")),r=await l(),r)return r;e.error(a.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.getTsConfigAliases=f,exports.loadConfig=l;
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";function e(e,t){return e.properties.filter(e=>"KeyValueProperty"===e.type).find(e=>"Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t)}exports.getObjectPropValue=function(t,r){const i=e(t,r);if("KeyValueProperty"===i?.type){const e=i.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}},exports.getObjectProperty=e;
1
+ "use strict";function e(e,t){return e.properties.filter(e=>"KeyValueProperty"===e.type).find(e=>"Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t)}function t(e){return 1===e.quasis.length&&0===e.expressions.length&&null!=e.quasis[0].cooked}exports.getObjectPropValue=function(r,i){const l=e(r,i);if("KeyValueProperty"===l?.type){const e=l.value;return"StringLiteral"===e.type?e.value:"TemplateLiteral"===e.type&&t(e)?e.quasis[0].cooked:"BooleanLiteral"===e.type||"NumericLiteral"===e.type?e.value:""}},exports.getObjectProperty=e,exports.isSimpleTemplateLiteral=t;
@@ -1 +1 @@
1
- "use strict";var e=require("./ast-utils.js");exports.CallExpressionHandler=class{pluginContext;config;logger;expressionResolver;objectKeys=new Set;constructor(e,t,r,n){this.config=e,this.pluginContext=t,this.logger=r,this.expressionResolver=n}handleCallExpression(t,r){const n=this.getFunctionName(t.callee);if(!n)return;const s=r(n),i=this.config.extract.functions||["t","*.t"];let o=void 0!==s;if(!o)for(const e of i)if(e.startsWith("*.")){if(n.endsWith(e.substring(1))){o=!0;break}}else if(e===n){o=!0;break}if(!o||0===t.arguments.length)return;const{keysToProcess:l,isSelectorAPI:a}=this.handleCallExpressionArgument(t,0);if(0===l.length)return;let u=!1;const p=this.config.extract.pluralSeparator??"_";for(let e=0;e<l.length;e++)l[e].endsWith(`${p}ordinal`)&&(u=!0,l[e]=l[e].slice(0,-8));let f,c;if(t.arguments.length>1){const e=t.arguments[1].expression;"ObjectExpression"===e.type?c=e:"StringLiteral"===e.type&&(f=e.value)}if(t.arguments.length>2){const e=t.arguments[2].expression;"ObjectExpression"===e.type&&(c=e)}const y=c?e.getObjectPropValue(c,"defaultValue"):void 0,g="string"==typeof y?y:f,h=e=>{if(!e||!Array.isArray(e.properties))return!1;for(const t of e.properties)if(t&&"KeyValueProperty"===t.type&&t.key){const e="Identifier"===t.key.type&&t.key.value||"StringLiteral"===t.key.type&&t.key.value;if("string"==typeof e&&e.startsWith("defaultValue"))return!0}return!1},d="string"==typeof g||h(c),x=h(c),k=Boolean(x||"string"==typeof g&&!("string"==typeof(v=g)&&/{{\s*count\s*}}/.test(v)));var v;for(let t=0;t<l.length;t++){let r,n=l[t];if(c){const t=e.getObjectPropValue(c,"ns");"string"==typeof t&&(r=t)}const i=this.config.extract.nsSeparator??":";if(!r&&i&&n.includes(i)){const e=n.split(i);if(r=e.shift(),n=e.join(i),!n||""===n.trim()){this.logger.warn(`Skipping key that became empty after namespace removal: '${r}${i}'`);continue}}!r&&s?.defaultNs&&(r=s.defaultNs),r||(r=this.config.extract.defaultNS);let o=n;if(s?.keyPrefix){const e=this.config.extract.keySeparator??".";if(o=!1!==e?s.keyPrefix.endsWith(e)?`${s.keyPrefix}${n}`:`${s.keyPrefix}${e}${n}`:`${s.keyPrefix}${n}`,!1!==e){if(o.split(e).some(e=>""===e.trim())){this.logger.warn(`Skipping key with empty segments: '${o}' (keyPrefix: '${s.keyPrefix}', key: '${n}')`);continue}}}const p=t===l.length-1&&g||n;if(c){const t=e.getObjectProperty(c,"context"),n=[];if("StringLiteral"===t?.value?.type||"NumericLiteral"===t?.value.type||"BooleanLiteral"===t?.value.type){const e=`${t.value.value}`,s=this.config.extract.contextSeparator??"_";""!==e&&n.push({key:`${o}${s}${e}`,ns:r,defaultValue:p,explicitDefault:d})}else if(t?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(t.value),s=this.config.extract.contextSeparator??"_";e.length>0&&(e.forEach(e=>{n.push({key:`${o}${s}${e}`,ns:r,defaultValue:p,explicitDefault:d})}),n.push({key:o,ns:r,defaultValue:p,explicitDefault:d}))}const s=e=>{if(e){if("KeyValueProperty"===e.type&&e.key){if("Identifier"===e.key.type)return e.key.value;if("StringLiteral"===e.key.type)return e.key.value}return"KeyValueProperty"===e.type&&e.value&&"Identifier"===e.value.type?e.key&&"Identifier"===e.key.type?e.key.value:void 0:"ShorthandProperty"!==e.type&&"Identifier"!==e.type||!e.value?e.key&&"string"==typeof e.key?e.key:void 0:e.value}},i=(()=>{if(!c||!Array.isArray(c.properties))return!1;for(const e of c.properties){if("count"===s(e))return!0}return!1})(),l=(()=>{if(!c||!Array.isArray(c.properties))return!1;for(const e of c.properties){if("ordinal"===s(e))return!("KeyValueProperty"!==e.type||!e.value||"BooleanLiteral"!==e.value.type)&&Boolean(e.value.value)}return!1})();if(i||u){this.config.extract.disablePlurals?n.length>0?n.forEach(this.pluginContext.addKey):this.pluginContext.addKey({key:o,ns:r,defaultValue:p,explicitDefault:d}):this.handlePluralKeys(o,r,c,l||u,g,k);continue}if(n.length>0){n.forEach(this.pluginContext.addKey);continue}!0===e.getObjectPropValue(c,"returnObjects")&&this.objectKeys.add(o)}a&&this.objectKeys.add(o),this.pluginContext.addKey({key:o,ns:r,defaultValue:p,explicitDefault:d})}}handleCallExpressionArgument(e,t){const r=e.arguments[t].expression,n=[];let s=!1;if("ArrowFunctionExpression"===r.type){const e=this.extractKeyFromSelector(r);e&&(n.push(e),s=!0)}else if("ArrayExpression"===r.type)for(const e of r.elements)e?.expression&&n.push(...this.expressionResolver.resolvePossibleKeyStringValues(e.expression));else n.push(...this.expressionResolver.resolvePossibleKeyStringValues(r));return{keysToProcess:n.filter(e=>!!e),isSelectorAPI:s}}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 r=t;const n=[];for(;"MemberExpression"===r.type;){const e=r.property;if("Identifier"===e.type)n.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;n.unshift(e.expression.value)}r=r.object}if(n.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return n.join(t)}return null}handlePluralKeys(t,r,n,s,i,o){try{const l=s?"ordinal":"cardinal",a=new Set;for(const e of this.config.locales)try{const t=new Intl.PluralRules(e,{type:l});t.resolvedOptions().pluralCategories.forEach(e=>a.add(e))}catch(e){const t=new Intl.PluralRules("en",{type:l});t.resolvedOptions().pluralCategories.forEach(e=>a.add(e))}const u=Array.from(a).sort(),p=this.config.extract.pluralSeparator??"_",f=e.getObjectPropValue(n,"defaultValue"),c=e.getObjectPropValue(n,`defaultValue${p}other`),y=e.getObjectPropValue(n,`defaultValue${p}ordinal${p}other`),g=e.getObjectProperty(n,"context"),h=[];if(g?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(g.value);if(e.length>0)if("StringLiteral"===g.value.type)for(const r of e)r.length>0&&h.push({key:t,context:r});else{for(const r of e)r.length>0&&h.push({key:t,context:r});!1!==this.config.extract?.generateBasePluralForms&&h.push({key:t})}else h.push({key:t})}else h.push({key:t});for(const{key:t,context:l}of h)for(const a of u){const u=s?`defaultValue${p}ordinal${p}${a}`:`defaultValue${p}${a}`,g=e.getObjectPropValue(n,u);let h,d;if(h="string"==typeof g?g:"one"===a&&"string"==typeof f?f:"one"===a&&"string"==typeof i?i:s&&"string"==typeof y?y:s||"string"!=typeof c?"string"==typeof f?f:"string"==typeof i?i:t:c,l){const e=this.config.extract.contextSeparator??"_";d=s?`${t}${e}${l}${p}ordinal${p}${a}`:`${t}${e}${l}${p}${a}`}else d=s?`${t}${p}ordinal${p}${a}`:`${t}${p}${a}`;this.pluginContext.addKey({key:d,ns:r,defaultValue:h,hasCount:!0,isOrdinal:s,explicitDefault:Boolean(o||"string"==typeof g||"string"==typeof c)})}}catch(s){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const o=i||e.getObjectPropValue(n,"defaultValue");this.pluginContext.addKey({key:t,ns:r,defaultValue:"string"==typeof o?o:t})}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let r=e;for(;"MemberExpression"===r.type;){if("Identifier"!==r.property.type)return null;t.unshift(r.property.value),r=r.object}if("ThisExpression"===r.type)t.unshift("this");else{if("Identifier"!==r.type)return null;t.unshift(r.value)}return t.join(".")}return null}};
1
+ "use strict";var e=require("./ast-utils.js");exports.CallExpressionHandler=class{pluginContext;config;logger;expressionResolver;objectKeys=new Set;constructor(e,t,r,n){this.config=e,this.pluginContext=t,this.logger=r,this.expressionResolver=n}handleCallExpression(t,r){const n=this.getFunctionName(t.callee);if(!n)return;const i=r(n),s=this.config.extract.functions||["t","*.t"];let o=void 0!==i;if(!o)for(const e of s)if(e.startsWith("*.")){if(n.endsWith(e.substring(1))){o=!0;break}}else if(e===n){o=!0;break}if(!o||0===t.arguments.length)return;const{keysToProcess:l,isSelectorAPI:a}=this.handleCallExpressionArgument(t,0);if(0===l.length)return;let u=!1;const p=this.config.extract.pluralSeparator??"_";for(let e=0;e<l.length;e++)l[e].endsWith(`${p}ordinal`)&&(u=!0,l[e]=l[e].slice(0,-8));let f,c;if(t.arguments.length>1){const r=t.arguments[1].expression;"ObjectExpression"===r.type?c=r:"StringLiteral"===r.type?f=r.value:"TemplateLiteral"===r.type&&e.isSimpleTemplateLiteral(r)&&(f=r.quasis[0].cooked)}if(t.arguments.length>2){const e=t.arguments[2].expression;"ObjectExpression"===e.type&&(c=e)}const y=c?e.getObjectPropValue(c,"defaultValue"):void 0,g="string"==typeof y?y:f,h=e=>{if(!e||!Array.isArray(e.properties))return!1;for(const t of e.properties)if(t&&"KeyValueProperty"===t.type&&t.key){const e="Identifier"===t.key.type&&t.key.value||"StringLiteral"===t.key.type&&t.key.value;if("string"==typeof e&&e.startsWith("defaultValue"))return!0}return!1},d="string"==typeof g||h(c),x=h(c),k=Boolean(x||"string"==typeof g&&!("string"==typeof(v=g)&&/{{\s*count\s*}}/.test(v)));var v;for(let t=0;t<l.length;t++){let r,n=l[t];if(c){const t=e.getObjectPropValue(c,"ns");"string"==typeof t&&(r=t)}const s=this.config.extract.nsSeparator??":";if(!r&&s&&n.includes(s)){const e=n.split(s);if(r=e.shift(),n=e.join(s),!n||""===n.trim()){this.logger.warn(`Skipping key that became empty after namespace removal: '${r}${s}'`);continue}}!r&&i?.defaultNs&&(r=i.defaultNs),r||(r=this.config.extract.defaultNS);let o=n;if(i?.keyPrefix){const e=this.config.extract.keySeparator??".";if(o=!1!==e?i.keyPrefix.endsWith(e)?`${i.keyPrefix}${n}`:`${i.keyPrefix}${e}${n}`:`${i.keyPrefix}${n}`,!1!==e){if(o.split(e).some(e=>""===e.trim())){this.logger.warn(`Skipping key with empty segments: '${o}' (keyPrefix: '${i.keyPrefix}', key: '${n}')`);continue}}}const p=t===l.length-1&&g||n;if(c){const t=e.getObjectProperty(c,"context"),n=[];if("StringLiteral"===t?.value?.type||"NumericLiteral"===t?.value.type||"BooleanLiteral"===t?.value.type){const e=`${t.value.value}`,i=this.config.extract.contextSeparator??"_";""!==e&&n.push({key:`${o}${i}${e}`,ns:r,defaultValue:p,explicitDefault:d})}else if(t?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(t.value),i=this.config.extract.contextSeparator??"_";e.length>0&&(e.forEach(e=>{n.push({key:`${o}${i}${e}`,ns:r,defaultValue:p,explicitDefault:d})}),n.push({key:o,ns:r,defaultValue:p,explicitDefault:d}))}const i=e=>{if(e){if("KeyValueProperty"===e.type&&e.key){if("Identifier"===e.key.type)return e.key.value;if("StringLiteral"===e.key.type)return e.key.value}return"KeyValueProperty"===e.type&&e.value&&"Identifier"===e.value.type?e.key&&"Identifier"===e.key.type?e.key.value:void 0:"ShorthandProperty"!==e.type&&"Identifier"!==e.type||!e.value?e.key&&"string"==typeof e.key?e.key:void 0:e.value}},s=(()=>{if(!c||!Array.isArray(c.properties))return!1;for(const e of c.properties){if("count"===i(e))return!0}return!1})(),l=(()=>{if(!c||!Array.isArray(c.properties))return!1;for(const e of c.properties){if("ordinal"===i(e))return!("KeyValueProperty"!==e.type||!e.value||"BooleanLiteral"!==e.value.type)&&Boolean(e.value.value)}return!1})();if(s||u){this.config.extract.disablePlurals?n.length>0?n.forEach(this.pluginContext.addKey):this.pluginContext.addKey({key:o,ns:r,defaultValue:p,explicitDefault:d}):this.handlePluralKeys(o,r,c,l||u,g,k);continue}if(n.length>0){n.forEach(this.pluginContext.addKey);continue}!0===e.getObjectPropValue(c,"returnObjects")&&this.objectKeys.add(o)}a&&this.objectKeys.add(o),this.pluginContext.addKey({key:o,ns:r,defaultValue:p,explicitDefault:d})}}handleCallExpressionArgument(e,t){const r=e.arguments[t].expression,n=[];let i=!1;if("ArrowFunctionExpression"===r.type){const e=this.extractKeyFromSelector(r);e&&(n.push(e),i=!0)}else if("ArrayExpression"===r.type)for(const e of r.elements)e?.expression&&n.push(...this.expressionResolver.resolvePossibleKeyStringValues(e.expression));else n.push(...this.expressionResolver.resolvePossibleKeyStringValues(r));return{keysToProcess:n.filter(e=>!!e),isSelectorAPI:i}}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 r=t;const n=[];for(;"MemberExpression"===r.type;){const e=r.property;if("Identifier"===e.type)n.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;n.unshift(e.expression.value)}r=r.object}if(n.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return n.join(t)}return null}handlePluralKeys(t,r,n,i,s,o){try{const l=i?"ordinal":"cardinal",a=new Set;for(const e of this.config.locales)try{const t=new Intl.PluralRules(e,{type:l});t.resolvedOptions().pluralCategories.forEach(e=>a.add(e))}catch(e){const t=new Intl.PluralRules("en",{type:l});t.resolvedOptions().pluralCategories.forEach(e=>a.add(e))}const u=Array.from(a).sort(),p=this.config.extract.pluralSeparator??"_",f=e.getObjectPropValue(n,"defaultValue"),c=e.getObjectPropValue(n,`defaultValue${p}other`),y=e.getObjectPropValue(n,`defaultValue${p}ordinal${p}other`),g=e.getObjectProperty(n,"context"),h=[];if(g?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(g.value);if(e.length>0)if("StringLiteral"===g.value.type)for(const r of e)r.length>0&&h.push({key:t,context:r});else{for(const r of e)r.length>0&&h.push({key:t,context:r});!1!==this.config.extract?.generateBasePluralForms&&h.push({key:t})}else h.push({key:t})}else h.push({key:t});for(const{key:t,context:l}of h)for(const a of u){const u=i?`defaultValue${p}ordinal${p}${a}`:`defaultValue${p}${a}`,g=e.getObjectPropValue(n,u);let h,d;if(h="string"==typeof g?g:"one"===a&&"string"==typeof f?f:"one"===a&&"string"==typeof s?s:i&&"string"==typeof y?y:i||"string"!=typeof c?"string"==typeof f?f:"string"==typeof s?s:t:c,l){const e=this.config.extract.contextSeparator??"_";d=i?`${t}${e}${l}${p}ordinal${p}${a}`:`${t}${e}${l}${p}${a}`}else d=i?`${t}${p}ordinal${p}${a}`:`${t}${p}${a}`;this.pluginContext.addKey({key:d,ns:r,defaultValue:h,hasCount:!0,isOrdinal:i,explicitDefault:Boolean(o||"string"==typeof g||"string"==typeof c)})}}catch(i){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const o=s||e.getObjectPropValue(n,"defaultValue");this.pluginContext.addKey({key:t,ns:r,defaultValue:"string"==typeof o?o:t})}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let r=e;for(;"MemberExpression"===r.type;){if("Identifier"!==r.property.type)return null;t.unshift(r.property.value),r=r.object}if("ThisExpression"===r.type)t.unshift("this");else{if("Identifier"!==r.type)return null;t.unshift(r.value)}return t.join(".")}return null}};
@@ -1 +1 @@
1
- "use strict";var e=require("./ast-utils.js");exports.extractFromTransComponent=function(t,n){const i=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),s=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),p=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"values"===e.name.value);let o;s||"JSXAttribute"!==p?.type||"JSXExpressionContainer"!==p.value?.type||"ObjectExpression"!==p.value.expression.type||(o=e.getObjectProperty(p.value.expression,"count"));const l=!!s||!!o,a=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"tOptions"===e.name.value),u="JSXAttribute"===a?.type&&"JSXExpressionContainer"===a.value?.type&&"ObjectExpression"===a.value.expression.type?a.value.expression:void 0,y=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),f=!!y,c=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value);let S="JSXAttribute"===c?.type&&"JSXExpressionContainer"===c.value?.type?c.value.expression:"JSXAttribute"===c?.type&&"StringLiteral"===c.value?.type?c.value:void 0;const g=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value);let v;if(v="JSXAttribute"===g?.type&&"StringLiteral"===g.value?.type?g.value.value:void 0,u&&(void 0===v&&(v=e.getObjectPropValue(u,"ns")),void 0===S)){const t=e.getObjectProperty(u,"context");t?.value&&(S=t.value)}const x=function(e,t){if(!e||0===e.length)return"";const n=new Set(t.extract.transKeepBasicHtmlNodesFor??["br","strong","i","p"]),i=e=>e&&"JSXText"===e.type&&/^\s*$/.test(e.value)&&e.value.includes("\n");function r(e,t,s=!1){if(!e||!e.length)return;let p=0,o=e.length-1;for(;p<=o&&i(e[p]);)p++;for(;o>=p&&i(e[o]);)o--;const l=p<=o?e.slice(p,o+1):[],a=l.some(e=>e&&("JSXElement"===e.type||"JSXFragment"===e.type));for(let e=0;e<l.length;e++){const p=l[e];if(p)if("JSXText"!==p.type)if("JSXExpressionContainer"!==p.type){if("JSXElement"===p.type){const e=p.opening&&p.opening.name&&"Identifier"===p.opening.name.type?p.opening.name.value:void 0;if(e&&n.has(e)){!(!p.opening||!p.opening.selfClosing)&&t.push(p),r(p.children||[],t,!1)}else t.push(p),r(p.children||[],t,!0);continue}"JSXFragment"!==p.type||r(p.children||[],t,s)}else{if(s&&!a&&p.expression){const e=p.expression.type;if("ObjectExpression"===e){const e=p.expression.properties&&p.expression.properties[0];if(e&&"KeyValueProperty"===e.type)continue}if("StringLiteral"===e){const e=String(p.expression.value||"");if(!(/^\s*$/.test(e)&&!e.includes("\n")))continue}else if("Identifier"===e||"MemberExpression"===e||"CallExpression"===e)continue}if(p.expression&&"StringLiteral"===p.expression.type){const n=String(p.expression.value||""),r=/^\s*$/.test(n)&&!n.includes("\n"),s=l[e-1],o=l[e+1];if(r){const n=l[e+2];if(o&&"JSXText"===o.type&&i(o)&&n&&("JSXElement"===n.type||"JSXFragment"===n.type)){const t=l[e-1],n=l[e-2];if(!t||"JSXText"!==t.type&&n&&"JSXExpressionContainer"===n.type)continue}if(s&&("JSXElement"===s.type||"JSXFragment"===s.type)&&o&&"JSXText"===o.type&&i(o))continue;const r=!o||"JSXText"===o.type&&!i(o);if(s&&"JSXText"===s.type&&r){const e=t[t.length-1];if(e&&"JSXText"===e.type){e.value=String(e.value)+p.expression.value;continue}}if(s&&("JSXElement"===s.type||"JSXFragment"===s.type)&&o&&"JSXText"===o.type&&i(o))continue}}t.push(p)}else{if(s&&!a)continue;if(s&&i(p))continue;if(i(p)){const n=l[e-1],i=l[e+1];if(n&&("JSXElement"===n.type||"JSXFragment"===n.type)&&i&&("JSXElement"===i.type||"JSXFragment"===i.type))continue;const r=t[t.length-1],s=l[e-1];if(r){if(s&&"JSXExpressionContainer"===s.type)continue;if("JSXText"===r.type&&s&&"JSXText"===s.type){r.value=String(r.value)+p.value;continue}}}if(s&&a&&0===e)continue;t.push(p)}}}const s=[];r(e,s,!1);const p=e=>String(e).replace(/^\s*\n\s*/g,"").replace(/\s*\n\s*$/g,"");function o(e,t){if(!e||0===e.length)return"";let r="",l=!1;for(let a=0;a<e.length;a++){const u=e[a];if(u)if("JSXText"!==u.type){if("JSXExpressionContainer"===u.type){const e=u.expression;if(!e)continue;if("StringLiteral"===e.type)r+=e.value;else if("Identifier"===e.type)r+=`{{${e.value}}}`;else if("ObjectExpression"===e.type){const t=e.properties[0];t&&"KeyValueProperty"===t.type&&t.key&&"Identifier"===t.key.type?r+=`{{${t.key.value}}}`:t&&"Identifier"===t.type?r+=`{{${t.value}}}`:r+="{{value}}"}else"MemberExpression"===e.type&&e.property&&"Identifier"===e.property.type?r+=`{{${e.property.value}}}`:"CallExpression"===e.type&&"Identifier"===e.callee?.type?r+=`{{${e.callee.value}}}`:r+="{{value}}";l=!1;continue}if("JSXElement"===u.type){let i;if(u.opening&&u.opening.name&&"Identifier"===u.opening.name.type&&(i=u.opening.name.value),i&&n.has(i)){const n=o(u.children||[],t),s=!(!u.opening||!u.opening.selfClosing),p=""!==String(n).trim();if(s||!p){const t=e[a-1];t&&"JSXText"===t.type&&/\n\s*$/.test(t.value)&&(r=r.replace(/\s+$/,"")),r+=`<${i}/>`,l=!0}else r+=`<${i}>${n}</${i}>`,l=!1}else{const e=u.children||[];if(e.some(e=>e&&("JSXText"===e.type||"JSXExpressionContainer"===e.type)&&-1!==s.indexOf(e))){const t=s.indexOf(u),n=o(e,void 0);r+=`<${t}>${p(n)}</${t}>`,l=!1}else{const i=new Map;let a=0;for(const t of e)if(t&&"JSXElement"===t.type){const e=t.opening&&t.opening.name&&"Identifier"===t.opening.name.type?t.opening.name.value:void 0;if(e&&n.has(e)){!(!t.opening||!t.opening.selfClosing)&&i.set(t,a++)}else i.set(t,a++)}const y=t&&t.has(u)?t.get(u):s.indexOf(u),f=o(e,i.size?i:void 0);r+=`<${y}>${p(f)}</${y}>`,l=!1}}continue}"JSXFragment"!==u.type||(r+=o(u.children||[]),l=!1)}else{if(i(u))continue;l?(r+=u.value.replace(/^\s+/,""),l=!1):r+=u.value}}return r}const l=o(e);return String(l).replace(/\s+/g," ").trim()}(t.children,n);let d,J,X;if("JSXAttribute"===r?.type&&"StringLiteral"===r.value?.type)d=r.value.value;else{const e=n.extract.defaultValue;d="string"==typeof e?e:""}if("JSXAttribute"===i?.type){if("StringLiteral"===i.value?.type){if(J=i.value,X=J.value,!X||""===X.trim())return null;if(v&&"StringLiteral"===J.type){const e=n.extract.nsSeparator??":",t=J.value;if(e&&t.startsWith(`${v}${e}`)){if(X=t.slice(`${v}${e}`.length),!X||""===X.trim())return null;J={...J,value:X}}}}else"JSXExpressionContainer"===i.value?.type&&"JSXEmptyExpression"!==i.value.expression.type&&(J=i.value.expression);if(!J)return null}return r||!X||x.trim()?!r&&x.trim()&&(d=x):d=X,{keyExpression:J,serializedChildren:x,ns:v,defaultValue:d,hasCount:l,isOrdinal:f,contextExpression:S,optionsNode:u,explicitDefault:Boolean(r&&"JSXAttribute"===r.type&&"StringLiteral"===r.value?.type||(e=>{if(!e||!Array.isArray(e.properties))return!1;for(const t of e.properties)if(t&&"KeyValueProperty"===t.type&&t.key){const e="Identifier"===t.key.type&&t.key.value||"StringLiteral"===t.key.type&&t.key.value;if("string"==typeof e&&e.startsWith("defaultValue"))return!0}return!1})(u))}};
1
+ "use strict";var e=require("./ast-utils.js");function t(t){if(t)return"StringLiteral"===t.type?t.value:"TemplateLiteral"===t.type&&e.isSimpleTemplateLiteral(t)?t.quasis[0].cooked:void 0}function n(e){return"StringLiteral"===e.value?.type?e.value.value:"JSXExpressionContainer"===e.value?.type?t(e.value.expression):void 0}exports.extractFromTransComponent=function(i,r){const s=i.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),o=i.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),p=i.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),l=i.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"values"===e.name.value);let a;p||"JSXAttribute"!==l?.type||"JSXExpressionContainer"!==l.value?.type||"ObjectExpression"!==l.value.expression.type||(a=e.getObjectProperty(l.value.expression,"count"));const u=!!p||!!a,y=i.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"tOptions"===e.name.value),f="JSXAttribute"===y?.type&&"JSXExpressionContainer"===y.value?.type&&"ObjectExpression"===y.value.expression.type?y.value.expression:void 0,c=i.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),v=!!c,S=i.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value);let d="JSXAttribute"===S?.type&&"JSXExpressionContainer"===S.value?.type?S.value.expression:"JSXAttribute"===S?.type&&"StringLiteral"===S.value?.type?S.value:void 0;const g=i.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value);let x;if(x="JSXAttribute"===g?.type?n(g):void 0,f&&(void 0===x&&(x=e.getObjectPropValue(f,"ns")),void 0===d)){const t=e.getObjectProperty(f,"context");t?.value&&(d=t.value)}const m=function(e,n){if(!e||0===e.length)return"";const i=new Set(n.extract.transKeepBasicHtmlNodesFor??["br","strong","i","p"]),r=e=>e&&"JSXText"===e.type&&/^\s*$/.test(e.value)&&e.value.includes("\n");function s(e,n,o=!1){if(!e||!e.length)return;let p=0,l=e.length-1;for(;p<=l&&r(e[p]);)p++;for(;l>=p&&r(e[l]);)l--;const a=p<=l?e.slice(p,l+1):[],u=a.some(e=>e&&("JSXElement"===e.type||"JSXFragment"===e.type));for(let e=0;e<a.length;e++){const p=a[e];if(p)if("JSXText"!==p.type){if("JSXExpressionContainer"===p.type){if(o&&!u&&p.expression){const e=p.expression.type;if("ObjectExpression"===e){const e=p.expression.properties&&p.expression.properties[0];if(e&&"KeyValueProperty"===e.type)continue}const n=t(p.expression);if(void 0!==n){if(!(/^\s*$/.test(n)&&!n.includes("\n")))continue}else if("Identifier"===e||"MemberExpression"===e||"CallExpression"===e)continue}const i=t(p.expression);if(void 0!==i){const t=/^\s*$/.test(i)&&!i.includes("\n"),s=a[e-1],o=a[e+1];if(t){const t=a[e+2];if(o&&"JSXText"===o.type&&r(o)&&t&&("JSXElement"===t.type||"JSXFragment"===t.type)){const t=a[e-1],n=a[e-2];if(!t||"JSXText"!==t.type&&n&&"JSXExpressionContainer"===n.type)continue}if(s&&("JSXElement"===s.type||"JSXFragment"===s.type)&&o&&"JSXText"===o.type&&r(o))continue;const i=!o||"JSXText"===o.type&&!r(o);if(s&&"JSXText"===s.type&&i){const e=n[n.length-1];if(e&&"JSXText"===e.type){e.value=String(e.value)+p.expression.value;continue}}if(s&&("JSXElement"===s.type||"JSXFragment"===s.type)&&o&&"JSXText"===o.type&&r(o))continue}}n.push(p);continue}if("JSXElement"===p.type){const e=p.opening&&p.opening.name&&"Identifier"===p.opening.name.type?p.opening.name.value:void 0;if(e&&i.has(e)){!(!p.opening||!p.opening.selfClosing)&&n.push(p),s(p.children||[],n,!1)}else n.push(p),s(p.children||[],n,!0);continue}"JSXFragment"!==p.type||s(p.children||[],n,o)}else{if(o&&!u)continue;if(o&&r(p))continue;if(r(p)){const t=a[e-1],i=a[e+1];if(t&&("JSXElement"===t.type||"JSXFragment"===t.type)&&i&&("JSXElement"===i.type||"JSXFragment"===i.type))continue;const r=n[n.length-1],s=a[e-1];if(r){if(s&&"JSXExpressionContainer"===s.type)continue;if("JSXText"===r.type&&s&&"JSXText"===s.type){r.value=String(r.value)+p.value;continue}}}if(o&&u&&0===e)continue;n.push(p)}}}const o=[];s(e,o,!1);const p=e=>String(e).replace(/^\s*\n\s*/g,"").replace(/\s*\n\s*$/g,"");function l(e,n){if(!e||0===e.length)return"";let s="",a=!1;for(let u=0;u<e.length;u++){const y=e[u];if(y)if("JSXText"!==y.type){if("JSXExpressionContainer"===y.type){const e=y.expression;if(!e)continue;const n=t(e);if(void 0!==n)s+=n;else if("Identifier"===e.type)s+=`{{${e.value}}}`;else if("ObjectExpression"===e.type){const t=e.properties[0];t&&"KeyValueProperty"===t.type&&t.key&&"Identifier"===t.key.type?s+=`{{${t.key.value}}}`:t&&"Identifier"===t.type?s+=`{{${t.value}}}`:s+="{{value}}"}else"MemberExpression"===e.type&&e.property&&"Identifier"===e.property.type?s+=`{{${e.property.value}}}`:"CallExpression"===e.type&&"Identifier"===e.callee?.type?s+=`{{${e.callee.value}}}`:s+="{{value}}";a=!1;continue}if("JSXElement"===y.type){let t;if(y.opening&&y.opening.name&&"Identifier"===y.opening.name.type&&(t=y.opening.name.value),t&&i.has(t)){const i=l(y.children||[],n),r=!(!y.opening||!y.opening.selfClosing),o=""!==String(i).trim();if(r||!o){const n=e[u-1];n&&"JSXText"===n.type&&/\n\s*$/.test(n.value)&&(s=s.replace(/\s+$/,"")),s+=`<${t}/>`,a=!0}else s+=`<${t}>${i}</${t}>`,a=!1}else{const e=y.children||[];if(e.some(e=>e&&("JSXText"===e.type||"JSXExpressionContainer"===e.type)&&-1!==o.indexOf(e))){const t=o.indexOf(y),n=l(e,void 0);s+=`<${t}>${p(n)}</${t}>`,a=!1}else{const t=new Map;let r=0;for(const n of e)if(n&&"JSXElement"===n.type){const e=n.opening&&n.opening.name&&"Identifier"===n.opening.name.type?n.opening.name.value:void 0;if(e&&i.has(e)){!(!n.opening||!n.opening.selfClosing)&&t.set(n,r++)}else t.set(n,r++)}const u=n&&n.has(y)?n.get(y):o.indexOf(y),f=l(e,t.size?t:void 0);s+=`<${u}>${p(f)}</${u}>`,a=!1}}continue}"JSXFragment"!==y.type||(s+=l(y.children||[]),a=!1)}else{if(r(y))continue;a?(s+=y.value.replace(/^\s+/,""),a=!1):s+=y.value}}return s}const a=l(e);return String(a).replace(/\s+/g," ").trim()}(i.children,r);let J;const X="JSXAttribute"===o?.type?n(o):void 0;if(void 0!==X)J=X;else{const e=r.extract.defaultValue;J="string"==typeof e?e:""}let b,E;if("JSXAttribute"===s?.type){if("StringLiteral"===s.value?.type){if(b=s.value,E=b.value,!E||""===E.trim())return null;if(x&&"StringLiteral"===b.type){const e=r.extract.nsSeparator??":",t=b.value;if(e&&t.startsWith(`${x}${e}`)){if(E=t.slice(`${x}${e}`.length),!E||""===E.trim())return null;b={...b,value:E}}}}else"JSXExpressionContainer"===s.value?.type&&"JSXEmptyExpression"!==s.value.expression.type&&(b=s.value.expression);if(!b)return null}return o||!E||m.trim()?!o&&m.trim()&&(J=m):J=E,{keyExpression:b,serializedChildren:m,ns:x,defaultValue:J,hasCount:u,isOrdinal:v,contextExpression:d,optionsNode:f,explicitDefault:void 0!==X||(e=>{if(!e||!Array.isArray(e.properties))return!1;for(const t of e.properties)if(t&&"KeyValueProperty"===t.type&&t.key){const e="Identifier"===t.key.type&&t.key.value||"StringLiteral"===t.key.type&&t.key.value;if("string"==typeof e&&e.startsWith("defaultValue"))return!0}return!1})(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 e from"chokidar";import{glob as o}from"glob";import{minimatch as n}from"minimatch";import i 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 d}from"./init.js";import{runLinter as f}from"./linter.js";import{runStatus as u}from"./status.js";import{runLocizeSync as g,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.13.1"),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 o=await a(),i=async()=>{const e=await s(o,{isWatchMode:!!t.watch,isDryRun:!!t.dryRun,syncPrimaryWithDefaults:!!t.syncPrimary});return t.ci&&!e?(console.log("✅ No files were updated."),process.exit(0)):t.ci&&e&&(console.error("❌ Some files were updated. This should not happen in CI mode."),process.exit(1)),e};if(await i(),t.watch){console.log("\nWatching for changes...");const t=await z(o.extract.input),a=x(o.extract.ignore),r=j(o.extract.output),c=[...a,...r].filter(Boolean),s=t.filter(t=>!c.some(e=>n(t,e,{dot:!0})));e.watch(s,{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),i()})}}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,e)=>{let o=await r();if(!o){console.log(i.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(i.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${i.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),o=t}await u(o,{detail:t,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 t=>{const o=await a(),i=()=>l(o);if(await i(),t.watch){console.log("\nWatching for changes...");const t=await z(o.types?.input||[]),a=[...x(o.extract?.ignore)].filter(Boolean),r=t.filter(t=>!a.some(e=>n(t,e,{dot:!0})));e.watch(r,{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),i()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=await a();await p(t)}),w.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await m(t)}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(d),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 o=async()=>{let t=await r();if(!t){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 i18next-cli init")}`),process.exit(1)),console.log(i.green("Project structure detected successfully!")),t=e}await f(t)};if(await o(),t.watch){console.log("\nWatching for changes...");const t=await r();if(t?.extract?.input){const i=await z(t.extract.input),a=[...x(t.extract.ignore),...j(t.extract.output)].filter(Boolean),r=i.filter(t=>!a.some(e=>n(t,e,{dot:!0})));e.watch(r,{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),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 t=>{const e=await a();await g(e,t)}),w.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const e=await a();await y(e,t)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const e=await a();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 e=x(t),n=await Promise.all(e.map(t=>o(t||"",{nodir:!0})));return Array.from(new Set(n.flat()))};
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.15.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()))};
@@ -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 o=await async function(){for(const t of p){const o=r(process.cwd(),t);try{return await e(o),o}catch{}}return null}();if(!o)return null;try{let r;if(o.endsWith(".ts")){const t=await w(),n=a(process.cwd(),{alias:t,interopDefault:!1}),e=await n.import(o,{default:!0});r=e}else{const t=n(o).href,e=await import(`${t}?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):(t.error(`Error: No default export found in ${o}`),null)}catch(r){return t.error(`Error loading configuration from ${o}`),t.error(r),null}}async function g(r=new u){let t=await d();if(t)return t;const{shouldInit:o}=await s.prompt([{type:"confirm",name:"shouldInit",message:f.yellow("Configuration file not found. Would you like to create one now?"),default:!0}]);if(o){if(await l(),r.info(f.green("Configuration created. Resuming command...")),t=await d(),t)return t;r.error(f.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)}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
+ 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
- function e(e,t){return e.properties.filter(e=>"KeyValueProperty"===e.type).find(e=>"Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t)}function t(t,r){const i=e(t,r);if("KeyValueProperty"===i?.type){const e=i.value;return"StringLiteral"===e.type||("BooleanLiteral"===e.type||"NumericLiteral"===e.type)?e.value:""}}export{t as getObjectPropValue,e as getObjectProperty};
1
+ function e(e,t){return e.properties.filter(e=>"KeyValueProperty"===e.type).find(e=>"Identifier"===e.key?.type&&e.key.value===t||"StringLiteral"===e.key?.type&&e.key.value===t)}function t(e){return 1===e.quasis.length&&0===e.expressions.length&&null!=e.quasis[0].cooked}function r(r,i){const n=e(r,i);if("KeyValueProperty"===n?.type){const e=n.value;return"StringLiteral"===e.type?e.value:"TemplateLiteral"===e.type&&t(e)?e.quasis[0].cooked:"BooleanLiteral"===e.type||"NumericLiteral"===e.type?e.value:""}}export{r as getObjectPropValue,e as getObjectProperty,t as isSimpleTemplateLiteral};
@@ -1 +1 @@
1
- import{getObjectPropValue as e,getObjectProperty as t}from"./ast-utils.js";class r{pluginContext;config;logger;expressionResolver;objectKeys=new Set;constructor(e,t,r,n){this.config=e,this.pluginContext=t,this.logger=r,this.expressionResolver=n}handleCallExpression(r,n){const i=this.getFunctionName(r.callee);if(!i)return;const s=n(i),o=this.config.extract.functions||["t","*.t"];let l=void 0!==s;if(!l)for(const e of o)if(e.startsWith("*.")){if(i.endsWith(e.substring(1))){l=!0;break}}else if(e===i){l=!0;break}if(!l||0===r.arguments.length)return;const{keysToProcess:a,isSelectorAPI:u}=this.handleCallExpressionArgument(r,0);if(0===a.length)return;let f=!1;const p=this.config.extract.pluralSeparator??"_";for(let e=0;e<a.length;e++)a[e].endsWith(`${p}ordinal`)&&(f=!0,a[e]=a[e].slice(0,-8));let y,c;if(r.arguments.length>1){const e=r.arguments[1].expression;"ObjectExpression"===e.type?c=e:"StringLiteral"===e.type&&(y=e.value)}if(r.arguments.length>2){const e=r.arguments[2].expression;"ObjectExpression"===e.type&&(c=e)}const g=c?e(c,"defaultValue"):void 0,h="string"==typeof g?g:y,d=e=>{if(!e||!Array.isArray(e.properties))return!1;for(const t of e.properties)if(t&&"KeyValueProperty"===t.type&&t.key){const e="Identifier"===t.key.type&&t.key.value||"StringLiteral"===t.key.type&&t.key.value;if("string"==typeof e&&e.startsWith("defaultValue"))return!0}return!1},x="string"==typeof h||d(c),k=d(c),v=Boolean(k||"string"==typeof h&&!("string"==typeof($=h)&&/{{\s*count\s*}}/.test($)));var $;for(let r=0;r<a.length;r++){let n,i=a[r];if(c){const t=e(c,"ns");"string"==typeof t&&(n=t)}const o=this.config.extract.nsSeparator??":";if(!n&&o&&i.includes(o)){const e=i.split(o);if(n=e.shift(),i=e.join(o),!i||""===i.trim()){this.logger.warn(`Skipping key that became empty after namespace removal: '${n}${o}'`);continue}}!n&&s?.defaultNs&&(n=s.defaultNs),n||(n=this.config.extract.defaultNS);let l=i;if(s?.keyPrefix){const e=this.config.extract.keySeparator??".";if(l=!1!==e?s.keyPrefix.endsWith(e)?`${s.keyPrefix}${i}`:`${s.keyPrefix}${e}${i}`:`${s.keyPrefix}${i}`,!1!==e){if(l.split(e).some(e=>""===e.trim())){this.logger.warn(`Skipping key with empty segments: '${l}' (keyPrefix: '${s.keyPrefix}', key: '${i}')`);continue}}}const p=r===a.length-1&&h||i;if(c){const r=t(c,"context"),i=[];if("StringLiteral"===r?.value?.type||"NumericLiteral"===r?.value.type||"BooleanLiteral"===r?.value.type){const e=`${r.value.value}`,t=this.config.extract.contextSeparator??"_";""!==e&&i.push({key:`${l}${t}${e}`,ns:n,defaultValue:p,explicitDefault:x})}else if(r?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(r.value),t=this.config.extract.contextSeparator??"_";e.length>0&&(e.forEach(e=>{i.push({key:`${l}${t}${e}`,ns:n,defaultValue:p,explicitDefault:x})}),i.push({key:l,ns:n,defaultValue:p,explicitDefault:x}))}const s=e=>{if(e){if("KeyValueProperty"===e.type&&e.key){if("Identifier"===e.key.type)return e.key.value;if("StringLiteral"===e.key.type)return e.key.value}return"KeyValueProperty"===e.type&&e.value&&"Identifier"===e.value.type?e.key&&"Identifier"===e.key.type?e.key.value:void 0:"ShorthandProperty"!==e.type&&"Identifier"!==e.type||!e.value?e.key&&"string"==typeof e.key?e.key:void 0:e.value}},o=(()=>{if(!c||!Array.isArray(c.properties))return!1;for(const e of c.properties){if("count"===s(e))return!0}return!1})(),a=(()=>{if(!c||!Array.isArray(c.properties))return!1;for(const e of c.properties){if("ordinal"===s(e))return!("KeyValueProperty"!==e.type||!e.value||"BooleanLiteral"!==e.value.type)&&Boolean(e.value.value)}return!1})();if(o||f){this.config.extract.disablePlurals?i.length>0?i.forEach(this.pluginContext.addKey):this.pluginContext.addKey({key:l,ns:n,defaultValue:p,explicitDefault:x}):this.handlePluralKeys(l,n,c,a||f,h,v);continue}if(i.length>0){i.forEach(this.pluginContext.addKey);continue}!0===e(c,"returnObjects")&&this.objectKeys.add(l)}u&&this.objectKeys.add(l),this.pluginContext.addKey({key:l,ns:n,defaultValue:p,explicitDefault:x})}}handleCallExpressionArgument(e,t){const r=e.arguments[t].expression,n=[];let i=!1;if("ArrowFunctionExpression"===r.type){const e=this.extractKeyFromSelector(r);e&&(n.push(e),i=!0)}else if("ArrayExpression"===r.type)for(const e of r.elements)e?.expression&&n.push(...this.expressionResolver.resolvePossibleKeyStringValues(e.expression));else n.push(...this.expressionResolver.resolvePossibleKeyStringValues(r));return{keysToProcess:n.filter(e=>!!e),isSelectorAPI:i}}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 r=t;const n=[];for(;"MemberExpression"===r.type;){const e=r.property;if("Identifier"===e.type)n.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;n.unshift(e.expression.value)}r=r.object}if(n.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return n.join(t)}return null}handlePluralKeys(r,n,i,s,o,l){try{const a=s?"ordinal":"cardinal",u=new Set;for(const e of this.config.locales)try{const t=new Intl.PluralRules(e,{type:a});t.resolvedOptions().pluralCategories.forEach(e=>u.add(e))}catch(e){const t=new Intl.PluralRules("en",{type:a});t.resolvedOptions().pluralCategories.forEach(e=>u.add(e))}const f=Array.from(u).sort(),p=this.config.extract.pluralSeparator??"_",y=e(i,"defaultValue"),c=e(i,`defaultValue${p}other`),g=e(i,`defaultValue${p}ordinal${p}other`),h=t(i,"context"),d=[];if(h?.value){const e=this.expressionResolver.resolvePossibleContextStringValues(h.value);if(e.length>0)if("StringLiteral"===h.value.type)for(const t of e)t.length>0&&d.push({key:r,context:t});else{for(const t of e)t.length>0&&d.push({key:r,context:t});!1!==this.config.extract?.generateBasePluralForms&&d.push({key:r})}else d.push({key:r})}else d.push({key:r});for(const{key:t,context:r}of d)for(const a of f){const u=e(i,s?`defaultValue${p}ordinal${p}${a}`:`defaultValue${p}${a}`);let f,h;if(f="string"==typeof u?u:"one"===a&&"string"==typeof y?y:"one"===a&&"string"==typeof o?o:s&&"string"==typeof g?g:s||"string"!=typeof c?"string"==typeof y?y:"string"==typeof o?o:t:c,r){const e=this.config.extract.contextSeparator??"_";h=s?`${t}${e}${r}${p}ordinal${p}${a}`:`${t}${e}${r}${p}${a}`}else h=s?`${t}${p}ordinal${p}${a}`:`${t}${p}${a}`;this.pluginContext.addKey({key:h,ns:n,defaultValue:f,hasCount:!0,isOrdinal:s,explicitDefault:Boolean(l||"string"==typeof u||"string"==typeof c)})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const s=o||e(i,"defaultValue");this.pluginContext.addKey({key:r,ns:n,defaultValue:"string"==typeof s?s:r})}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let r=e;for(;"MemberExpression"===r.type;){if("Identifier"!==r.property.type)return null;t.unshift(r.property.value),r=r.object}if("ThisExpression"===r.type)t.unshift("this");else{if("Identifier"!==r.type)return null;t.unshift(r.value)}return t.join(".")}return null}}export{r as CallExpressionHandler};
1
+ import{isSimpleTemplateLiteral as e,getObjectPropValue as t,getObjectProperty as r}from"./ast-utils.js";class n{pluginContext;config;logger;expressionResolver;objectKeys=new Set;constructor(e,t,r,n){this.config=e,this.pluginContext=t,this.logger=r,this.expressionResolver=n}handleCallExpression(n,i){const s=this.getFunctionName(n.callee);if(!s)return;const o=i(s),l=this.config.extract.functions||["t","*.t"];let a=void 0!==o;if(!a)for(const e of l)if(e.startsWith("*.")){if(s.endsWith(e.substring(1))){a=!0;break}}else if(e===s){a=!0;break}if(!a||0===n.arguments.length)return;const{keysToProcess:u,isSelectorAPI:f}=this.handleCallExpressionArgument(n,0);if(0===u.length)return;let p=!1;const y=this.config.extract.pluralSeparator??"_";for(let e=0;e<u.length;e++)u[e].endsWith(`${y}ordinal`)&&(p=!0,u[e]=u[e].slice(0,-8));let c,g;if(n.arguments.length>1){const t=n.arguments[1].expression;"ObjectExpression"===t.type?g=t:"StringLiteral"===t.type?c=t.value:"TemplateLiteral"===t.type&&e(t)&&(c=t.quasis[0].cooked)}if(n.arguments.length>2){const e=n.arguments[2].expression;"ObjectExpression"===e.type&&(g=e)}const h=g?t(g,"defaultValue"):void 0,d="string"==typeof h?h:c,x=e=>{if(!e||!Array.isArray(e.properties))return!1;for(const t of e.properties)if(t&&"KeyValueProperty"===t.type&&t.key){const e="Identifier"===t.key.type&&t.key.value||"StringLiteral"===t.key.type&&t.key.value;if("string"==typeof e&&e.startsWith("defaultValue"))return!0}return!1},k="string"==typeof d||x(g),v=x(g),$=Boolean(v||"string"==typeof d&&!("string"==typeof(m=d)&&/{{\s*count\s*}}/.test(m)));var m;for(let e=0;e<u.length;e++){let n,i=u[e];if(g){const e=t(g,"ns");"string"==typeof e&&(n=e)}const s=this.config.extract.nsSeparator??":";if(!n&&s&&i.includes(s)){const e=i.split(s);if(n=e.shift(),i=e.join(s),!i||""===i.trim()){this.logger.warn(`Skipping key that became empty after namespace removal: '${n}${s}'`);continue}}!n&&o?.defaultNs&&(n=o.defaultNs),n||(n=this.config.extract.defaultNS);let l=i;if(o?.keyPrefix){const e=this.config.extract.keySeparator??".";if(l=!1!==e?o.keyPrefix.endsWith(e)?`${o.keyPrefix}${i}`:`${o.keyPrefix}${e}${i}`:`${o.keyPrefix}${i}`,!1!==e){if(l.split(e).some(e=>""===e.trim())){this.logger.warn(`Skipping key with empty segments: '${l}' (keyPrefix: '${o.keyPrefix}', key: '${i}')`);continue}}}const a=e===u.length-1&&d||i;if(g){const e=r(g,"context"),i=[];if("StringLiteral"===e?.value?.type||"NumericLiteral"===e?.value.type||"BooleanLiteral"===e?.value.type){const t=`${e.value.value}`,r=this.config.extract.contextSeparator??"_";""!==t&&i.push({key:`${l}${r}${t}`,ns:n,defaultValue:a,explicitDefault:k})}else if(e?.value){const t=this.expressionResolver.resolvePossibleContextStringValues(e.value),r=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{i.push({key:`${l}${r}${e}`,ns:n,defaultValue:a,explicitDefault:k})}),i.push({key:l,ns:n,defaultValue:a,explicitDefault:k}))}const s=e=>{if(e){if("KeyValueProperty"===e.type&&e.key){if("Identifier"===e.key.type)return e.key.value;if("StringLiteral"===e.key.type)return e.key.value}return"KeyValueProperty"===e.type&&e.value&&"Identifier"===e.value.type?e.key&&"Identifier"===e.key.type?e.key.value:void 0:"ShorthandProperty"!==e.type&&"Identifier"!==e.type||!e.value?e.key&&"string"==typeof e.key?e.key:void 0:e.value}},o=(()=>{if(!g||!Array.isArray(g.properties))return!1;for(const e of g.properties){if("count"===s(e))return!0}return!1})(),u=(()=>{if(!g||!Array.isArray(g.properties))return!1;for(const e of g.properties){if("ordinal"===s(e))return!("KeyValueProperty"!==e.type||!e.value||"BooleanLiteral"!==e.value.type)&&Boolean(e.value.value)}return!1})();if(o||p){this.config.extract.disablePlurals?i.length>0?i.forEach(this.pluginContext.addKey):this.pluginContext.addKey({key:l,ns:n,defaultValue:a,explicitDefault:k}):this.handlePluralKeys(l,n,g,u||p,d,$);continue}if(i.length>0){i.forEach(this.pluginContext.addKey);continue}!0===t(g,"returnObjects")&&this.objectKeys.add(l)}f&&this.objectKeys.add(l),this.pluginContext.addKey({key:l,ns:n,defaultValue:a,explicitDefault:k})}}handleCallExpressionArgument(e,t){const r=e.arguments[t].expression,n=[];let i=!1;if("ArrowFunctionExpression"===r.type){const e=this.extractKeyFromSelector(r);e&&(n.push(e),i=!0)}else if("ArrayExpression"===r.type)for(const e of r.elements)e?.expression&&n.push(...this.expressionResolver.resolvePossibleKeyStringValues(e.expression));else n.push(...this.expressionResolver.resolvePossibleKeyStringValues(r));return{keysToProcess:n.filter(e=>!!e),isSelectorAPI:i}}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 r=t;const n=[];for(;"MemberExpression"===r.type;){const e=r.property;if("Identifier"===e.type)n.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;n.unshift(e.expression.value)}r=r.object}if(n.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return n.join(t)}return null}handlePluralKeys(e,n,i,s,o,l){try{const a=s?"ordinal":"cardinal",u=new Set;for(const e of this.config.locales)try{const t=new Intl.PluralRules(e,{type:a});t.resolvedOptions().pluralCategories.forEach(e=>u.add(e))}catch(e){const t=new Intl.PluralRules("en",{type:a});t.resolvedOptions().pluralCategories.forEach(e=>u.add(e))}const f=Array.from(u).sort(),p=this.config.extract.pluralSeparator??"_",y=t(i,"defaultValue"),c=t(i,`defaultValue${p}other`),g=t(i,`defaultValue${p}ordinal${p}other`),h=r(i,"context"),d=[];if(h?.value){const t=this.expressionResolver.resolvePossibleContextStringValues(h.value);if(t.length>0)if("StringLiteral"===h.value.type)for(const r of t)r.length>0&&d.push({key:e,context:r});else{for(const r of t)r.length>0&&d.push({key:e,context:r});!1!==this.config.extract?.generateBasePluralForms&&d.push({key:e})}else d.push({key:e})}else d.push({key:e});for(const{key:e,context:r}of d)for(const a of f){const u=t(i,s?`defaultValue${p}ordinal${p}${a}`:`defaultValue${p}${a}`);let f,h;if(f="string"==typeof u?u:"one"===a&&"string"==typeof y?y:"one"===a&&"string"==typeof o?o:s&&"string"==typeof g?g:s||"string"!=typeof c?"string"==typeof y?y:"string"==typeof o?o:e:c,r){const t=this.config.extract.contextSeparator??"_";h=s?`${e}${t}${r}${p}ordinal${p}${a}`:`${e}${t}${r}${p}${a}`}else h=s?`${e}${p}ordinal${p}${a}`:`${e}${p}${a}`;this.pluginContext.addKey({key:h,ns:n,defaultValue:f,hasCount:!0,isOrdinal:s,explicitDefault:Boolean(l||"string"==typeof u||"string"==typeof c)})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const s=o||t(i,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof s?s:e})}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let r=e;for(;"MemberExpression"===r.type;){if("Identifier"!==r.property.type)return null;t.unshift(r.property.value),r=r.object}if("ThisExpression"===r.type)t.unshift("this");else{if("Identifier"!==r.type)return null;t.unshift(r.value)}return t.join(".")}return null}}export{n as CallExpressionHandler};
@@ -1 +1 @@
1
- import{getObjectProperty as e,getObjectPropValue as t}from"./ast-utils.js";function n(n,i){const r=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),s=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),p=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),o=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"values"===e.name.value);let l;p||"JSXAttribute"!==o?.type||"JSXExpressionContainer"!==o.value?.type||"ObjectExpression"!==o.value.expression.type||(l=e(o.value.expression,"count"));const a=!!p||!!l,u=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"tOptions"===e.name.value),y="JSXAttribute"===u?.type&&"JSXExpressionContainer"===u.value?.type&&"ObjectExpression"===u.value.expression.type?u.value.expression:void 0,f=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),c=!!f,S=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value);let v="JSXAttribute"===S?.type&&"JSXExpressionContainer"===S.value?.type?S.value.expression:"JSXAttribute"===S?.type&&"StringLiteral"===S.value?.type?S.value:void 0;const g=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value);let x;if(x="JSXAttribute"===g?.type&&"StringLiteral"===g.value?.type?g.value.value:void 0,y&&(void 0===x&&(x=t(y,"ns")),void 0===v)){const t=e(y,"context");t?.value&&(v=t.value)}const d=function(e,t){if(!e||0===e.length)return"";const n=new Set(t.extract.transKeepBasicHtmlNodesFor??["br","strong","i","p"]),i=e=>e&&"JSXText"===e.type&&/^\s*$/.test(e.value)&&e.value.includes("\n");function r(e,t,s=!1){if(!e||!e.length)return;let p=0,o=e.length-1;for(;p<=o&&i(e[p]);)p++;for(;o>=p&&i(e[o]);)o--;const l=p<=o?e.slice(p,o+1):[],a=l.some(e=>e&&("JSXElement"===e.type||"JSXFragment"===e.type));for(let e=0;e<l.length;e++){const p=l[e];if(p)if("JSXText"!==p.type)if("JSXExpressionContainer"!==p.type){if("JSXElement"===p.type){const e=p.opening&&p.opening.name&&"Identifier"===p.opening.name.type?p.opening.name.value:void 0;if(e&&n.has(e)){!(!p.opening||!p.opening.selfClosing)&&t.push(p),r(p.children||[],t,!1)}else t.push(p),r(p.children||[],t,!0);continue}"JSXFragment"!==p.type||r(p.children||[],t,s)}else{if(s&&!a&&p.expression){const e=p.expression.type;if("ObjectExpression"===e){const e=p.expression.properties&&p.expression.properties[0];if(e&&"KeyValueProperty"===e.type)continue}if("StringLiteral"===e){const e=String(p.expression.value||"");if(!(/^\s*$/.test(e)&&!e.includes("\n")))continue}else if("Identifier"===e||"MemberExpression"===e||"CallExpression"===e)continue}if(p.expression&&"StringLiteral"===p.expression.type){const n=String(p.expression.value||""),r=/^\s*$/.test(n)&&!n.includes("\n"),s=l[e-1],o=l[e+1];if(r){const n=l[e+2];if(o&&"JSXText"===o.type&&i(o)&&n&&("JSXElement"===n.type||"JSXFragment"===n.type)){const t=l[e-1],n=l[e-2];if(!t||"JSXText"!==t.type&&n&&"JSXExpressionContainer"===n.type)continue}if(s&&("JSXElement"===s.type||"JSXFragment"===s.type)&&o&&"JSXText"===o.type&&i(o))continue;const r=!o||"JSXText"===o.type&&!i(o);if(s&&"JSXText"===s.type&&r){const e=t[t.length-1];if(e&&"JSXText"===e.type){e.value=String(e.value)+p.expression.value;continue}}if(s&&("JSXElement"===s.type||"JSXFragment"===s.type)&&o&&"JSXText"===o.type&&i(o))continue}}t.push(p)}else{if(s&&!a)continue;if(s&&i(p))continue;if(i(p)){const n=l[e-1],i=l[e+1];if(n&&("JSXElement"===n.type||"JSXFragment"===n.type)&&i&&("JSXElement"===i.type||"JSXFragment"===i.type))continue;const r=t[t.length-1],s=l[e-1];if(r){if(s&&"JSXExpressionContainer"===s.type)continue;if("JSXText"===r.type&&s&&"JSXText"===s.type){r.value=String(r.value)+p.value;continue}}}if(s&&a&&0===e)continue;t.push(p)}}}const s=[];r(e,s,!1);const p=e=>String(e).replace(/^\s*\n\s*/g,"").replace(/\s*\n\s*$/g,"");function o(e,t){if(!e||0===e.length)return"";let r="",l=!1;for(let a=0;a<e.length;a++){const u=e[a];if(u)if("JSXText"!==u.type){if("JSXExpressionContainer"===u.type){const e=u.expression;if(!e)continue;if("StringLiteral"===e.type)r+=e.value;else if("Identifier"===e.type)r+=`{{${e.value}}}`;else if("ObjectExpression"===e.type){const t=e.properties[0];t&&"KeyValueProperty"===t.type&&t.key&&"Identifier"===t.key.type?r+=`{{${t.key.value}}}`:t&&"Identifier"===t.type?r+=`{{${t.value}}}`:r+="{{value}}"}else"MemberExpression"===e.type&&e.property&&"Identifier"===e.property.type?r+=`{{${e.property.value}}}`:"CallExpression"===e.type&&"Identifier"===e.callee?.type?r+=`{{${e.callee.value}}}`:r+="{{value}}";l=!1;continue}if("JSXElement"===u.type){let i;if(u.opening&&u.opening.name&&"Identifier"===u.opening.name.type&&(i=u.opening.name.value),i&&n.has(i)){const n=o(u.children||[],t),s=!(!u.opening||!u.opening.selfClosing),p=""!==String(n).trim();if(s||!p){const t=e[a-1];t&&"JSXText"===t.type&&/\n\s*$/.test(t.value)&&(r=r.replace(/\s+$/,"")),r+=`<${i}/>`,l=!0}else r+=`<${i}>${n}</${i}>`,l=!1}else{const e=u.children||[];if(e.some(e=>e&&("JSXText"===e.type||"JSXExpressionContainer"===e.type)&&-1!==s.indexOf(e))){const t=s.indexOf(u),n=o(e,void 0);r+=`<${t}>${p(n)}</${t}>`,l=!1}else{const i=new Map;let a=0;for(const t of e)if(t&&"JSXElement"===t.type){const e=t.opening&&t.opening.name&&"Identifier"===t.opening.name.type?t.opening.name.value:void 0;if(e&&n.has(e)){!(!t.opening||!t.opening.selfClosing)&&i.set(t,a++)}else i.set(t,a++)}const y=t&&t.has(u)?t.get(u):s.indexOf(u),f=o(e,i.size?i:void 0);r+=`<${y}>${p(f)}</${y}>`,l=!1}}continue}"JSXFragment"!==u.type||(r+=o(u.children||[]),l=!1)}else{if(i(u))continue;l?(r+=u.value.replace(/^\s+/,""),l=!1):r+=u.value}}return r}const l=o(e);return String(l).replace(/\s+/g," ").trim()}(n.children,i);let J,X,m;if("JSXAttribute"===s?.type&&"StringLiteral"===s.value?.type)J=s.value.value;else{const e=i.extract.defaultValue;J="string"==typeof e?e:""}if("JSXAttribute"===r?.type){if("StringLiteral"===r.value?.type){if(X=r.value,m=X.value,!m||""===m.trim())return null;if(x&&"StringLiteral"===X.type){const e=i.extract.nsSeparator??":",t=X.value;if(e&&t.startsWith(`${x}${e}`)){if(m=t.slice(`${x}${e}`.length),!m||""===m.trim())return null;X={...X,value:m}}}}else"JSXExpressionContainer"===r.value?.type&&"JSXEmptyExpression"!==r.value.expression.type&&(X=r.value.expression);if(!X)return null}s||!m||d.trim()?!s&&d.trim()&&(J=d):J=m;return{keyExpression:X,serializedChildren:d,ns:x,defaultValue:J,hasCount:a,isOrdinal:c,contextExpression:v,optionsNode:y,explicitDefault:Boolean(s&&"JSXAttribute"===s.type&&"StringLiteral"===s.value?.type||(e=>{if(!e||!Array.isArray(e.properties))return!1;for(const t of e.properties)if(t&&"KeyValueProperty"===t.type&&t.key){const e="Identifier"===t.key.type&&t.key.value||"StringLiteral"===t.key.type&&t.key.value;if("string"==typeof e&&e.startsWith("defaultValue"))return!0}return!1})(y))}}export{n as extractFromTransComponent};
1
+ import{getObjectProperty as e,getObjectPropValue as t,isSimpleTemplateLiteral as n}from"./ast-utils.js";function i(e){if(e)return"StringLiteral"===e.type?e.value:"TemplateLiteral"===e.type&&n(e)?e.quasis[0].cooked:void 0}function r(e){return"StringLiteral"===e.value?.type?e.value.value:"JSXExpressionContainer"===e.value?.type?i(e.value.expression):void 0}function s(n,s){const o=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"i18nKey"===e.name.value),p=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"defaults"===e.name.value),l=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"count"===e.name.value),a=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"values"===e.name.value);let u;l||"JSXAttribute"!==a?.type||"JSXExpressionContainer"!==a.value?.type||"ObjectExpression"!==a.value.expression.type||(u=e(a.value.expression,"count"));const y=!!l||!!u,f=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"tOptions"===e.name.value),c="JSXAttribute"===f?.type&&"JSXExpressionContainer"===f.value?.type&&"ObjectExpression"===f.value.expression.type?f.value.expression:void 0,v=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),S=!!v,d=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"context"===e.name.value);let g="JSXAttribute"===d?.type&&"JSXExpressionContainer"===d.value?.type?d.value.expression:"JSXAttribute"===d?.type&&"StringLiteral"===d.value?.type?d.value:void 0;const x=n.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ns"===e.name.value);let m;if(m="JSXAttribute"===x?.type?r(x):void 0,c&&(void 0===m&&(m=t(c,"ns")),void 0===g)){const t=e(c,"context");t?.value&&(g=t.value)}const J=function(e,t){if(!e||0===e.length)return"";const n=new Set(t.extract.transKeepBasicHtmlNodesFor??["br","strong","i","p"]),r=e=>e&&"JSXText"===e.type&&/^\s*$/.test(e.value)&&e.value.includes("\n");function s(e,t,o=!1){if(!e||!e.length)return;let p=0,l=e.length-1;for(;p<=l&&r(e[p]);)p++;for(;l>=p&&r(e[l]);)l--;const a=p<=l?e.slice(p,l+1):[],u=a.some(e=>e&&("JSXElement"===e.type||"JSXFragment"===e.type));for(let e=0;e<a.length;e++){const p=a[e];if(p)if("JSXText"!==p.type){if("JSXExpressionContainer"===p.type){if(o&&!u&&p.expression){const e=p.expression.type;if("ObjectExpression"===e){const e=p.expression.properties&&p.expression.properties[0];if(e&&"KeyValueProperty"===e.type)continue}const t=i(p.expression);if(void 0!==t){if(!(/^\s*$/.test(t)&&!t.includes("\n")))continue}else if("Identifier"===e||"MemberExpression"===e||"CallExpression"===e)continue}const n=i(p.expression);if(void 0!==n){const i=/^\s*$/.test(n)&&!n.includes("\n"),s=a[e-1],o=a[e+1];if(i){const n=a[e+2];if(o&&"JSXText"===o.type&&r(o)&&n&&("JSXElement"===n.type||"JSXFragment"===n.type)){const t=a[e-1],n=a[e-2];if(!t||"JSXText"!==t.type&&n&&"JSXExpressionContainer"===n.type)continue}if(s&&("JSXElement"===s.type||"JSXFragment"===s.type)&&o&&"JSXText"===o.type&&r(o))continue;const i=!o||"JSXText"===o.type&&!r(o);if(s&&"JSXText"===s.type&&i){const e=t[t.length-1];if(e&&"JSXText"===e.type){e.value=String(e.value)+p.expression.value;continue}}if(s&&("JSXElement"===s.type||"JSXFragment"===s.type)&&o&&"JSXText"===o.type&&r(o))continue}}t.push(p);continue}if("JSXElement"===p.type){const e=p.opening&&p.opening.name&&"Identifier"===p.opening.name.type?p.opening.name.value:void 0;if(e&&n.has(e)){!(!p.opening||!p.opening.selfClosing)&&t.push(p),s(p.children||[],t,!1)}else t.push(p),s(p.children||[],t,!0);continue}"JSXFragment"!==p.type||s(p.children||[],t,o)}else{if(o&&!u)continue;if(o&&r(p))continue;if(r(p)){const n=a[e-1],i=a[e+1];if(n&&("JSXElement"===n.type||"JSXFragment"===n.type)&&i&&("JSXElement"===i.type||"JSXFragment"===i.type))continue;const r=t[t.length-1],s=a[e-1];if(r){if(s&&"JSXExpressionContainer"===s.type)continue;if("JSXText"===r.type&&s&&"JSXText"===s.type){r.value=String(r.value)+p.value;continue}}}if(o&&u&&0===e)continue;t.push(p)}}}const o=[];s(e,o,!1);const p=e=>String(e).replace(/^\s*\n\s*/g,"").replace(/\s*\n\s*$/g,"");function l(e,t){if(!e||0===e.length)return"";let s="",a=!1;for(let u=0;u<e.length;u++){const y=e[u];if(y)if("JSXText"!==y.type){if("JSXExpressionContainer"===y.type){const e=y.expression;if(!e)continue;const t=i(e);if(void 0!==t)s+=t;else if("Identifier"===e.type)s+=`{{${e.value}}}`;else if("ObjectExpression"===e.type){const t=e.properties[0];t&&"KeyValueProperty"===t.type&&t.key&&"Identifier"===t.key.type?s+=`{{${t.key.value}}}`:t&&"Identifier"===t.type?s+=`{{${t.value}}}`:s+="{{value}}"}else"MemberExpression"===e.type&&e.property&&"Identifier"===e.property.type?s+=`{{${e.property.value}}}`:"CallExpression"===e.type&&"Identifier"===e.callee?.type?s+=`{{${e.callee.value}}}`:s+="{{value}}";a=!1;continue}if("JSXElement"===y.type){let i;if(y.opening&&y.opening.name&&"Identifier"===y.opening.name.type&&(i=y.opening.name.value),i&&n.has(i)){const n=l(y.children||[],t),r=!(!y.opening||!y.opening.selfClosing),o=""!==String(n).trim();if(r||!o){const t=e[u-1];t&&"JSXText"===t.type&&/\n\s*$/.test(t.value)&&(s=s.replace(/\s+$/,"")),s+=`<${i}/>`,a=!0}else s+=`<${i}>${n}</${i}>`,a=!1}else{const e=y.children||[];if(e.some(e=>e&&("JSXText"===e.type||"JSXExpressionContainer"===e.type)&&-1!==o.indexOf(e))){const t=o.indexOf(y),n=l(e,void 0);s+=`<${t}>${p(n)}</${t}>`,a=!1}else{const i=new Map;let r=0;for(const t of e)if(t&&"JSXElement"===t.type){const e=t.opening&&t.opening.name&&"Identifier"===t.opening.name.type?t.opening.name.value:void 0;if(e&&n.has(e)){!(!t.opening||!t.opening.selfClosing)&&i.set(t,r++)}else i.set(t,r++)}const u=t&&t.has(y)?t.get(y):o.indexOf(y),f=l(e,i.size?i:void 0);s+=`<${u}>${p(f)}</${u}>`,a=!1}}continue}"JSXFragment"!==y.type||(s+=l(y.children||[]),a=!1)}else{if(r(y))continue;a?(s+=y.value.replace(/^\s+/,""),a=!1):s+=y.value}}return s}const a=l(e);return String(a).replace(/\s+/g," ").trim()}(n.children,s);let X;const E="JSXAttribute"===p?.type?r(p):void 0;if(void 0!==E)X=E;else{const e=s.extract.defaultValue;X="string"==typeof e?e:""}let b,h;if("JSXAttribute"===o?.type){if("StringLiteral"===o.value?.type){if(b=o.value,h=b.value,!h||""===h.trim())return null;if(m&&"StringLiteral"===b.type){const e=s.extract.nsSeparator??":",t=b.value;if(e&&t.startsWith(`${m}${e}`)){if(h=t.slice(`${m}${e}`.length),!h||""===h.trim())return null;b={...b,value:h}}}}else"JSXExpressionContainer"===o.value?.type&&"JSXEmptyExpression"!==o.value.expression.type&&(b=o.value.expression);if(!b)return null}p||!h||J.trim()?!p&&J.trim()&&(X=J):X=h;return{keyExpression:b,serializedChildren:J,ns:m,defaultValue:X,hasCount:y,isOrdinal:S,contextExpression:g,optionsNode:c,explicitDefault:void 0!==E||(e=>{if(!e||!Array.isArray(e.properties))return!1;for(const t of e.properties)if(t&&"KeyValueProperty"===t.type&&t.key){const e="Identifier"===t.key.type&&t.key.value||"StringLiteral"===t.key.type&&t.key.value;if("string"==typeof e&&e.startsWith("defaultValue"))return!0}return!1})(c)}}export{s as extractFromTransComponent};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.13.1",
3
+ "version": "1.15.0",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
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.13.1')
25
+ .version('1.15.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 config = await ensureConfig()
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
- let config = await loadConfig()
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 config = await ensureConfig()
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 config = await ensureConfig()
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 config = await ensureConfig()
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 config = await ensureConfig()
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 config = await ensureConfig()
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
- * @returns Promise that resolves to the loaded configuration object, or null if loading failed
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 configPath = await findConfigFile()
79
+ export async function loadConfig (configPath?: string, logger: Logger = new ConsoleLogger()): Promise<I18nextToolkitConfig | null> {
80
+ const configPathFound = await findConfigFile(configPath)
85
81
 
86
- if (!configPath) {
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 (configPath.endsWith('.ts')) {
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(configPath, { default: true })
101
+ const configModule = await jiti.import(configPathFound, { default: true })
103
102
  config = configModule
104
103
  } else {
105
- const configUrl = pathToFileURL(configPath).href
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 ${configPath}`)
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 ${configPath}`)
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
- * This function is a wrapper around loadConfig that provides an interactive fallback.
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 { ObjectExpression } from '@swc/core'
1
+ import type { ObjectExpression, TemplateLiteral } from '@swc/core'
2
2
 
3
3
  /**
4
4
  * Finds and returns the full property node (KeyValueProperty) for the given
@@ -27,6 +27,18 @@ export function getObjectProperty (object: ObjectExpression, propName: string) {
27
27
  )
28
28
  }
29
29
 
30
+ /**
31
+ * Checks if the given template literal has no interpolation expressions
32
+ *
33
+ * @param literal - Template literal to check
34
+ * @returns Boolean true if the literal has no expressions and can be parsed (no invalid escapes), false otherwise
35
+ *
36
+ * @private
37
+ */
38
+ export function isSimpleTemplateLiteral (literal: TemplateLiteral): boolean {
39
+ return literal.quasis.length === 1 && literal.expressions.length === 0 && literal.quasis[0].cooked != null
40
+ }
41
+
30
42
  /**
31
43
  * Extracts string value from object property.
32
44
  *
@@ -45,6 +57,7 @@ export function getObjectPropValue (object: ObjectExpression, propName: string):
45
57
  if (prop?.type === 'KeyValueProperty') {
46
58
  const val = prop.value
47
59
  if (val.type === 'StringLiteral') return val.value
60
+ if (val.type === 'TemplateLiteral' && isSimpleTemplateLiteral(val)) return val.quasis[0].cooked
48
61
  if (val.type === 'BooleanLiteral') return val.value
49
62
  if (val.type === 'NumericLiteral') return val.value
50
63
  return '' // Indicate presence for other types
@@ -1,7 +1,7 @@
1
1
  import type { CallExpression, ArrowFunctionExpression, ObjectExpression } from '@swc/core'
2
2
  import type { PluginContext, I18nextToolkitConfig, Logger, ExtractedKey, ScopeInfo } from '../../types'
3
3
  import { ExpressionResolver } from './expression-resolver'
4
- import { getObjectProperty, getObjectPropValue } from './ast-utils'
4
+ import { getObjectProperty, getObjectPropValue, isSimpleTemplateLiteral } from './ast-utils'
5
5
 
6
6
  export class CallExpressionHandler {
7
7
  private pluginContext: PluginContext
@@ -87,6 +87,8 @@ export class CallExpressionHandler {
87
87
  options = arg2
88
88
  } else if (arg2.type === 'StringLiteral') {
89
89
  defaultValue = arg2.value
90
+ } else if (arg2.type === 'TemplateLiteral' && isSimpleTemplateLiteral(arg2)) {
91
+ defaultValue = arg2.quasis[0].cooked
90
92
  }
91
93
  }
92
94
  if (node.arguments.length > 2) {
@@ -1,6 +1,6 @@
1
- import type { Expression, JSXElement, ObjectExpression, Property } from '@swc/core'
1
+ import type { Expression, JSXAttribute, JSXElement, JSXExpression, ObjectExpression, Property } from '@swc/core'
2
2
  import type { I18nextToolkitConfig } from '../../types'
3
- import { getObjectProperty, getObjectPropValue } from './ast-utils'
3
+ import { getObjectProperty, getObjectPropValue, isSimpleTemplateLiteral } from './ast-utils'
4
4
 
5
5
  export interface ExtractedJSXAttributes {
6
6
  /** holds the raw key expression from the AST */
@@ -31,6 +31,32 @@ export interface ExtractedJSXAttributes {
31
31
  explicitDefault?: boolean;
32
32
  }
33
33
 
34
+ function getStringLiteralFromExpression (expression: JSXExpression | null): string | undefined {
35
+ if (!expression) return undefined
36
+
37
+ if (expression.type === 'StringLiteral') {
38
+ return expression.value
39
+ }
40
+
41
+ if (expression.type === 'TemplateLiteral' && isSimpleTemplateLiteral(expression)) {
42
+ return expression.quasis[0].cooked
43
+ }
44
+
45
+ return undefined
46
+ }
47
+
48
+ function getStringLiteralFromAttribute (attr: JSXAttribute): string | undefined {
49
+ if (attr.value?.type === 'StringLiteral') {
50
+ return attr.value.value
51
+ }
52
+
53
+ if (attr.value?.type === 'JSXExpressionContainer') {
54
+ return getStringLiteralFromExpression(attr.value.expression)
55
+ }
56
+
57
+ return undefined
58
+ }
59
+
34
60
  /**
35
61
  * Extracts translation keys from JSX Trans components.
36
62
  *
@@ -136,8 +162,8 @@ export function extractFromTransComponent (node: JSXElement, config: I18nextTool
136
162
  // 1. Prioritize direct props for 'ns' and 'context'
137
163
  const nsAttr = node.opening.attributes?.find(attr => attr.type === 'JSXAttribute' && attr.name.type === 'Identifier' && attr.name.value === 'ns')
138
164
  let ns: string | undefined
139
- if (nsAttr?.type === 'JSXAttribute' && nsAttr.value?.type === 'StringLiteral') {
140
- ns = nsAttr.value.value
165
+ if (nsAttr?.type === 'JSXAttribute') {
166
+ ns = getStringLiteralFromAttribute(nsAttr)
141
167
  } else {
142
168
  ns = undefined
143
169
  }
@@ -160,9 +186,10 @@ export function extractFromTransComponent (node: JSXElement, config: I18nextTool
160
186
  // Handle default value properly
161
187
  let defaultValue: string
162
188
 
163
- if (defaultsAttr?.type === 'JSXAttribute' && defaultsAttr.value?.type === 'StringLiteral') {
189
+ const defaultAttributeLiteral = defaultsAttr?.type === 'JSXAttribute' ? getStringLiteralFromAttribute(defaultsAttr) : undefined
190
+ if (defaultAttributeLiteral !== undefined) {
164
191
  // Explicit defaults attribute takes precedence
165
- defaultValue = defaultsAttr.value.value
192
+ defaultValue = defaultAttributeLiteral
166
193
  } else {
167
194
  // Use the configured default value or fall back to empty string
168
195
  const configuredDefault = config.extract.defaultValue
@@ -239,10 +266,7 @@ export function extractFromTransComponent (node: JSXElement, config: I18nextTool
239
266
  return false
240
267
  }
241
268
 
242
- const explicitDefault = Boolean(
243
- (defaultsAttr && defaultsAttr.type === 'JSXAttribute' && defaultsAttr.value?.type === 'StringLiteral') ||
244
- optionsHasDefaultProps(optionsNode)
245
- )
269
+ const explicitDefault = defaultAttributeLiteral !== undefined || optionsHasDefaultProps(optionsNode)
246
270
 
247
271
  return {
248
272
  keyExpression,
@@ -385,8 +409,8 @@ function serializeJSXChildren (children: any[], config: I18nextToolkitConfig): s
385
409
  // For common simple content expressions, don't allocate a separate slot.
386
410
  // However, if it's a pure-space StringLiteral ({" "}) we want to keep the
387
411
  // special-space handling below, so only skip non-whitespace string literals.
388
- if (exprType === 'StringLiteral') {
389
- const textVal = String(n.expression.value || '')
412
+ const textVal = getStringLiteralFromExpression(n.expression)
413
+ if (textVal !== undefined) {
390
414
  const isPureSpaceNoNewline = /^\s*$/.test(textVal) && !textVal.includes('\n')
391
415
  if (!isPureSpaceNoNewline) {
392
416
  continue
@@ -401,8 +425,8 @@ function serializeJSXChildren (children: any[], config: I18nextToolkitConfig): s
401
425
  // - If it's pure space (no newline) and it directly follows a non-text sibling
402
426
  // (element/fragment), treat it as formatting and skip it.
403
427
  // - Otherwise, count it as a slot (do NOT merge it into previous JSXText).
404
- if (n.expression && n.expression.type === 'StringLiteral') {
405
- const textVal = String(n.expression.value || '')
428
+ const textVal = getStringLiteralFromExpression(n.expression)
429
+ if (textVal !== undefined) {
406
430
  const isPureSpaceNoNewline = /^\s*$/.test(textVal) && !textVal.includes('\n')
407
431
  const prevOriginal = meaningfulNodes[i - 1]
408
432
  const nextOriginal = meaningfulNodes[i + 1]
@@ -551,8 +575,9 @@ function serializeJSXChildren (children: any[], config: I18nextToolkitConfig): s
551
575
  const expr = node.expression
552
576
  if (!expr) continue
553
577
 
554
- if (expr.type === 'StringLiteral') {
555
- out += expr.value
578
+ const textVal = getStringLiteralFromExpression(expr)
579
+ if (textVal !== undefined) {
580
+ out += textVal
556
581
  } else if (expr.type === 'Identifier') {
557
582
  out += `{{${expr.value}}}`
558
583
  } else if (expr.type === 'ObjectExpression') {
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
- * This function:
24
- * 1. Searches for a config file using findConfigFile()
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
- * This function is a wrapper around loadConfig that provides an interactive fallback.
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.
@@ -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;AAqBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,UAAU,CAAE,MAAM,GAAE,MAA4B,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA2C5G;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAE,MAAM,GAAE,MAA4B,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA8BvG;AAyBD;;;GAGG;AACH,wBAAsB,kBAAkB,IAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAyB3E"}
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"}
@@ -1,4 +1,4 @@
1
- import type { ObjectExpression } from '@swc/core';
1
+ import type { ObjectExpression, TemplateLiteral } from '@swc/core';
2
2
  /**
3
3
  * Finds and returns the full property node (KeyValueProperty) for the given
4
4
  * property name from an ObjectExpression.
@@ -15,6 +15,15 @@ import type { ObjectExpression } from '@swc/core';
15
15
  * @returns The matching KeyValueProperty node if found, otherwise undefined.
16
16
  */
17
17
  export declare function getObjectProperty(object: ObjectExpression, propName: string): import("@swc/types").KeyValueProperty | undefined;
18
+ /**
19
+ * Checks if the given template literal has no interpolation expressions
20
+ *
21
+ * @param literal - Template literal to check
22
+ * @returns Boolean true if the literal has no expressions and can be parsed (no invalid escapes), false otherwise
23
+ *
24
+ * @private
25
+ */
26
+ export declare function isSimpleTemplateLiteral(literal: TemplateLiteral): boolean;
18
27
  /**
19
28
  * Extracts string value from object property.
20
29
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAEjD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,qDAU5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAWrH"}
1
+ {"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAElE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,qDAU5E;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAE1E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAYrH"}
@@ -1 +1 @@
1
- {"version":3,"file":"call-expression-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/call-expression-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6C,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,aAAa,CAAA;AACvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAG1D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,kBAAkB,CAAoB;IACvC,UAAU,cAAoB;gBAGnC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB;IAQxC;;;;;;;;;;;;;OAaG;IACH,oBAAoB,CAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,IAAI;IA4QxG;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,gBAAgB;IA0IxB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"call-expression-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/call-expression-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6C,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,aAAa,CAAA;AACvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAG1D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,kBAAkB,CAAoB;IACvC,UAAU,cAAoB;gBAGnC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,kBAAkB;IAQxC;;;;;;;;;;;;;OAaG;IACH,oBAAoB,CAAE,IAAI,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,GAAG,IAAI;IA8QxG;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,gBAAgB;IA0IxB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
@@ -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,UAAU,EAAE,gBAAgB,EAAY,MAAM,WAAW,CAAA;AACnF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAGvD,MAAM,WAAW,sBAAsB;IACrC,gDAAgD;IAChD,aAAa,CAAC,EAAE,UAAU,CAAC;IAE3B,qDAAqD;IACrD,kBAAkB,EAAE,MAAM,CAAC;IAE3B,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,8DAA8D;IAC9D,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAE/B,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAE/B,kHAAkH;IAClH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,yBAAyB,CAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,GAAG,IAAI,CAgMxH"}
1
+ {"version":3,"file":"jsx-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAgB,UAAU,EAAiB,gBAAgB,EAAY,MAAM,WAAW,CAAA;AAChH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAGvD,MAAM,WAAW,sBAAsB;IACrC,gDAAgD;IAChD,aAAa,CAAC,EAAE,UAAU,CAAC;IAE3B,qDAAqD;IACrD,kBAAkB,EAAE,MAAM,CAAC;IAE3B,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,8DAA8D;IAC9D,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAE/B,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAE/B,kHAAkH;IAClH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA4BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,yBAAyB,CAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,GAAG,IAAI,CA8LxH"}