i18next-cli 1.6.1 → 1.7.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,11 @@ 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.7.0](https://github.com/i18next/i18next-cli/compare/v1.6.1...v1.7.9) - 2025-10-06
9
+
10
+ - **Extractor (Comments):** Improved comment extraction for complex translation patterns with both context and plural options. Commented `t()` calls like `// t('options.option', { context: 'month', count: 1 })` now correctly generate all combinations of context and plural forms (e.g., `options.option_month_one`, `options.option_month_other`, `options.option_day_one`, etc.), providing comprehensive key extraction for dynamic scenarios where developers use commented hints to declare all possible runtime values. [#50](https://github.com/i18next/i18next-cli/issues/50)
11
+ - **Plugin System:** Added new `extractKeysFromExpression` and `extractContextFromExpression` plugin hooks for simplified custom key extraction. These pure, context-less functions allow plugins to parse specific expressions during AST traversal without handling pluralization, namespace resolution, or file writing manually. Perfect for TypeScript-specific syntax (like `satisfies` expressions), template literals with dynamic variables, or custom key generation patterns. Plugins can now focus on expression parsing logic while the core extractor handles the heavy lifting. [#49](https://github.com/i18next/i18next-cli/pull/49)
12
+
8
13
  ## [1.6.1](https://github.com/i18next/i18next-cli/compare/v1.6.0...v1.6.1) - 2025-10-05
9
14
 
10
15
  - **Extractor (Comments):** Fixed namespace scope resolution for `t()` calls in comments. Commented translation calls like `// t("Private")` now correctly inherit the namespace from the surrounding `useTranslation('access')` scope instead of defaulting to the default namespace, matching i18next-parser behavior. This ensures consistency between commented and actual translation calls within the same component scope. [#44](https://github.com/i18next/i18next-cli/issues/44)
package/README.md CHANGED
@@ -390,10 +390,12 @@ Create custom plugins to extend the capabilities of `i18next-cli`. The plugin sy
390
390
  - `setup`: Runs once when the CLI is initialized. Use it for any setup tasks.
391
391
  - `onLoad`: Runs for each file *before* it is parsed. You can use this to transform code (e.g., transpile a custom language to JavaScript).
392
392
  - `onVisitNode`: Runs for every node in the Abstract Syntax Tree (AST) of a parsed JavaScript/TypeScript file. This provides access to the full parsing context, including variable scope and TypeScript-specific syntax like `satisfies` and `as` operators.
393
+ - `extractKeysFromExpression`: Runs for specific expressions during AST traversal to extract additional translation keys. This is ideal for handling custom syntax patterns or complex key generation logic without managing pluralization manually.
394
+ - `extractContextFromExpression`: Runs for specific expressions to extract context values that can't be statically analyzed. Useful for dynamic context patterns or custom context resolution logic.
393
395
  - `onEnd`: Runs after all JS/TS files have been parsed but *before* the final keys are compared with existing translation files. This is the ideal hook for parsing non-JavaScript files (like `.html`, `.vue`, or `.svelte`) and adding their keys to the collection.
394
396
  - `afterSync`: Runs after the extractor has compared the found keys with your translation files and generated the final results. This is perfect for post-processing tasks, like generating a report of newly added keys.
395
397
 
396
- **Example Plugin (`my-custom-plugin.mjs`):**
398
+ **Basic Plugin Example:**
397
399
 
398
400
  ```typescript
399
401
  import { glob } from 'glob';
@@ -402,74 +404,134 @@ import { readFile, writeFile } from 'node:fs/promises';
402
404
  export const myCustomPlugin = () => ({
403
405
  name: 'my-custom-plugin',
404
406
 
405
- /**
406
- * Runs after the core extractor has finished but before comparison.
407
- * Ideal for adding keys from non-JS/TS files.
408
- */
409
- async onEnd(allKeys) {
410
- // Example: Parse HTML files for data-i18n attributes
411
- const htmlFiles = await glob('src/**/*.html');
412
- for (const file of htmlFiles) {
407
+ // Handle custom file formats
408
+ async onEnd(keys) {
409
+ // Extract keys from .vue files
410
+ const vueFiles = await glob('src/**/*.vue');
411
+ for (const file of vueFiles) {
413
412
  const content = await readFile(file, 'utf-8');
414
- const matches = content.match(/data-i18n="([^"]+)"/g) || [];
415
- for (const match of matches) {
416
- const key = match.replace(/data-i18n="([^"]+)"/, '$1');
417
- // Add the found key to the collection
418
- allKeys.set(`translation:${key}`, { key, ns: 'translation', defaultValue: key });
413
+ const keyMatches = content.matchAll(/\{\{\s*\$t\(['"]([^'"]+)['"]\)/g);
414
+ for (const match of keyMatches) {
415
+ keys.set(`translation:${match[1]}`, {
416
+ key: match[1],
417
+ defaultValue: match[1],
418
+ ns: 'translation'
419
+ });
419
420
  }
420
421
  }
421
- },
422
+ }
423
+ });
424
+ ```
422
425
 
423
- /**
424
- * Runs after the extractor has generated the final translation results.
425
- * Ideal for reporting or post-processing.
426
- */
427
- async afterSync(results, config) {
428
- const primaryLanguage = config.extract.primaryLanguage || config.locales[0];
429
- const newKeys = [];
430
-
431
- for (const result of results) {
432
- // Find the result for the primary language
433
- if (!result.path.includes(`/${primaryLanguage}/`)) continue;
434
-
435
- const newKeysFlat = Object.keys(result.newTranslations);
436
- const existingKeysFlat = Object.keys(result.existingTranslations);
426
+ **Advanced Plugin with Expression Parsing:**
427
+
428
+ ```typescript
429
+ export const advancedExtractionPlugin = () => ({
430
+ name: 'advanced-extraction-plugin',
431
+
432
+ // Extract keys from TypeScript satisfies expressions
433
+ extractKeysFromExpression: (expression, config, logger) => {
434
+ const keys = [];
435
+
436
+ // Handle template literals with variable substitutions
437
+ if (expression.type === 'TemplateLiteral') {
438
+ // Extract pattern: `user.${role}.permission`
439
+ const parts = expression.quasis.map(q => q.cooked);
440
+ const variables = expression.expressions.map(e =>
441
+ e.type === 'Identifier' ? e.value : 'dynamic'
442
+ );
437
443
 
438
- // Find keys that are in the new file but not the old one
439
- for (const key of newKeysFlat) {
440
- if (!existingKeysFlat.includes(key)) {
441
- newKeys.push({
442
- key: key,
443
- defaultValue: result.newTranslations[key],
444
- });
444
+ if (variables.includes('role')) {
445
+ // Generate keys for known roles
446
+ keys.push('user.admin.permission', 'user.manager.permission', 'user.employee.permission');
447
+ }
448
+ }
449
+
450
+ // Handle TypeScript satisfies expressions
451
+ if (expression.type === 'TsAsExpression' &&
452
+ expression.typeAnnotation?.type === 'TsUnionType') {
453
+ const unionTypes = expression.typeAnnotation.types;
454
+ for (const unionType of unionTypes) {
455
+ if (unionType.type === 'TsLiteralType' &&
456
+ unionType.literal?.type === 'StringLiteral') {
457
+ keys.push(`dynamic.${unionType.literal.value}.extracted`);
445
458
  }
446
459
  }
447
460
  }
448
-
449
- if (newKeys.length > 0) {
450
- console.log(`[My Plugin] Found ${newKeys.length} new keys!`);
451
- // Example: Write a report for your copywriter
452
- await writeFile('new-keys-report.json', JSON.stringify(newKeys, null, 2));
461
+
462
+ return keys;
463
+ },
464
+
465
+ // Extract context from conditional expressions
466
+ extractContextFromExpression: (expression, config, logger) => {
467
+ const contexts = [];
468
+
469
+ // Handle ternary operators: isAdmin ? 'admin' : 'user'
470
+ if (expression.type === 'ConditionalExpression') {
471
+ if (expression.consequent.type === 'StringLiteral') {
472
+ contexts.push(expression.consequent.value);
473
+ }
474
+ if (expression.alternate.type === 'StringLiteral') {
475
+ contexts.push(expression.alternate.value);
476
+ }
477
+ }
478
+
479
+ // Handle template literals: `${role}.${level}`
480
+ if (expression.type === 'TemplateLiteral') {
481
+ const parts = expression.expressions.map(expr =>
482
+ expr.type === 'Identifier' ? expr.value : 'unknown'
483
+ );
484
+ if (parts.length > 0) {
485
+ const joins = expression.quasis.map(quasi => quasi.cooked);
486
+ contexts.push(joins.reduce((acc, join, i) =>
487
+ acc + (join || '') + (parts[i] || ''), ''
488
+ ));
489
+ }
490
+ }
491
+
492
+ return contexts;
493
+ },
494
+
495
+ // Handle complex AST patterns
496
+ onVisitNode: (node, context) => {
497
+ // Custom extraction for specific component patterns
498
+ if (node.type === 'JSXElement' &&
499
+ node.opening.name.type === 'Identifier' &&
500
+ node.opening.name.value === 'CustomTransComponent') {
501
+
502
+ const keyAttr = node.opening.attributes?.find(attr =>
503
+ attr.type === 'JSXAttribute' &&
504
+ attr.name.value === 'translationKey'
505
+ );
506
+
507
+ if (keyAttr?.value?.type === 'StringLiteral') {
508
+ context.addKey({
509
+ key: keyAttr.value.value,
510
+ defaultValue: 'Custom component translation',
511
+ ns: 'components'
512
+ });
513
+ }
453
514
  }
454
515
  }
455
516
  });
456
517
  ```
457
518
 
458
- **Configuration (`i18next.config.ts`):**
519
+ **Configuration:**
459
520
 
460
521
  ```typescript
461
522
  import { defineConfig } from 'i18next-cli';
462
- import { myCustomPlugin } from './my-custom-plugin.mjs';
523
+ import { myCustomPlugin, advancedExtractionPlugin } from './my-plugins.mjs';
463
524
 
464
525
  export default defineConfig({
465
526
  locales: ['en', 'de'],
466
527
  extract: {
467
- input: ['src/**/*.{ts,tsx}'],
468
- output: 'locales/{{language}}/{{namespace}}.json',
528
+ input: ['src/**/*.{ts,tsx,vue}'],
529
+ output: 'locales/{{language}}/{{namespace}}.json'
469
530
  },
470
531
  plugins: [
471
532
  myCustomPlugin(),
472
- ],
533
+ advancedExtractionPlugin()
534
+ ]
473
535
  });
474
536
  ```
475
537
 
package/dist/cjs/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var e=require("commander"),t=require("chokidar"),n=require("glob"),o=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.6.1"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").option("--dry-run","Run the extractor without writing any files to disk.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a,{isWatchMode:e.watch,isDryRun:e.dryRun});e.ci&&t&&(console.error(o.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(o.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${o.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let n=await i.loadConfig();if(!n){console.log(o.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),n=e}await g.runStatus(n,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const o=await i.ensureConfig(),a=()=>c.runTypesGenerator(o);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(o.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await l.runMigrator(e)}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const r=async()=>{let e=await i.loadConfig();if(!e){console.log(o.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),e=t}await d.runLinter(e)};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await i.loadConfig();if(e?.extract?.input){t.watch(await n.glob(e.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
2
+ "use strict";var e=require("commander"),t=require("chokidar"),n=require("glob"),o=require("chalk"),i=require("./config.js"),a=require("./heuristic-config.js"),r=require("./extractor/core/extractor.js");require("node:path"),require("node:fs/promises"),require("jiti");var c=require("./types-generator.js"),s=require("./syncer.js"),l=require("./migrator.js"),u=require("./init.js"),d=require("./linter.js"),g=require("./status.js"),p=require("./locize.js");const f=new e.Command;f.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.7.0"),f.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").option("--dry-run","Run the extractor without writing any files to disk.").action(async e=>{const a=await i.ensureConfig(),c=async()=>{const t=await r.runExtractor(a,{isWatchMode:e.watch,isDryRun:e.dryRun});e.ci&&t&&(console.error(o.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(o.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${o.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),c()})}}),f.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(e,t)=>{let n=await i.loadConfig();if(!n){console.log(o.blue("No config file found. Attempting to detect project structure..."));const e=await a.detectConfig();e||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),n=e}await g.runStatus(n,{detail:e,namespace:t.namespace})}),f.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async e=>{const o=await i.ensureConfig(),a=()=>c.runTypesGenerator(o);if(await a(),e.watch){console.log("\nWatching for changes...");t.watch(await n.glob(o.types?.input||[]),{persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),a()})}}),f.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const e=await i.ensureConfig();await s.runSyncer(e)}),f.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async e=>{await l.runMigrator(e)}),f.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(u.runInit),f.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async e=>{const r=async()=>{let e=await i.loadConfig();if(!e){console.log(o.blue("No config file found. Attempting to detect project structure..."));const t=await a.detectConfig();t||(console.error(o.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${o.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(o.green("Project structure detected successfully!")),e=t}await d.runLinter(e)};if(await r(),e.watch){console.log("\nWatching for changes...");const e=await i.loadConfig();if(e?.extract?.input){t.watch(await n.glob(e.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",e=>{console.log(`\nFile changed: ${e}`),r()})}}}),f.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeSync(t,e)}),f.command("locize-download").description("Download all translations from your locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeDownload(t,e)}),f.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async e=>{const t=await i.ensureConfig();await p.runLocizeMigrate(t,e)}),f.parse(process.argv);
@@ -1 +1 @@
1
- "use strict";var e=require("ora"),t=require("chalk"),a=require("@swc/core"),r=require("node:fs/promises"),n=require("node:path"),o=require("./key-finder.js"),s=require("./translation-manager.js"),i=require("../../utils/validation.js"),c=require("../parsers/comment-parser.js"),l=require("../../utils/logger.js"),u=require("../../utils/file-utils.js"),g=require("../../utils/funnel-msg-tracker.js");exports.extract=async function(e){e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(t=>t!==e?.extract?.primaryLanguage),e.extract.functions||=["t","*.t"],e.extract.transComponents||=["Trans"];const{allKeys:t,objectKeys:a}=await o.findKeys(e);return s.getTranslations(t,a,e)},exports.processFile=async function(e,t,n,o,s,u=new l.ConsoleLogger){try{let i=await r.readFile(e,"utf-8");for(const a of t)try{const t=await(a.onLoad?.(i,e));void 0!==t&&(i=t)}catch(e){u.warn(`Plugin ${a.name} onLoad failed:`,e)}const l=await a.parse(i,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0});o.getVarFromScope=n.getVarFromScope.bind(n),n.visit(l),c.extractKeysFromComments(i,o,s,n.getVarFromScope.bind(n))}catch(t){throw new i.ExtractorError("Failed to process file",e,t)}},exports.runExtractor=async function(a,{isWatchMode:c=!1,isDryRun:d=!1}={},p=new l.ConsoleLogger){a.extract.primaryLanguage||=a.locales[0]||"en",a.extract.secondaryLanguages||=a.locales.filter(e=>e!==a?.extract?.primaryLanguage),a.extract.functions||=["t","*.t"],a.extract.transComponents||=["Trans"],i.validateExtractorConfig(a);const y=e("Running i18next key extractor...\n").start();try{const{allKeys:e,objectKeys:i}=await o.findKeys(a,p);y.text=`Found ${e.size} unique keys. Updating translation files...`;const c=await s.getTranslations(e,i,a);let l=!1;for(const e of c)if(e.updated&&(l=!0,!d)){const o=u.serializeTranslationFile(e.newTranslations,a.extract.outputFormat,a.extract.indentation);await r.mkdir(n.dirname(e.path),{recursive:!0}),await r.writeFile(e.path,o),p.info(t.green(`Updated: ${e.path}`))}if((a.plugins||[]).length>0){y.text="Running post-extraction plugins...";for(const e of a.plugins||[])await(e.afterSync?.(c,a))}return y.succeed(t.bold("Extraction complete!")),l&&await async function(){if(!await g.shouldShowFunnel("extract"))return;return console.log(t.yellow.bold("\n💡 Tip: Tired of running the extractor manually?")),console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,'),console.log(" where keys are created and translated automatically as you code."),console.log(` Learn more: ${t.cyan("https://www.locize.com/blog/i18next-savemissing-ai-automation")}`),console.log(` Watch the video: ${t.cyan("https://youtu.be/joPsZghT3wM")}`),g.recordFunnelShown("extract")}(),l}catch(e){throw y.fail(t.red("Extraction failed.")),e}};
1
+ "use strict";var e=require("ora"),t=require("chalk"),a=require("@swc/core"),r=require("node:fs/promises"),n=require("node:path"),o=require("./key-finder.js"),s=require("./translation-manager.js"),i=require("../../utils/validation.js"),c=require("../parsers/comment-parser.js"),l=require("../../utils/logger.js"),u=require("../../utils/file-utils.js"),g=require("../../utils/funnel-msg-tracker.js");exports.extract=async function(e){e.extract.primaryLanguage||=e.locales[0]||"en",e.extract.secondaryLanguages||=e.locales.filter(t=>t!==e?.extract?.primaryLanguage),e.extract.functions||=["t","*.t"],e.extract.transComponents||=["Trans"];const{allKeys:t,objectKeys:a}=await o.findKeys(e);return s.getTranslations(t,a,e)},exports.processFile=async function(e,t,n,o,s,u=new l.ConsoleLogger){try{let i=await r.readFile(e,"utf-8");for(const a of t)try{const t=await(a.onLoad?.(i,e));void 0!==t&&(i=t)}catch(e){u.warn(`Plugin ${a.name} onLoad failed:`,e)}const l=await a.parse(i,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0});o.getVarFromScope=n.getVarFromScope.bind(n),n.visit(l),c.extractKeysFromComments(i,o,s,n.getVarFromScope.bind(n))}catch(t){throw new i.ExtractorError("Failed to process file",e,t)}},exports.runExtractor=async function(a,{isWatchMode:c=!1,isDryRun:d=!1}={},p=new l.ConsoleLogger){a.extract.primaryLanguage||=a.locales[0]||"en",a.extract.secondaryLanguages||=a.locales.filter(e=>e!==a?.extract?.primaryLanguage),a.extract.functions||=["t","*.t"],a.extract.transComponents||=["Trans"],i.validateExtractorConfig(a);const y=a.plugins||[],f=e("Running i18next key extractor...\n").start();try{const{allKeys:e,objectKeys:i}=await o.findKeys(a,p);f.text=`Found ${e.size} unique keys. Updating translation files...`;const c=await s.getTranslations(e,i,a);let l=!1;for(const e of c)if(e.updated&&(l=!0,!d)){const o=u.serializeTranslationFile(e.newTranslations,a.extract.outputFormat,a.extract.indentation);await r.mkdir(n.dirname(e.path),{recursive:!0}),await r.writeFile(e.path,o),p.info(t.green(`Updated: ${e.path}`))}if(y.length>0){f.text="Running post-extraction plugins...";for(const e of y)await(e.afterSync?.(c,a))}return f.succeed(t.bold("Extraction complete!")),l&&await async function(){if(!await g.shouldShowFunnel("extract"))return;return console.log(t.yellow.bold("\n💡 Tip: Tired of running the extractor manually?")),console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,'),console.log(" where keys are created and translated automatically as you code."),console.log(` Learn more: ${t.cyan("https://www.locize.com/blog/i18next-savemissing-ai-automation")}`),console.log(` Watch the video: ${t.cyan("https://youtu.be/joPsZghT3wM")}`),g.recordFunnelShown("extract")}(),l}catch(e){throw f.fail(t.red("Extraction failed.")),e}};
@@ -1 +1 @@
1
- "use strict";var e=require("glob"),r=require("./extractor.js"),o=require("../../utils/logger.js"),t=require("../plugin-manager.js"),i=require("../parsers/ast-visitors.js");exports.findKeys=async function(n,s=new o.ConsoleLogger){const{plugins:a,...c}=n,g=a||[],u=await async function(r){const o=["node_modules/**"],t=Array.isArray(r.extract.ignore)?r.extract.ignore:r.extract.ignore?[r.extract.ignore]:[];return await e.glob(r.extract.input,{ignore:[...o,...t],cwd:process.cwd()})}(n),l=new Map,d=t.createPluginContext(l,g,c,s),f={onBeforeVisitNode:e=>{for(const r of g)try{r.onVisitNode?.(e,d)}catch(e){s.warn(`Plugin ${r.name} onVisitNode failed:`,e)}}},w=new i.ASTVisitors(c,d,s,f);d.getVarFromScope=w.getVarFromScope.bind(w),await t.initializePlugins(g);for(const e of u)await r.processFile(e,g,w,d,c,s);for(const e of g)await(e.onEnd?.(l));return{allKeys:l,objectKeys:w.objectKeys}};
1
+ "use strict";var e=require("glob"),r=require("./extractor.js"),t=require("../../utils/logger.js"),o=require("../plugin-manager.js"),n=require("../parsers/ast-visitors.js");exports.findKeys=async function(i,s=new t.ConsoleLogger){const{plugins:a,...c}=i,l=a||[],u=await async function(r){const t=["node_modules/**"],o=Array.isArray(r.extract.ignore)?r.extract.ignore:r.extract.ignore?[r.extract.ignore]:[];return await e.glob(r.extract.input,{ignore:[...t,...o],cwd:process.cwd()})}(i),g=new Map,x=o.createPluginContext(g,l,c,s),p={onBeforeVisitNode:e=>{for(const r of l)try{r.onVisitNode?.(e,x)}catch(e){s.warn(`Plugin ${r.name} onVisitNode failed:`,e)}},resolvePossibleKeyStringValues:e=>l.flatMap(r=>{try{return r.extractKeysFromExpression?.(e,i,s)??[]}catch(e){return s.warn(`Plugin ${r.name} extractKeysFromExpression failed:`,e),[]}}),resolvePossibleContextStringValues:e=>l.flatMap(r=>{try{return r.extractContextFromExpression?.(e,i,s)??[]}catch(e){return s.warn(`Plugin ${r.name} extractContextFromExpression failed:`,e),[]}})},f=new n.ASTVisitors(c,x,s,p);x.getVarFromScope=f.getVarFromScope.bind(f),await o.initializePlugins(l);for(const e of u)await r.processFile(e,l,f,x,c,s);for(const e of l)await(e.onEnd?.(g));return{allKeys:g,objectKeys:f.objectKeys}};
@@ -1 +1 @@
1
- "use strict";var e=require("./jsx-parser.js"),t=require("./ast-utils.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];hooks;objectKeys=new Set;scope=new Map;constructor(e,t,r,n){this.pluginContext=t,this.config=e,this.logger=r,this.hooks={onBeforeVisitNode:n?.onBeforeVisitNode,onAfterVisitNode:n?.onAfterVisitNode}}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),this.hooks.onBeforeVisitNode?.(e),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}this.hooks.onAfterVisitNode?.(e);for(const t in e){if("span"===t)continue;const r=e[t];if(Array.isArray(r))for(const e of r)e&&"object"==typeof e&&this.walk(e);else r&&"object"==typeof r&&this.walk(r)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e)){return this.scopeStack[t].get(e)}const t=this.scope.get(e);if(t)return t}handleVariableDeclarator(e){const t=e.init;if(!t)return;const r="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!r)return;const n=r.callee;if("Identifier"===n.type){const t=this.getUseTranslationConfig(n.value);if(t)return this.handleUseTranslationDeclarator(e,r,t),void this.handleUseTranslationForComments(e,r,t)}"MemberExpression"===n.type&&"Identifier"===n.property.type&&"getFixedT"===n.property.value&&this.handleGetFixedTDeclarator(e,r)}handleUseTranslationForComments(e,t,r){let n;if("Identifier"===e.id.type&&(n=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(n=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){n="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){n=t.value.value;break}}if(!n)return;const s=t.arguments?.[r.nsArg]?.expression,i=t.arguments?.[r.keyPrefixArg]?.expression;let a,o;if("StringLiteral"===s?.type?a=s.value:"ArrayExpression"===s?.type&&"StringLiteral"===s.elements[0]?.expression.type&&(a=s.elements[0].expression.value),"ObjectExpression"===i?.type){const e=i.properties.find(e=>"KeyValueProperty"===e.type&&"Identifier"===e.key.type&&"keyPrefix"===e.key.value);"KeyValueProperty"===e?.type&&"StringLiteral"===e.value.type&&(o=e.value.value)}(a||o)&&this.scope.set(n,{defaultNs:a,keyPrefix:o})}handleUseTranslationDeclarator(e,r,n){let s;if("Identifier"===e.id.type&&(s=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(s=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){s="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){s=t.value.value;break}}if(!s)return;const i=r.arguments?.[n.nsArg]?.expression;let a;"StringLiteral"===i?.type?a=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(a=i.elements[0].expression.value);const o=r.arguments?.[n.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t.getObjectPropValue(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(s,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const r=e.id.value,n=t.arguments,s=n[1]?.expression,i=n[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===i?.type?i.value:void 0;(a||o)&&this.setVarInScope(r,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const r=this.getFunctionName(e.callee);if(!r)return;const n=this.getVarFromScope(r),s=this.config.extract.functions||["t","*.t"];let i=void 0!==n;if(!i)for(const e of s)if(e.startsWith("*.")){if(r.endsWith(e.substring(1))){i=!0;break}}else if(e===r){i=!0;break}if(!i||0===e.arguments.length)return;const{keysToProcess:a,isSelectorAPI:o}=this.handleCallExpressionArgument(e,0);if(0===a.length)return;let l=!1;const u=this.config.extract.pluralSeparator??"_";for(let e=0;e<a.length;e++)a[e].endsWith(`${u}ordinal`)&&(l=!0,a[e]=a[e].slice(0,-8));let p,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(p=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const f=c?t.getObjectPropValue(c,"defaultValue"):void 0,y="string"==typeof f?f:p;for(let e=0;e<a.length;e++){let r,s=a[e];if(c){const e=t.getObjectPropValue(c,"ns");"string"==typeof e&&(r=e)}const i=this.config.extract.nsSeparator??":";if(!r&&i&&s.includes(i)){const e=s.split(i);r=e.shift(),s=e.join(i)}!r&&n?.defaultNs&&(r=n.defaultNs),r||(r=this.config.extract.defaultNS);let u=s;if(n?.keyPrefix){const e=this.config.extract.keySeparator??".";u=`${n.keyPrefix}${e}${s}`}const p=e===a.length-1&&y||s;if(c){const e=t.getObjectProperty(c,"context"),n=[];if("StringLiteral"===e?.value?.type||"NumericLiteral"===e?.value.type||"BooleanLiteral"===e?.value.type){const t=`${e.value.value}`,s=this.config.extract.contextSeparator??"_";""!==t&&n.push({key:`${u}${s}${t}`,ns:r,defaultValue:p})}else if(e?.value){const t=this.resolvePossibleStringValues(e.value),s=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{n.push({key:`${u}${s}${e}`,ns:r,defaultValue:p})}),n.push({key:u,ns:r,defaultValue:p}))}const s=void 0!==t.getObjectPropValue(c,"count"),i=!0===t.getObjectPropValue(c,"ordinal");if(s||l){if(n.length>0)for(const{key:e,ns:t}of n)this.handlePluralKeys(e,t,c,i||l);else this.handlePluralKeys(u,r,c,i||l);continue}if(n.length>0){n.forEach(this.pluginContext.addKey);continue}!0===t.getObjectPropValue(c,"returnObjects")&&this.objectKeys.add(u)}o&&this.objectKeys.add(u),this.pluginContext.addKey({key:u,ns:r,defaultValue:p})}}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.resolvePossibleStringValues(e.expression));else n.push(...this.resolvePossibleStringValues(r));return{keysToProcess:n.filter(e=>!!e),isSelectorAPI:s}}handlePluralKeys(e,r,n,s){try{const i=s?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t.getObjectPropValue(n,"defaultValue"),u=t.getObjectPropValue(n,`defaultValue${o}other`),p=t.getObjectPropValue(n,`defaultValue${o}ordinal${o}other`);for(const i of a){const a=s?`defaultValue${o}ordinal${o}${i}`:`defaultValue${o}${i}`,c=t.getObjectPropValue(n,a);let f;f="string"==typeof c?c:"one"===i&&"string"==typeof l?l:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof l?l:e:u;const y=s?`${e}${o}ordinal${o}${i}`:`${e}${o}${i}`;this.pluginContext.addKey({key:y,ns:r,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(s){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=t.getObjectPropValue(n,"defaultValue");this.pluginContext.addKey({key:e,ns:r,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,r){try{const n=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,s=this.config.extract.pluralSeparator??"_";for(const i of n)this.pluginContext.addKey({key:`${e}${s}${i}`,ns:r,defaultValue:t,hasCount:!0})}catch(n){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:r,defaultValue:t})}}handleJSXElement(t){const r=this.getElementName(t);if(r&&(this.config.extract.transComponents||["Trans"]).includes(r)){const r=e.extractFromTransComponent(t,this.config),n=[];if(r){if(r.keyExpression){const e=this.resolvePossibleStringValues(r.keyExpression);n.push(...e)}else n.push(r.serializedChildren);let e;const{contextExpression:s,optionsNode:i,defaultValue:a,hasCount:o,isOrdinal:l,serializedChildren:u}=r;if(r.ns){const{ns:t}=r;e=n.map(e=>({key:e,ns:t,defaultValue:a||u,hasCount:o,isOrdinal:l}))}else{e=n.map(e=>{const t=this.config.extract.nsSeparator??":";let r;if(t&&e.includes(t)){let n;[r,...n]=e.split(t),e=n.join(t)}return{key:e,ns:r,defaultValue:a||u,hasCount:o,isOrdinal:l}});const r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===r?.type&&"JSXExpressionContainer"===r.value?.type&&"Identifier"===r.value.expression.type){const t=r.value.expression.value,n=this.getVarFromScope(t);n?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=n.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),s&&o){const r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),n=!!r,a=this.resolvePossibleStringValues(s),o=this.config.extract.contextSeparator??"_";if(a.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,n,i));for(const t of a)for(const r of e){const e=`${r.key}${o}${t}`;this.generatePluralKeysForTrans(e,r.defaultValue,r.ns,n,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,n,i))}else if(s){const t=this.resolvePossibleStringValues(s),r=this.config.extract.contextSeparator??"_";if(t.length>0){for(const n of t)for(const{key:t,ns:s,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${r}${n}`,ns:s,defaultValue:i});"StringLiteral"!==s.type&&e.forEach(this.pluginContext.addKey)}}else if(o){const r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),n=!!r;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,n,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,r,n,s,i){try{const a=s?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t.getObjectPropValue(i,`defaultValue${l}other`),p=t.getObjectPropValue(i,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=s?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`,c=i?t.getObjectPropValue(i,o):void 0;let f;f="string"==typeof c?c:"one"===a&&"string"==typeof r?r:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof r?r:e:u;const y=s?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:y,ns:n,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:n,defaultValue:r})}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const r=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&r.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&r.unshift(t.value),r.join(".")}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let 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}resolvePossibleStringValues(e,t=!1){if("StringLiteral"===e.type)return e.value||t?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent,t),...this.resolvePossibleStringValues(e.alternate,t)]}return"Identifier"===e.type&&"undefined"===e.value?[]:"TemplateLiteral"===e.type?this.resolvePossibleStringValuesFromTemplateString(e):"NumericLiteral"===e.type||"BooleanLiteral"===e.type?[`${e.value}`]:[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...r]=e.quasis;return e.expressions.reduce((e,t,n)=>e.flatMap(e=>{const s=r[n]?.cooked??"";return this.resolvePossibleStringValues(t,!0).map(t=>`${e}${t}${s}`)}),[t.cooked??""])}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const r of t){if("string"==typeof r&&r===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof r&&r.name===e)return{name:r.name,nsArg:r.nsArg??0,keyPrefixArg:r.keyPrefixArg??1}}}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("./jsx-parser.js"),t=require("./ast-utils.js");exports.ASTVisitors=class{pluginContext;config;logger;scopeStack=[];hooks;objectKeys=new Set;scope=new Map;constructor(e,t,r,s){this.pluginContext=t,this.config=e,this.logger=r,this.hooks={onBeforeVisitNode:s?.onBeforeVisitNode,onAfterVisitNode:s?.onAfterVisitNode,resolvePossibleKeyStringValues:s?.resolvePossibleKeyStringValues,resolvePossibleContextStringValues:s?.resolvePossibleContextStringValues}}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),this.hooks.onBeforeVisitNode?.(e),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}this.hooks.onAfterVisitNode?.(e);for(const t in e){if("span"===t)continue;const r=e[t];if(Array.isArray(r))for(const e of r)e&&"object"==typeof e&&this.walk(e);else r&&"object"==typeof r&&this.walk(r)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e)){return this.scopeStack[t].get(e)}const t=this.scope.get(e);if(t)return t}handleVariableDeclarator(e){const t=e.init;if(!t)return;const r="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!r)return;const s=r.callee;if("Identifier"===s.type){const t=this.getUseTranslationConfig(s.value);if(t)return this.handleUseTranslationDeclarator(e,r,t),void this.handleUseTranslationForComments(e,r,t)}"MemberExpression"===s.type&&"Identifier"===s.property.type&&"getFixedT"===s.property.value&&this.handleGetFixedTDeclarator(e,r)}handleUseTranslationForComments(e,t,r){let s;if("Identifier"===e.id.type&&(s=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(s=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){s="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){s=t.value.value;break}}if(!s)return;const n=t.arguments?.[r.nsArg]?.expression,i=t.arguments?.[r.keyPrefixArg]?.expression;let o,a;if("StringLiteral"===n?.type?o=n.value:"ArrayExpression"===n?.type&&"StringLiteral"===n.elements[0]?.expression.type&&(o=n.elements[0].expression.value),"ObjectExpression"===i?.type){const e=i.properties.find(e=>"KeyValueProperty"===e.type&&"Identifier"===e.key.type&&"keyPrefix"===e.key.value);"KeyValueProperty"===e?.type&&"StringLiteral"===e.value.type&&(a=e.value.value)}(o||a)&&this.scope.set(s,{defaultNs:o,keyPrefix:a})}handleUseTranslationDeclarator(e,r,s){let n;if("Identifier"===e.id.type&&(n=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(n=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){n="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){n=t.value.value;break}}if(!n)return;const i=r.arguments?.[s.nsArg]?.expression;let o;"StringLiteral"===i?.type?o=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(o=i.elements[0].expression.value);const a=r.arguments?.[s.keyPrefixArg]?.expression;let l;if("ObjectExpression"===a?.type){const e=t.getObjectPropValue(a,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(n,{defaultNs:o,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const r=e.id.value,s=t.arguments,n=s[1]?.expression,i=s[2]?.expression,o="StringLiteral"===n?.type?n.value:void 0,a="StringLiteral"===i?.type?i.value:void 0;(o||a)&&this.setVarInScope(r,{defaultNs:o,keyPrefix:a})}handleCallExpression(e){const r=this.getFunctionName(e.callee);if(!r)return;const s=this.getVarFromScope(r),n=this.config.extract.functions||["t","*.t"];let i=void 0!==s;if(!i)for(const e of n)if(e.startsWith("*.")){if(r.endsWith(e.substring(1))){i=!0;break}}else if(e===r){i=!0;break}if(!i||0===e.arguments.length)return;const{keysToProcess:o,isSelectorAPI:a}=this.handleCallExpressionArgument(e,0);if(0===o.length)return;let l=!1;const u=this.config.extract.pluralSeparator??"_";for(let e=0;e<o.length;e++)o[e].endsWith(`${u}ordinal`)&&(l=!0,o[e]=o[e].slice(0,-8));let p,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(p=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const f=c?t.getObjectPropValue(c,"defaultValue"):void 0,y="string"==typeof f?f:p;for(let e=0;e<o.length;e++){let r,n=o[e];if(c){const e=t.getObjectPropValue(c,"ns");"string"==typeof e&&(r=e)}const i=this.config.extract.nsSeparator??":";if(!r&&i&&n.includes(i)){const e=n.split(i);r=e.shift(),n=e.join(i)}!r&&s?.defaultNs&&(r=s.defaultNs),r||(r=this.config.extract.defaultNS);let u=n;if(s?.keyPrefix){const e=this.config.extract.keySeparator??".";u=`${s.keyPrefix}${e}${n}`}const p=e===o.length-1&&y||n;if(c){const e=t.getObjectProperty(c,"context"),s=[];if("StringLiteral"===e?.value?.type||"NumericLiteral"===e?.value.type||"BooleanLiteral"===e?.value.type){const t=`${e.value.value}`,n=this.config.extract.contextSeparator??"_";""!==t&&s.push({key:`${u}${n}${t}`,ns:r,defaultValue:p})}else if(e?.value){const t=this.resolvePossibleContextStringValues(e.value),n=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{s.push({key:`${u}${n}${e}`,ns:r,defaultValue:p})}),s.push({key:u,ns:r,defaultValue:p}))}const n=void 0!==t.getObjectPropValue(c,"count"),i=!0===t.getObjectPropValue(c,"ordinal");if(n||l){if(s.length>0)for(const{key:e,ns:t}of s)this.handlePluralKeys(e,t,c,i||l);else this.handlePluralKeys(u,r,c,i||l);continue}if(s.length>0){s.forEach(this.pluginContext.addKey);continue}!0===t.getObjectPropValue(c,"returnObjects")&&this.objectKeys.add(u)}a&&this.objectKeys.add(u),this.pluginContext.addKey({key:u,ns:r,defaultValue:p})}}handleCallExpressionArgument(e,t){const r=e.arguments[t].expression,s=[];let n=!1;if("ArrowFunctionExpression"===r.type){const e=this.extractKeyFromSelector(r);e&&(s.push(e),n=!0)}else if("ArrayExpression"===r.type)for(const e of r.elements)e?.expression&&s.push(...this.resolvePossibleKeyStringValues(e.expression));else s.push(...this.resolvePossibleKeyStringValues(r));return{keysToProcess:s.filter(e=>!!e),isSelectorAPI:n}}handlePluralKeys(e,r,s,n){try{const i=n?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,a=this.config.extract.pluralSeparator??"_",l=t.getObjectPropValue(s,"defaultValue"),u=t.getObjectPropValue(s,`defaultValue${a}other`),p=t.getObjectPropValue(s,`defaultValue${a}ordinal${a}other`);for(const i of o){const o=n?`defaultValue${a}ordinal${a}${i}`:`defaultValue${a}${i}`,c=t.getObjectPropValue(s,o);let f;f="string"==typeof c?c:"one"===i&&"string"==typeof l?l:n&&"string"==typeof p?p:n||"string"!=typeof u?"string"==typeof l?l:e:u;const y=n?`${e}${a}ordinal${a}${i}`:`${e}${a}${i}`;this.pluginContext.addKey({key:y,ns:r,defaultValue:f,hasCount:!0,isOrdinal:n})}}catch(n){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=t.getObjectPropValue(s,"defaultValue");this.pluginContext.addKey({key:e,ns:r,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,r){try{const s=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,n=this.config.extract.pluralSeparator??"_";for(const i of s)this.pluginContext.addKey({key:`${e}${n}${i}`,ns:r,defaultValue:t,hasCount:!0})}catch(s){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:r,defaultValue:t})}}handleJSXElement(t){const r=this.getElementName(t);if(r&&(this.config.extract.transComponents||["Trans"]).includes(r)){const r=e.extractFromTransComponent(t,this.config),s=[];if(r){if(r.keyExpression){const e=this.resolvePossibleKeyStringValues(r.keyExpression);s.push(...e)}else s.push(r.serializedChildren);let e;const{contextExpression:n,optionsNode:i,defaultValue:o,hasCount:a,isOrdinal:l,serializedChildren:u}=r;if(r.ns){const{ns:t}=r;e=s.map(e=>({key:e,ns:t,defaultValue:o||u,hasCount:a,isOrdinal:l}))}else{e=s.map(e=>{const t=this.config.extract.nsSeparator??":";let r;if(t&&e.includes(t)){let s;[r,...s]=e.split(t),e=s.join(t)}return{key:e,ns:r,defaultValue:o||u,hasCount:a,isOrdinal:l}});const r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===r?.type&&"JSXExpressionContainer"===r.value?.type&&"Identifier"===r.value.expression.type){const t=r.value.expression.value,s=this.getVarFromScope(t);s?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=s.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),n&&a){const r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),s=!!r,o=this.resolvePossibleContextStringValues(n),a=this.config.extract.contextSeparator??"_";if(o.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,s,i));for(const t of o)for(const r of e){const e=`${r.key}${a}${t}`;this.generatePluralKeysForTrans(e,r.defaultValue,r.ns,s,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,s,i))}else if(n){const t=this.resolvePossibleContextStringValues(n),r=this.config.extract.contextSeparator??"_";if(t.length>0){for(const s of t)for(const{key:t,ns:n,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${r}${s}`,ns:n,defaultValue:i});"StringLiteral"!==n.type&&e.forEach(this.pluginContext.addKey)}}else if(a){const r=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),s=!!r;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,s,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,r,s,n,i){try{const o=n?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:o}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t.getObjectPropValue(i,`defaultValue${l}other`),p=t.getObjectPropValue(i,`defaultValue${l}ordinal${l}other`));for(const o of a){const a=n?`defaultValue${l}ordinal${l}${o}`:`defaultValue${l}${o}`,c=i?t.getObjectPropValue(i,a):void 0;let f;f="string"==typeof c?c:"one"===o&&"string"==typeof r?r:n&&"string"==typeof p?p:n||"string"!=typeof u?"string"==typeof r?r:e:u;const y=n?`${e}${l}ordinal${l}${o}`:`${e}${l}${o}`;this.pluginContext.addKey({key:y,ns:s,defaultValue:f,hasCount:!0,isOrdinal:n})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:s,defaultValue:r})}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const r=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&r.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&r.unshift(t.value),r.join(".")}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let r=t;const s=[];for(;"MemberExpression"===r.type;){const e=r.property;if("Identifier"===e.type)s.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;s.unshift(e.expression.value)}r=r.object}if(s.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return s.join(t)}return null}resolvePossibleContextStringValues(e){return[...this.hooks.resolvePossibleContextStringValues?.(e)??[],...this.resolvePossibleStringValuesFromExpression(e)]}resolvePossibleKeyStringValues(e){return[...this.hooks.resolvePossibleKeyStringValues?.(e)??[],...this.resolvePossibleStringValuesFromExpression(e)]}resolvePossibleStringValuesFromExpression(e,t=!1){if("StringLiteral"===e.type)return e.value||t?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValuesFromExpression(e.consequent,t),...this.resolvePossibleStringValuesFromExpression(e.alternate,t)]}return"Identifier"===e.type&&"undefined"===e.value?[]:"TemplateLiteral"===e.type?this.resolvePossibleStringValuesFromTemplateString(e):"NumericLiteral"===e.type||"BooleanLiteral"===e.type?[`${e.value}`]:[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...r]=e.quasis;return e.expressions.reduce((e,t,s)=>e.flatMap(e=>{const n=r[s]?.cooked??"";return this.resolvePossibleStringValuesFromExpression(t,!0).map(t=>`${e}${t}${n}`)}),[t.cooked??""])}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const r of t){if("string"==typeof r&&r===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof r&&r.name===e)return{name:r.name,nsArg:r.nsArg??0,keyPrefixArg:r.keyPrefixArg??1}}}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";function t(t){const e=/^\s*,\s*(['"])(.*?)\1/.exec(t);if(e)return e[2];const s=/^\s*,\s*\{[^}]*defaultValue\s*:\s*(['"])(.*?)\1/.exec(t);return s?s[2]:void 0}function e(t){const e=/^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(t);if(e)return e[2]}exports.extractKeysFromComments=function(s,n,c,o){const r=new RegExp("\\bt\\s*\\(\\s*(['\"])([^'\"]+)\\1","g"),u=function(t){const e=[],s=new Set,n=/\/\/(.*)|\/\*([\s\S]*?)\*\//g;let c;for(;null!==(c=n.exec(t));){const t=(c[1]??c[2]).trim();t&&!s.has(t)&&(s.add(t),e.push(t))}return e}(s);for(const s of u){let u;for(;null!==(u=r.exec(s));){let r,f=u[2];const l=s.slice(u.index+u[0].length),i=t(l);r=e(l);const a=c.extract.nsSeparator??":";if(!r&&a&&f.includes(a)){const t=f.split(a);r=t.shift(),f=t.join(a)}if(!r&&o){const t=o("t");t?.defaultNs&&(r=t.defaultNs)}r||(r=c.extract.defaultNS),n.addKey({key:f,ns:r,defaultValue:i??f})}}};
1
+ "use strict";function e(e,t,n,s,o){const c=o.extract.primaryLanguage||o.locales[0]||"en",a=new Intl.PluralRules(c),u=[0,1,2,3,5,100],l=new Set;for(const e of u)l.add(a.select(e));for(const o of l)s.addKey({key:`${e}_${o}`,ns:n,defaultValue:t})}function t(e,t,n,s,o,c){const a=c.extract.primaryLanguage||c.locales[0]||"en",u=new Intl.PluralRules(a),l=[0,1,2,3,5,100],r=new Set;for(const e of l)r.add(u.select(e));for(const c of r)o.addKey({key:`${e}_${s}_${c}`,ns:n,defaultValue:t})}function n(e){const t=/^\s*,\s*(['"])(.*?)\1/.exec(e);if(t)return t[2];const n=/^\s*,\s*\{[^}]*defaultValue\s*:\s*(['"])(.*?)\1/.exec(e);return n?n[2]:void 0}function s(e){const t=/^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}function o(e){const t=/^\s*,\s*\{[^}]*context\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}function c(e){const t=/^\s*,\s*\{[^}]*count\s*:\s*(\d+)/.exec(e);if(t)return parseInt(t[1],10)}exports.extractKeysFromComments=function(a,u,l,r){const f=new RegExp("\\bt\\s*\\(\\s*(['\"])([^'\"]+)\\1","g"),d=function(e){const t=[],n=new Set,s=/\/\/(.*)|\/\*([\s\S]*?)\*\//g;let o;for(;null!==(o=s.exec(e));){const e=(o[1]??o[2]).trim();e&&!n.has(e)&&(n.add(e),t.push(e))}return t}(a);for(const a of d){let d;for(;null!==(d=f.exec(a));){let f,i=d[2];const x=a.slice(d.index+d[0].length),y=n(x),g=o(x),p=c(x);f=s(x);const $=l.extract.nsSeparator??":";if(!f&&$&&i.includes($)){const e=i.split($);f=e.shift(),i=e.join($)}if(!f&&r){const e=r("t");e?.defaultNs&&(f=e.defaultNs)}f||(f=l.extract.defaultNS),g&&p?(e(i,y??i,f,u,l),t(i,y??i,f,g,u,l)):g?(u.addKey({key:i,ns:f,defaultValue:y??i}),u.addKey({key:`${i}_${g}`,ns:f,defaultValue:y??i})):p?e(i,y??i,f,u,l):u.addKey({key:i,ns:f,defaultValue:y??i})}}};
package/dist/esm/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as t}from"commander";import o from"chokidar";import{glob as e}from"glob";import n from"chalk";import{ensureConfig as i,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as g}from"./status.js";import{runLocizeSync as f,runLocizeDownload as u,runLocizeMigrate as h}from"./locize.js";const w=new t;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.6.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.").action(async t=>{const a=await i(),c=async()=>{const o=await r(a,{isWatchMode:t.watch,isDryRun:t.dryRun});t.ci&&o&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),t.watch){console.log("\nWatching for changes...");o.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(t,o)=>{let e=await a();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await g(e,{detail:t,namespace:o.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async t=>{const n=await i(),a=()=>s(n);if(await a(),t.watch){console.log("\nWatching for changes...");o.watch(await e(n.types?.input||[]),{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=await i();await l(t)}),w.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await m(t)}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async t=>{const i=async()=>{let t=await a();if(!t){console.log(n.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),t=o}await d(t)};if(await i(),t.watch){console.log("\nWatching for changes...");const t=await a();if(t?.extract?.input){o.watch(await e(t.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),i()})}}}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async t=>{const o=await i();await f(o,t)}),w.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const o=await i();await u(o,t)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const o=await i();await h(o,t)}),w.parse(process.argv);
2
+ import{Command as t}from"commander";import o from"chokidar";import{glob as e}from"glob";import n from"chalk";import{ensureConfig as i,loadConfig as a}from"./config.js";import{detectConfig as c}from"./heuristic-config.js";import{runExtractor as r}from"./extractor/core/extractor.js";import"node:path";import"node:fs/promises";import"jiti";import{runTypesGenerator as s}from"./types-generator.js";import{runSyncer as l}from"./syncer.js";import{runMigrator as m}from"./migrator.js";import{runInit as p}from"./init.js";import{runLinter as d}from"./linter.js";import{runStatus as g}from"./status.js";import{runLocizeSync as f,runLocizeDownload as u,runLocizeMigrate as h}from"./locize.js";const w=new t;w.name("i18next-cli").description("A unified, high-performance i18next CLI.").version("1.7.0"),w.command("extract").description("Extract translation keys from source files and update resource files.").option("-w, --watch","Watch for file changes and re-run the extractor.").option("--ci","Exit with a non-zero status code if any files are updated.").option("--dry-run","Run the extractor without writing any files to disk.").action(async t=>{const a=await i(),c=async()=>{const o=await r(a,{isWatchMode:t.watch,isDryRun:t.dryRun});t.ci&&o&&(console.error(n.red.bold("\n[CI Mode] Error: Translation files were updated. Please commit the changes.")),console.log(n.yellow("💡 Tip: Tired of committing JSON files? locize syncs your team automatically => https://www.locize.com/docs/getting-started")),console.log(` Learn more: ${n.cyan("npx i18next-cli locize-sync")}`),process.exit(1))};if(await c(),t.watch){console.log("\nWatching for changes...");o.watch(await e(a.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),c()})}}),w.command("status [locale]").description("Display translation status. Provide a locale for a detailed key-by-key view.").option("-n, --namespace <ns>","Filter the status report by a specific namespace").action(async(t,o)=>{let e=await a();if(!e){console.log(n.blue("No config file found. Attempting to detect project structure..."));const t=await c();t||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),e=t}await g(e,{detail:t,namespace:o.namespace})}),w.command("types").description("Generate TypeScript definitions from translation resource files.").option("-w, --watch","Watch for file changes and re-run the type generator.").action(async t=>{const n=await i(),a=()=>s(n);if(await a(),t.watch){console.log("\nWatching for changes...");o.watch(await e(n.types?.input||[]),{persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),a()})}}),w.command("sync").description("Synchronize secondary language files with the primary language file.").action(async()=>{const t=await i();await l(t)}),w.command("migrate-config [configPath]").description("Migrate a legacy i18next-parser.config.js to the new format.").action(async t=>{await m(t)}),w.command("init").description("Create a new i18next.config.ts/js file with an interactive setup wizard.").action(p),w.command("lint").description("Find potential issues like hardcoded strings in your codebase.").option("-w, --watch","Watch for file changes and re-run the linter.").action(async t=>{const i=async()=>{let t=await a();if(!t){console.log(n.blue("No config file found. Attempting to detect project structure..."));const o=await c();o||(console.error(n.red("Could not automatically detect your project structure.")),console.log(`Please create a config file first by running: ${n.cyan("npx i18next-cli init")}`),process.exit(1)),console.log(n.green("Project structure detected successfully!")),t=o}await d(t)};if(await i(),t.watch){console.log("\nWatching for changes...");const t=await a();if(t?.extract?.input){o.watch(await e(t.extract.input),{ignored:/node_modules/,persistent:!0}).on("change",t=>{console.log(`\nFile changed: ${t}`),i()})}}}),w.command("locize-sync").description("Synchronize local translations with your locize project.").option("--update-values","Update values of existing translations on locize.").option("--src-lng-only","Check for changes in source language only.").option("--compare-mtime","Compare modification times when syncing.").option("--dry-run","Run the command without making any changes.").action(async t=>{const o=await i();await f(o,t)}),w.command("locize-download").description("Download all translations from your locize project.").action(async t=>{const o=await i();await u(o,t)}),w.command("locize-migrate").description("Migrate local translation files to a new locize project.").action(async t=>{const o=await i();await h(o,t)}),w.parse(process.argv);
@@ -1 +1 @@
1
- import t from"ora";import a from"chalk";import{parse as o}from"@swc/core";import{mkdir as e,writeFile as r,readFile as n}from"node:fs/promises";import{dirname as i}from"node:path";import{findKeys as s}from"./key-finder.js";import{getTranslations as c}from"./translation-manager.js";import{validateExtractorConfig as l,ExtractorError as m}from"../../utils/validation.js";import{extractKeysFromComments as u}from"../parsers/comment-parser.js";import{ConsoleLogger as p}from"../../utils/logger.js";import{serializeTranslationFile as f}from"../../utils/file-utils.js";import{shouldShowFunnel as g,recordFunnelShown as d}from"../../utils/funnel-msg-tracker.js";async function y(o,{isWatchMode:n=!1,isDryRun:m=!1}={},u=new p){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(t=>t!==o?.extract?.primaryLanguage),o.extract.functions||=["t","*.t"],o.extract.transComponents||=["Trans"],l(o);const y=t("Running i18next key extractor...\n").start();try{const{allKeys:t,objectKeys:n}=await s(o,u);y.text=`Found ${t.size} unique keys. Updating translation files...`;const l=await c(t,n,o);let p=!1;for(const t of l)if(t.updated&&(p=!0,!m)){const n=f(t.newTranslations,o.extract.outputFormat,o.extract.indentation);await e(i(t.path),{recursive:!0}),await r(t.path,n),u.info(a.green(`Updated: ${t.path}`))}if((o.plugins||[]).length>0){y.text="Running post-extraction plugins...";for(const t of o.plugins||[])await(t.afterSync?.(l,o))}return y.succeed(a.bold("Extraction complete!")),p&&await async function(){if(!await g("extract"))return;return console.log(a.yellow.bold("\n💡 Tip: Tired of running the extractor manually?")),console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,'),console.log(" where keys are created and translated automatically as you code."),console.log(` Learn more: ${a.cyan("https://www.locize.com/blog/i18next-savemissing-ai-automation")}`),console.log(` Watch the video: ${a.cyan("https://youtu.be/joPsZghT3wM")}`),d("extract")}(),p}catch(t){throw y.fail(a.red("Extraction failed.")),t}}async function w(t,a,e,r,i,s=new p){try{let c=await n(t,"utf-8");for(const o of a)try{const a=await(o.onLoad?.(c,t));void 0!==a&&(c=a)}catch(t){s.warn(`Plugin ${o.name} onLoad failed:`,t)}const l=await o(c,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0});r.getVarFromScope=e.getVarFromScope.bind(e),e.visit(l),u(c,r,i,e.getVarFromScope.bind(e))}catch(a){throw new m("Failed to process file",t,a)}}async function x(t){t.extract.primaryLanguage||=t.locales[0]||"en",t.extract.secondaryLanguages||=t.locales.filter(a=>a!==t?.extract?.primaryLanguage),t.extract.functions||=["t","*.t"],t.extract.transComponents||=["Trans"];const{allKeys:a,objectKeys:o}=await s(t);return c(a,o,t)}export{x as extract,w as processFile,y as runExtractor};
1
+ import t from"ora";import a from"chalk";import{parse as o}from"@swc/core";import{mkdir as e,writeFile as r,readFile as n}from"node:fs/promises";import{dirname as i}from"node:path";import{findKeys as s}from"./key-finder.js";import{getTranslations as c}from"./translation-manager.js";import{validateExtractorConfig as l,ExtractorError as m}from"../../utils/validation.js";import{extractKeysFromComments as u}from"../parsers/comment-parser.js";import{ConsoleLogger as p}from"../../utils/logger.js";import{serializeTranslationFile as f}from"../../utils/file-utils.js";import{shouldShowFunnel as g,recordFunnelShown as d}from"../../utils/funnel-msg-tracker.js";async function y(o,{isWatchMode:n=!1,isDryRun:m=!1}={},u=new p){o.extract.primaryLanguage||=o.locales[0]||"en",o.extract.secondaryLanguages||=o.locales.filter(t=>t!==o?.extract?.primaryLanguage),o.extract.functions||=["t","*.t"],o.extract.transComponents||=["Trans"],l(o);const y=o.plugins||[],w=t("Running i18next key extractor...\n").start();try{const{allKeys:t,objectKeys:n}=await s(o,u);w.text=`Found ${t.size} unique keys. Updating translation files...`;const l=await c(t,n,o);let p=!1;for(const t of l)if(t.updated&&(p=!0,!m)){const n=f(t.newTranslations,o.extract.outputFormat,o.extract.indentation);await e(i(t.path),{recursive:!0}),await r(t.path,n),u.info(a.green(`Updated: ${t.path}`))}if(y.length>0){w.text="Running post-extraction plugins...";for(const t of y)await(t.afterSync?.(l,o))}return w.succeed(a.bold("Extraction complete!")),p&&await async function(){if(!await g("extract"))return;return console.log(a.yellow.bold("\n💡 Tip: Tired of running the extractor manually?")),console.log(' Discover a real-time "push" workflow with `saveMissing` and Locize AI,'),console.log(" where keys are created and translated automatically as you code."),console.log(` Learn more: ${a.cyan("https://www.locize.com/blog/i18next-savemissing-ai-automation")}`),console.log(` Watch the video: ${a.cyan("https://youtu.be/joPsZghT3wM")}`),d("extract")}(),p}catch(t){throw w.fail(a.red("Extraction failed.")),t}}async function w(t,a,e,r,i,s=new p){try{let c=await n(t,"utf-8");for(const o of a)try{const a=await(o.onLoad?.(c,t));void 0!==a&&(c=a)}catch(t){s.warn(`Plugin ${o.name} onLoad failed:`,t)}const l=await o(c,{syntax:"typescript",tsx:!0,decorators:!0,comments:!0});r.getVarFromScope=e.getVarFromScope.bind(e),e.visit(l),u(c,r,i,e.getVarFromScope.bind(e))}catch(a){throw new m("Failed to process file",t,a)}}async function x(t){t.extract.primaryLanguage||=t.locales[0]||"en",t.extract.secondaryLanguages||=t.locales.filter(a=>a!==t?.extract?.primaryLanguage),t.extract.functions||=["t","*.t"],t.extract.transComponents||=["Trans"];const{allKeys:a,objectKeys:o}=await s(t);return c(a,o,t)}export{x as extract,w as processFile,y as runExtractor};
@@ -1 +1 @@
1
- import{glob as o}from"glob";import{processFile as r}from"./extractor.js";import{ConsoleLogger as t}from"../../utils/logger.js";import{createPluginContext as e,initializePlugins as n}from"../plugin-manager.js";import{ASTVisitors as i}from"../parsers/ast-visitors.js";async function a(a,s=new t){const{plugins:c,...f}=a,m=c||[],g=await async function(r){const t=["node_modules/**"],e=Array.isArray(r.extract.ignore)?r.extract.ignore:r.extract.ignore?[r.extract.ignore]:[];return await o(r.extract.input,{ignore:[...t,...e],cwd:process.cwd()})}(a),p=new Map,w=e(p,m,f,s),d={onBeforeVisitNode:o=>{for(const r of m)try{r.onVisitNode?.(o,w)}catch(o){s.warn(`Plugin ${r.name} onVisitNode failed:`,o)}}},l=new i(f,w,s,d);w.getVarFromScope=l.getVarFromScope.bind(l),await n(m);for(const o of g)await r(o,m,l,w,f,s);for(const o of m)await(o.onEnd?.(p));return{allKeys:p,objectKeys:l.objectKeys}}export{a as findKeys};
1
+ import{glob as r}from"glob";import{processFile as t}from"./extractor.js";import{ConsoleLogger as e}from"../../utils/logger.js";import{createPluginContext as o,initializePlugins as n}from"../plugin-manager.js";import{ASTVisitors as a}from"../parsers/ast-visitors.js";async function s(s,i=new e){const{plugins:c,...l}=s,m=c||[],p=await async function(t){const e=["node_modules/**"],o=Array.isArray(t.extract.ignore)?t.extract.ignore:t.extract.ignore?[t.extract.ignore]:[];return await r(t.extract.input,{ignore:[...e,...o],cwd:process.cwd()})}(s),f=new Map,g=o(f,m,l,i),u={onBeforeVisitNode:r=>{for(const t of m)try{t.onVisitNode?.(r,g)}catch(r){i.warn(`Plugin ${t.name} onVisitNode failed:`,r)}},resolvePossibleKeyStringValues:r=>m.flatMap(t=>{try{return t.extractKeysFromExpression?.(r,s,i)??[]}catch(r){return i.warn(`Plugin ${t.name} extractKeysFromExpression failed:`,r),[]}}),resolvePossibleContextStringValues:r=>m.flatMap(t=>{try{return t.extractContextFromExpression?.(r,s,i)??[]}catch(r){return i.warn(`Plugin ${t.name} extractContextFromExpression failed:`,r),[]}})},x=new a(l,g,i,u);g.getVarFromScope=x.getVarFromScope.bind(x),await n(m);for(const r of p)await t(r,m,x,g,l,i);for(const r of m)await(r.onEnd?.(f));return{allKeys:f,objectKeys:x.objectKeys}}export{s as findKeys};
@@ -1 +1 @@
1
- import{extractFromTransComponent as e}from"./jsx-parser.js";import{getObjectPropValue as t,getObjectProperty as n}from"./ast-utils.js";class r{pluginContext;config;logger;scopeStack=[];hooks;objectKeys=new Set;scope=new Map;constructor(e,t,n,r){this.pluginContext=t,this.config=e,this.logger=n,this.hooks={onBeforeVisitNode:r?.onBeforeVisitNode,onAfterVisitNode:r?.onAfterVisitNode}}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),this.hooks.onBeforeVisitNode?.(e),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}this.hooks.onAfterVisitNode?.(e);for(const t in e){if("span"===t)continue;const n=e[t];if(Array.isArray(n))for(const e of n)e&&"object"==typeof e&&this.walk(e);else n&&"object"==typeof n&&this.walk(n)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e)){return this.scopeStack[t].get(e)}const t=this.scope.get(e);if(t)return t}handleVariableDeclarator(e){const t=e.init;if(!t)return;const n="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!n)return;const r=n.callee;if("Identifier"===r.type){const t=this.getUseTranslationConfig(r.value);if(t)return this.handleUseTranslationDeclarator(e,n,t),void this.handleUseTranslationForComments(e,n,t)}"MemberExpression"===r.type&&"Identifier"===r.property.type&&"getFixedT"===r.property.value&&this.handleGetFixedTDeclarator(e,n)}handleUseTranslationForComments(e,t,n){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){r="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const s=t.arguments?.[n.nsArg]?.expression,i=t.arguments?.[n.keyPrefixArg]?.expression;let a,o;if("StringLiteral"===s?.type?a=s.value:"ArrayExpression"===s?.type&&"StringLiteral"===s.elements[0]?.expression.type&&(a=s.elements[0].expression.value),"ObjectExpression"===i?.type){const e=i.properties.find(e=>"KeyValueProperty"===e.type&&"Identifier"===e.key.type&&"keyPrefix"===e.key.value);"KeyValueProperty"===e?.type&&"StringLiteral"===e.value.type&&(o=e.value.value)}(a||o)&&this.scope.set(r,{defaultNs:a,keyPrefix:o})}handleUseTranslationDeclarator(e,n,r){let s;if("Identifier"===e.id.type&&(s=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(s=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){s="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){s=t.value.value;break}}if(!s)return;const i=n.arguments?.[r.nsArg]?.expression;let a;"StringLiteral"===i?.type?a=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(a=i.elements[0].expression.value);const o=n.arguments?.[r.keyPrefixArg]?.expression;let l;if("ObjectExpression"===o?.type){const e=t(o,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(s,{defaultNs:a,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const n=e.id.value,r=t.arguments,s=r[1]?.expression,i=r[2]?.expression,a="StringLiteral"===s?.type?s.value:void 0,o="StringLiteral"===i?.type?i.value:void 0;(a||o)&&this.setVarInScope(n,{defaultNs:a,keyPrefix:o})}handleCallExpression(e){const r=this.getFunctionName(e.callee);if(!r)return;const s=this.getVarFromScope(r),i=this.config.extract.functions||["t","*.t"];let a=void 0!==s;if(!a)for(const e of i)if(e.startsWith("*.")){if(r.endsWith(e.substring(1))){a=!0;break}}else if(e===r){a=!0;break}if(!a||0===e.arguments.length)return;const{keysToProcess:o,isSelectorAPI:l}=this.handleCallExpressionArgument(e,0);if(0===o.length)return;let u=!1;const p=this.config.extract.pluralSeparator??"_";for(let e=0;e<o.length;e++)o[e].endsWith(`${p}ordinal`)&&(u=!0,o[e]=o[e].slice(0,-8));let f,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(f=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const y=c?t(c,"defaultValue"):void 0,d="string"==typeof y?y:f;for(let e=0;e<o.length;e++){let r,i=o[e];if(c){const e=t(c,"ns");"string"==typeof e&&(r=e)}const a=this.config.extract.nsSeparator??":";if(!r&&a&&i.includes(a)){const e=i.split(a);r=e.shift(),i=e.join(a)}!r&&s?.defaultNs&&(r=s.defaultNs),r||(r=this.config.extract.defaultNS);let p=i;if(s?.keyPrefix){const e=this.config.extract.keySeparator??".";p=`${s.keyPrefix}${e}${i}`}const f=e===o.length-1&&d||i;if(c){const e=n(c,"context"),s=[];if("StringLiteral"===e?.value?.type||"NumericLiteral"===e?.value.type||"BooleanLiteral"===e?.value.type){const t=`${e.value.value}`,n=this.config.extract.contextSeparator??"_";""!==t&&s.push({key:`${p}${n}${t}`,ns:r,defaultValue:f})}else if(e?.value){const t=this.resolvePossibleStringValues(e.value),n=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{s.push({key:`${p}${n}${e}`,ns:r,defaultValue:f})}),s.push({key:p,ns:r,defaultValue:f}))}const i=void 0!==t(c,"count"),a=!0===t(c,"ordinal");if(i||u){if(s.length>0)for(const{key:e,ns:t}of s)this.handlePluralKeys(e,t,c,a||u);else this.handlePluralKeys(p,r,c,a||u);continue}if(s.length>0){s.forEach(this.pluginContext.addKey);continue}!0===t(c,"returnObjects")&&this.objectKeys.add(p)}l&&this.objectKeys.add(p),this.pluginContext.addKey({key:p,ns:r,defaultValue:f})}}handleCallExpressionArgument(e,t){const n=e.arguments[t].expression,r=[];let s=!1;if("ArrowFunctionExpression"===n.type){const e=this.extractKeyFromSelector(n);e&&(r.push(e),s=!0)}else if("ArrayExpression"===n.type)for(const e of n.elements)e?.expression&&r.push(...this.resolvePossibleStringValues(e.expression));else r.push(...this.resolvePossibleStringValues(n));return{keysToProcess:r.filter(e=>!!e),isSelectorAPI:s}}handlePluralKeys(e,n,r,s){try{const i=s?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,o=this.config.extract.pluralSeparator??"_",l=t(r,"defaultValue"),u=t(r,`defaultValue${o}other`),p=t(r,`defaultValue${o}ordinal${o}other`);for(const i of a){const a=t(r,s?`defaultValue${o}ordinal${o}${i}`:`defaultValue${o}${i}`);let f;f="string"==typeof a?a:"one"===i&&"string"==typeof l?l:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof l?l:e:u;const c=s?`${e}${o}ordinal${o}${i}`:`${e}${o}${i}`;this.pluginContext.addKey({key:c,ns:n,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(s){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=t(r,"defaultValue");this.pluginContext.addKey({key:e,ns:n,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,n){try{const r=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,s=this.config.extract.pluralSeparator??"_";for(const i of r)this.pluginContext.addKey({key:`${e}${s}${i}`,ns:n,defaultValue:t,hasCount:!0})}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:n,defaultValue:t})}}handleJSXElement(t){const n=this.getElementName(t);if(n&&(this.config.extract.transComponents||["Trans"]).includes(n)){const n=e(t,this.config),r=[];if(n){if(n.keyExpression){const e=this.resolvePossibleStringValues(n.keyExpression);r.push(...e)}else r.push(n.serializedChildren);let e;const{contextExpression:s,optionsNode:i,defaultValue:a,hasCount:o,isOrdinal:l,serializedChildren:u}=n;if(n.ns){const{ns:t}=n;e=r.map(e=>({key:e,ns:t,defaultValue:a||u,hasCount:o,isOrdinal:l}))}else{e=r.map(e=>{const t=this.config.extract.nsSeparator??":";let n;if(t&&e.includes(t)){let r;[n,...r]=e.split(t),e=r.join(t)}return{key:e,ns:n,defaultValue:a||u,hasCount:o,isOrdinal:l}});const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===n?.type&&"JSXExpressionContainer"===n.value?.type&&"Identifier"===n.value.expression.type){const t=n.value.expression.value,r=this.getVarFromScope(t);r?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=r.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),s&&o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!n,a=this.resolvePossibleStringValues(s),o=this.config.extract.contextSeparator??"_";if(a.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i));for(const t of a)for(const n of e){const e=`${n.key}${o}${t}`;this.generatePluralKeysForTrans(e,n.defaultValue,n.ns,r,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i))}else if(s){const t=this.resolvePossibleStringValues(s),n=this.config.extract.contextSeparator??"_";if(t.length>0){for(const r of t)for(const{key:t,ns:s,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${n}${r}`,ns:s,defaultValue:i});"StringLiteral"!==s.type&&e.forEach(this.pluginContext.addKey)}}else if(o){const n=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),r=!!n;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,r,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,n,r,s,i){try{const a=s?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:a}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t(i,`defaultValue${l}other`),p=t(i,`defaultValue${l}ordinal${l}other`));for(const a of o){const o=i?t(i,s?`defaultValue${l}ordinal${l}${a}`:`defaultValue${l}${a}`):void 0;let f;f="string"==typeof o?o:"one"===a&&"string"==typeof n?n:s&&"string"==typeof p?p:s||"string"!=typeof u?"string"==typeof n?n:e:u;const c=s?`${e}${l}ordinal${l}${a}`:`${e}${l}${a}`;this.pluginContext.addKey({key:c,ns:r,defaultValue:f,hasCount:!0,isOrdinal:s})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:r,defaultValue:n})}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const n=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&n.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&n.unshift(t.value),n.join(".")}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let n=t;const r=[];for(;"MemberExpression"===n.type;){const e=n.property;if("Identifier"===e.type)r.unshift(e.value);else{if("Computed"!==e.type||"StringLiteral"!==e.expression.type)return null;r.unshift(e.expression.value)}n=n.object}if(r.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return r.join(t)}return null}resolvePossibleStringValues(e,t=!1){if("StringLiteral"===e.type)return e.value||t?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValues(e.consequent,t),...this.resolvePossibleStringValues(e.alternate,t)]}return"Identifier"===e.type&&"undefined"===e.value?[]:"TemplateLiteral"===e.type?this.resolvePossibleStringValuesFromTemplateString(e):"NumericLiteral"===e.type||"BooleanLiteral"===e.type?[`${e.value}`]:[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...n]=e.quasis;return e.expressions.reduce((e,t,r)=>e.flatMap(e=>{const s=n[r]?.cooked??"";return this.resolvePossibleStringValues(t,!0).map(t=>`${e}${t}${s}`)}),[t.cooked??""])}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const n of t){if("string"==typeof n&&n===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof n&&n.name===e)return{name:n.name,nsArg:n.nsArg??0,keyPrefixArg:n.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let n=e;for(;"MemberExpression"===n.type;){if("Identifier"!==n.property.type)return null;t.unshift(n.property.value),n=n.object}if("ThisExpression"===n.type)t.unshift("this");else{if("Identifier"!==n.type)return null;t.unshift(n.value)}return t.join(".")}return null}}export{r as ASTVisitors};
1
+ import{extractFromTransComponent as e}from"./jsx-parser.js";import{getObjectPropValue as t,getObjectProperty as s}from"./ast-utils.js";class n{pluginContext;config;logger;scopeStack=[];hooks;objectKeys=new Set;scope=new Map;constructor(e,t,s,n){this.pluginContext=t,this.config=e,this.logger=s,this.hooks={onBeforeVisitNode:n?.onBeforeVisitNode,onAfterVisitNode:n?.onAfterVisitNode,resolvePossibleKeyStringValues:n?.resolvePossibleKeyStringValues,resolvePossibleContextStringValues:n?.resolvePossibleContextStringValues}}visit(e){this.enterScope(),this.walk(e),this.exitScope()}walk(e){if(!e)return;let t=!1;switch("Function"!==e.type&&"ArrowFunctionExpression"!==e.type&&"FunctionExpression"!==e.type||(this.enterScope(),t=!0),this.hooks.onBeforeVisitNode?.(e),e.type){case"VariableDeclarator":this.handleVariableDeclarator(e);break;case"CallExpression":this.handleCallExpression(e);break;case"JSXElement":this.handleJSXElement(e)}this.hooks.onAfterVisitNode?.(e);for(const t in e){if("span"===t)continue;const s=e[t];if(Array.isArray(s))for(const e of s)e&&"object"==typeof e&&this.walk(e);else s&&"object"==typeof s&&this.walk(s)}t&&this.exitScope()}enterScope(){this.scopeStack.push(new Map)}exitScope(){this.scopeStack.pop()}setVarInScope(e,t){this.scopeStack.length>0&&this.scopeStack[this.scopeStack.length-1].set(e,t)}getVarFromScope(e){for(let t=this.scopeStack.length-1;t>=0;t--)if(this.scopeStack[t].has(e)){return this.scopeStack[t].get(e)}const t=this.scope.get(e);if(t)return t}handleVariableDeclarator(e){const t=e.init;if(!t)return;const s="AwaitExpression"===t.type&&"CallExpression"===t.argument.type?t.argument:"CallExpression"===t.type?t:null;if(!s)return;const n=s.callee;if("Identifier"===n.type){const t=this.getUseTranslationConfig(n.value);if(t)return this.handleUseTranslationDeclarator(e,s,t),void this.handleUseTranslationForComments(e,s,t)}"MemberExpression"===n.type&&"Identifier"===n.property.type&&"getFixedT"===n.property.value&&this.handleGetFixedTDeclarator(e,s)}handleUseTranslationForComments(e,t,s){let n;if("Identifier"===e.id.type&&(n=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(n=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){n="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){n=t.value.value;break}}if(!n)return;const r=t.arguments?.[s.nsArg]?.expression,i=t.arguments?.[s.keyPrefixArg]?.expression;let o,a;if("StringLiteral"===r?.type?o=r.value:"ArrayExpression"===r?.type&&"StringLiteral"===r.elements[0]?.expression.type&&(o=r.elements[0].expression.value),"ObjectExpression"===i?.type){const e=i.properties.find(e=>"KeyValueProperty"===e.type&&"Identifier"===e.key.type&&"keyPrefix"===e.key.value);"KeyValueProperty"===e?.type&&"StringLiteral"===e.value.type&&(a=e.value.value)}(o||a)&&this.scope.set(n,{defaultNs:o,keyPrefix:a})}handleUseTranslationDeclarator(e,s,n){let r;if("Identifier"===e.id.type&&(r=e.id.value),"ArrayPattern"===e.id.type){const t=e.id.elements[0];"Identifier"===t?.type&&(r=t.value)}if("ObjectPattern"===e.id.type)for(const t of e.id.properties){if("AssignmentPatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value){r="t";break}if("KeyValuePatternProperty"===t.type&&"Identifier"===t.key.type&&"t"===t.key.value&&"Identifier"===t.value.type){r=t.value.value;break}}if(!r)return;const i=s.arguments?.[n.nsArg]?.expression;let o;"StringLiteral"===i?.type?o=i.value:"ArrayExpression"===i?.type&&"StringLiteral"===i.elements[0]?.expression.type&&(o=i.elements[0].expression.value);const a=s.arguments?.[n.keyPrefixArg]?.expression;let l;if("ObjectExpression"===a?.type){const e=t(a,"keyPrefix");l="string"==typeof e?e:void 0}this.setVarInScope(r,{defaultNs:o,keyPrefix:l})}handleGetFixedTDeclarator(e,t){if("Identifier"!==e.id.type||!e.init||"CallExpression"!==e.init.type)return;const s=e.id.value,n=t.arguments,r=n[1]?.expression,i=n[2]?.expression,o="StringLiteral"===r?.type?r.value:void 0,a="StringLiteral"===i?.type?i.value:void 0;(o||a)&&this.setVarInScope(s,{defaultNs:o,keyPrefix:a})}handleCallExpression(e){const n=this.getFunctionName(e.callee);if(!n)return;const r=this.getVarFromScope(n),i=this.config.extract.functions||["t","*.t"];let o=void 0!==r;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===e.arguments.length)return;const{keysToProcess:a,isSelectorAPI:l}=this.handleCallExpressionArgument(e,0);if(0===a.length)return;let u=!1;const p=this.config.extract.pluralSeparator??"_";for(let e=0;e<a.length;e++)a[e].endsWith(`${p}ordinal`)&&(u=!0,a[e]=a[e].slice(0,-8));let f,c;if(e.arguments.length>1){const t=e.arguments[1].expression;"ObjectExpression"===t.type?c=t:"StringLiteral"===t.type&&(f=t.value)}if(e.arguments.length>2){const t=e.arguments[2].expression;"ObjectExpression"===t.type&&(c=t)}const y=c?t(c,"defaultValue"):void 0,g="string"==typeof y?y:f;for(let e=0;e<a.length;e++){let n,i=a[e];if(c){const e=t(c,"ns");"string"==typeof e&&(n=e)}const o=this.config.extract.nsSeparator??":";if(!n&&o&&i.includes(o)){const e=i.split(o);n=e.shift(),i=e.join(o)}!n&&r?.defaultNs&&(n=r.defaultNs),n||(n=this.config.extract.defaultNS);let p=i;if(r?.keyPrefix){const e=this.config.extract.keySeparator??".";p=`${r.keyPrefix}${e}${i}`}const f=e===a.length-1&&g||i;if(c){const e=s(c,"context"),r=[];if("StringLiteral"===e?.value?.type||"NumericLiteral"===e?.value.type||"BooleanLiteral"===e?.value.type){const t=`${e.value.value}`,s=this.config.extract.contextSeparator??"_";""!==t&&r.push({key:`${p}${s}${t}`,ns:n,defaultValue:f})}else if(e?.value){const t=this.resolvePossibleContextStringValues(e.value),s=this.config.extract.contextSeparator??"_";t.length>0&&(t.forEach(e=>{r.push({key:`${p}${s}${e}`,ns:n,defaultValue:f})}),r.push({key:p,ns:n,defaultValue:f}))}const i=void 0!==t(c,"count"),o=!0===t(c,"ordinal");if(i||u){if(r.length>0)for(const{key:e,ns:t}of r)this.handlePluralKeys(e,t,c,o||u);else this.handlePluralKeys(p,n,c,o||u);continue}if(r.length>0){r.forEach(this.pluginContext.addKey);continue}!0===t(c,"returnObjects")&&this.objectKeys.add(p)}l&&this.objectKeys.add(p),this.pluginContext.addKey({key:p,ns:n,defaultValue:f})}}handleCallExpressionArgument(e,t){const s=e.arguments[t].expression,n=[];let r=!1;if("ArrowFunctionExpression"===s.type){const e=this.extractKeyFromSelector(s);e&&(n.push(e),r=!0)}else if("ArrayExpression"===s.type)for(const e of s.elements)e?.expression&&n.push(...this.resolvePossibleKeyStringValues(e.expression));else n.push(...this.resolvePossibleKeyStringValues(s));return{keysToProcess:n.filter(e=>!!e),isSelectorAPI:r}}handlePluralKeys(e,s,n,r){try{const i=r?"ordinal":"cardinal",o=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:i}).resolvedOptions().pluralCategories,a=this.config.extract.pluralSeparator??"_",l=t(n,"defaultValue"),u=t(n,`defaultValue${a}other`),p=t(n,`defaultValue${a}ordinal${a}other`);for(const i of o){const o=t(n,r?`defaultValue${a}ordinal${a}${i}`:`defaultValue${a}${i}`);let f;f="string"==typeof o?o:"one"===i&&"string"==typeof l?l:r&&"string"==typeof p?p:r||"string"!=typeof u?"string"==typeof l?l:e:u;const c=r?`${e}${a}ordinal${a}${i}`:`${e}${a}${i}`;this.pluginContext.addKey({key:c,ns:s,defaultValue:f,hasCount:!0,isOrdinal:r})}}catch(r){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`);const i=t(n,"defaultValue");this.pluginContext.addKey({key:e,ns:s,defaultValue:"string"==typeof i?i:e})}}handleSimplePluralKeys(e,t,s){try{const n=new Intl.PluralRules(this.config.extract?.primaryLanguage).resolvedOptions().pluralCategories,r=this.config.extract.pluralSeparator??"_";for(const i of n)this.pluginContext.addKey({key:`${e}${r}${i}`,ns:s,defaultValue:t,hasCount:!0})}catch(n){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}".`),this.pluginContext.addKey({key:e,ns:s,defaultValue:t})}}handleJSXElement(t){const s=this.getElementName(t);if(s&&(this.config.extract.transComponents||["Trans"]).includes(s)){const s=e(t,this.config),n=[];if(s){if(s.keyExpression){const e=this.resolvePossibleKeyStringValues(s.keyExpression);n.push(...e)}else n.push(s.serializedChildren);let e;const{contextExpression:r,optionsNode:i,defaultValue:o,hasCount:a,isOrdinal:l,serializedChildren:u}=s;if(s.ns){const{ns:t}=s;e=n.map(e=>({key:e,ns:t,defaultValue:o||u,hasCount:a,isOrdinal:l}))}else{e=n.map(e=>{const t=this.config.extract.nsSeparator??":";let s;if(t&&e.includes(t)){let n;[s,...n]=e.split(t),e=n.join(t)}return{key:e,ns:s,defaultValue:o||u,hasCount:a,isOrdinal:l}});const s=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"t"===e.name.value);if("JSXAttribute"===s?.type&&"JSXExpressionContainer"===s.value?.type&&"Identifier"===s.value.expression.type){const t=s.value.expression.value,n=this.getVarFromScope(t);n?.defaultNs&&e.forEach(e=>{e.ns||(e.ns=n.defaultNs)})}}if(e.forEach(e=>{e.ns||(e.ns=this.config.extract.defaultNS)}),r&&a){const s=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),n=!!s,o=this.resolvePossibleContextStringValues(r),a=this.config.extract.contextSeparator??"_";if(o.length>0){e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,n,i));for(const t of o)for(const s of e){const e=`${s.key}${a}${t}`;this.generatePluralKeysForTrans(e,s.defaultValue,s.ns,n,i)}}else e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,n,i))}else if(r){const t=this.resolvePossibleContextStringValues(r),s=this.config.extract.contextSeparator??"_";if(t.length>0){for(const n of t)for(const{key:t,ns:r,defaultValue:i}of e)this.pluginContext.addKey({key:`${t}${s}${n}`,ns:r,defaultValue:i});"StringLiteral"!==r.type&&e.forEach(this.pluginContext.addKey)}}else if(a){const s=t.opening.attributes?.find(e=>"JSXAttribute"===e.type&&"Identifier"===e.name.type&&"ordinal"===e.name.value),n=!!s;e.forEach(e=>this.generatePluralKeysForTrans(e.key,e.defaultValue,e.ns,n,i))}else e.forEach(this.pluginContext.addKey)}}}generatePluralKeysForTrans(e,s,n,r,i){try{const o=r?"ordinal":"cardinal",a=new Intl.PluralRules(this.config.extract?.primaryLanguage,{type:o}).resolvedOptions().pluralCategories,l=this.config.extract.pluralSeparator??"_";let u,p;i&&(u=t(i,`defaultValue${l}other`),p=t(i,`defaultValue${l}ordinal${l}other`));for(const o of a){const a=i?t(i,r?`defaultValue${l}ordinal${l}${o}`:`defaultValue${l}${o}`):void 0;let f;f="string"==typeof a?a:"one"===o&&"string"==typeof s?s:r&&"string"==typeof p?p:r||"string"!=typeof u?"string"==typeof s?s:e:u;const c=r?`${e}${l}ordinal${l}${o}`:`${e}${l}${o}`;this.pluginContext.addKey({key:c,ns:n,defaultValue:f,hasCount:!0,isOrdinal:r})}}catch(t){this.logger.warn(`Could not determine plural rules for language "${this.config.extract?.primaryLanguage}". Falling back to simple key extraction.`),this.pluginContext.addKey({key:e,ns:n,defaultValue:s})}}getElementName(e){if("Identifier"===e.opening.name.type)return e.opening.name.value;if("JSXMemberExpression"===e.opening.name.type){let t=e.opening.name;const s=[];for(;"JSXMemberExpression"===t.type;)"Identifier"===t.property.type&&s.unshift(t.property.value),t=t.object;return"Identifier"===t.type&&s.unshift(t.value),s.join(".")}}extractKeyFromSelector(e){let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return null;t=e.argument}let s=t;const n=[];for(;"MemberExpression"===s.type;){const e=s.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)}s=s.object}if(n.length>0){const e=this.config.extract.keySeparator,t="string"==typeof e?e:".";return n.join(t)}return null}resolvePossibleContextStringValues(e){return[...this.hooks.resolvePossibleContextStringValues?.(e)??[],...this.resolvePossibleStringValuesFromExpression(e)]}resolvePossibleKeyStringValues(e){return[...this.hooks.resolvePossibleKeyStringValues?.(e)??[],...this.resolvePossibleStringValuesFromExpression(e)]}resolvePossibleStringValuesFromExpression(e,t=!1){if("StringLiteral"===e.type)return e.value||t?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValuesFromExpression(e.consequent,t),...this.resolvePossibleStringValuesFromExpression(e.alternate,t)]}return"Identifier"===e.type&&"undefined"===e.value?[]:"TemplateLiteral"===e.type?this.resolvePossibleStringValuesFromTemplateString(e):"NumericLiteral"===e.type||"BooleanLiteral"===e.type?[`${e.value}`]:[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...s]=e.quasis;return e.expressions.reduce((e,t,n)=>e.flatMap(e=>{const r=s[n]?.cooked??"";return this.resolvePossibleStringValuesFromExpression(t,!0).map(t=>`${e}${t}${r}`)}),[t.cooked??""])}getUseTranslationConfig(e){const t=this.config.extract.useTranslationNames||["useTranslation"];for(const s of t){if("string"==typeof s&&s===e)return{name:e,nsArg:0,keyPrefixArg:1};if("object"==typeof s&&s.name===e)return{name:s.name,nsArg:s.nsArg??0,keyPrefixArg:s.keyPrefixArg??1}}}getFunctionName(e){if("Identifier"===e.type)return e.value;if("MemberExpression"===e.type){const t=[];let s=e;for(;"MemberExpression"===s.type;){if("Identifier"!==s.property.type)return null;t.unshift(s.property.value),s=s.object}if("ThisExpression"===s.type)t.unshift("this");else{if("Identifier"!==s.type)return null;t.unshift(s.value)}return t.join(".")}return null}}export{n as ASTVisitors};
@@ -1 +1 @@
1
- function t(t,n,c,o){const u=new RegExp("\\bt\\s*\\(\\s*(['\"])([^'\"]+)\\1","g"),f=function(t){const e=[],s=new Set,n=/\/\/(.*)|\/\*([\s\S]*?)\*\//g;let c;for(;null!==(c=n.exec(t));){const t=(c[1]??c[2]).trim();t&&!s.has(t)&&(s.add(t),e.push(t))}return e}(t);for(const t of f){let f;for(;null!==(f=u.exec(t));){let u,l=f[2];const r=t.slice(f.index+f[0].length),i=e(r);u=s(r);const a=c.extract.nsSeparator??":";if(!u&&a&&l.includes(a)){const t=l.split(a);u=t.shift(),l=t.join(a)}if(!u&&o){const t=o("t");t?.defaultNs&&(u=t.defaultNs)}u||(u=c.extract.defaultNS),n.addKey({key:l,ns:u,defaultValue:i??l})}}}function e(t){const e=/^\s*,\s*(['"])(.*?)\1/.exec(t);if(e)return e[2];const s=/^\s*,\s*\{[^}]*defaultValue\s*:\s*(['"])(.*?)\1/.exec(t);return s?s[2]:void 0}function s(t){const e=/^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(t);if(e)return e[2]}export{t as extractKeysFromComments};
1
+ function e(e,l,u,f){const r=new RegExp("\\bt\\s*\\(\\s*(['\"])([^'\"]+)\\1","g"),d=function(e){const t=[],n=new Set,s=/\/\/(.*)|\/\*([\s\S]*?)\*\//g;let o;for(;null!==(o=s.exec(e));){const e=(o[1]??o[2]).trim();e&&!n.has(e)&&(n.add(e),t.push(e))}return t}(e);for(const e of d){let d;for(;null!==(d=r.exec(e));){let r,i=d[2];const x=e.slice(d.index+d[0].length),y=s(x),g=c(x),p=a(x);r=o(x);const $=u.extract.nsSeparator??":";if(!r&&$&&i.includes($)){const e=i.split($);r=e.shift(),i=e.join($)}if(!r&&f){const e=f("t");e?.defaultNs&&(r=e.defaultNs)}r||(r=u.extract.defaultNS),g&&p?(t(i,y??i,r,l,u),n(i,y??i,r,g,l,u)):g?(l.addKey({key:i,ns:r,defaultValue:y??i}),l.addKey({key:`${i}_${g}`,ns:r,defaultValue:y??i})):p?t(i,y??i,r,l,u):l.addKey({key:i,ns:r,defaultValue:y??i})}}}function t(e,t,n,s,o){const c=o.extract.primaryLanguage||o.locales[0]||"en",a=new Intl.PluralRules(c),l=[0,1,2,3,5,100],u=new Set;for(const e of l)u.add(a.select(e));for(const o of u)s.addKey({key:`${e}_${o}`,ns:n,defaultValue:t})}function n(e,t,n,s,o,c){const a=c.extract.primaryLanguage||c.locales[0]||"en",l=new Intl.PluralRules(a),u=[0,1,2,3,5,100],f=new Set;for(const e of u)f.add(l.select(e));for(const c of f)o.addKey({key:`${e}_${s}_${c}`,ns:n,defaultValue:t})}function s(e){const t=/^\s*,\s*(['"])(.*?)\1/.exec(e);if(t)return t[2];const n=/^\s*,\s*\{[^}]*defaultValue\s*:\s*(['"])(.*?)\1/.exec(e);return n?n[2]:void 0}function o(e){const t=/^\s*,\s*\{[^}]*ns\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}function c(e){const t=/^\s*,\s*\{[^}]*context\s*:\s*(['"])(.*?)\1/.exec(e);if(t)return t[2]}function a(e){const t=/^\s*,\s*\{[^}]*count\s*:\s*(\d+)/.exec(e);if(t)return parseInt(t[1],10)}export{e as extractKeysFromComments};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -21,7 +21,7 @@ const program = new Command()
21
21
  program
22
22
  .name('i18next-cli')
23
23
  .description('A unified, high-performance i18next CLI.')
24
- .version('1.6.1')
24
+ .version('1.7.0')
25
25
 
26
26
  program
27
27
  .command('extract')
@@ -59,6 +59,8 @@ export async function runExtractor (
59
59
 
60
60
  validateExtractorConfig(config)
61
61
 
62
+ const plugins = config.plugins || []
63
+
62
64
  const spinner = ora('Running i18next key extractor...\n').start()
63
65
 
64
66
  try {
@@ -86,9 +88,9 @@ export async function runExtractor (
86
88
  }
87
89
 
88
90
  // Run afterSync hooks from plugins
89
- if ((config.plugins || []).length > 0) {
91
+ if (plugins.length > 0) {
90
92
  spinner.text = 'Running post-extraction plugins...'
91
- for (const plugin of (config.plugins || [])) {
93
+ for (const plugin of plugins) {
92
94
  await plugin.afterSync?.(results, config)
93
95
  }
94
96
  }
@@ -1,4 +1,5 @@
1
1
  import { glob } from 'glob'
2
+ import type { Expression } from '@swc/core'
2
3
  import type { ExtractedKey, Logger, I18nextToolkitConfig } from '../../types'
3
4
  import { processFile } from './extractor'
4
5
  import { ConsoleLogger } from '../../utils/logger'
@@ -57,6 +58,26 @@ export async function findKeys (
57
58
  }
58
59
  }
59
60
  },
61
+ resolvePossibleKeyStringValues: (expression: Expression) => {
62
+ return plugins.flatMap(plugin => {
63
+ try {
64
+ return plugin.extractKeysFromExpression?.(expression, config, logger) ?? []
65
+ } catch (err) {
66
+ logger.warn(`Plugin ${plugin.name} extractKeysFromExpression failed:`, err)
67
+ return []
68
+ }
69
+ })
70
+ },
71
+ resolvePossibleContextStringValues: (expression: Expression) => {
72
+ return plugins.flatMap(plugin => {
73
+ try {
74
+ return plugin.extractContextFromExpression?.(expression, config, logger) ?? []
75
+ } catch (err) {
76
+ logger.warn(`Plugin ${plugin.name} extractContextFromExpression failed:`, err)
77
+ return []
78
+ }
79
+ })
80
+ },
60
81
  } satisfies ASTVisitorHooks
61
82
 
62
83
  // 3. Create the visitor instance, passing it the context.
@@ -12,6 +12,8 @@ interface UseTranslationHookConfig {
12
12
  export interface ASTVisitorHooks {
13
13
  onBeforeVisitNode?: (node: Node) => void
14
14
  onAfterVisitNode?: (node: Node) => void
15
+ resolvePossibleContextStringValues?: (expression: Expression, returnEmptyStrings?: boolean) => string[]
16
+ resolvePossibleKeyStringValues?: (expression: Expression, returnEmptyStrings?: boolean) => string[]
15
17
  }
16
18
 
17
19
  /**
@@ -65,7 +67,9 @@ export class ASTVisitors {
65
67
  this.logger = logger
66
68
  this.hooks = {
67
69
  onBeforeVisitNode: hooks?.onBeforeVisitNode,
68
- onAfterVisitNode: hooks?.onAfterVisitNode
70
+ onAfterVisitNode: hooks?.onAfterVisitNode,
71
+ resolvePossibleKeyStringValues: hooks?.resolvePossibleKeyStringValues,
72
+ resolvePossibleContextStringValues: hooks?.resolvePossibleContextStringValues
69
73
  }
70
74
  }
71
75
 
@@ -574,7 +578,7 @@ export class ASTVisitors {
574
578
  keysWithContext.push({ key: `${finalKey}${contextSeparator}${contextValue}`, ns, defaultValue: dv })
575
579
  }
576
580
  } else if (contextProp?.value) {
577
- const contextValues = this.resolvePossibleStringValues(contextProp.value)
581
+ const contextValues = this.resolvePossibleContextStringValues(contextProp.value)
578
582
  const contextSeparator = this.config.extract.contextSeparator ?? '_'
579
583
 
580
584
  if (contextValues.length > 0) {
@@ -652,11 +656,11 @@ export class ASTVisitors {
652
656
  } else if (firstArg.type === 'ArrayExpression') {
653
657
  for (const element of firstArg.elements) {
654
658
  if (element?.expression) {
655
- keysToProcess.push(...this.resolvePossibleStringValues(element.expression))
659
+ keysToProcess.push(...this.resolvePossibleKeyStringValues(element.expression))
656
660
  }
657
661
  }
658
662
  } else {
659
- keysToProcess.push(...this.resolvePossibleStringValues(firstArg))
663
+ keysToProcess.push(...this.resolvePossibleKeyStringValues(firstArg))
660
664
  }
661
665
 
662
666
  return {
@@ -786,7 +790,7 @@ export class ASTVisitors {
786
790
 
787
791
  if (extractedAttributes) {
788
792
  if (extractedAttributes.keyExpression) {
789
- const keyValues = this.resolvePossibleStringValues(extractedAttributes.keyExpression)
793
+ const keyValues = this.resolvePossibleKeyStringValues(extractedAttributes.keyExpression)
790
794
  keysToProcess.push(...keyValues)
791
795
  } else {
792
796
  keysToProcess.push(extractedAttributes.serializedChildren)
@@ -864,7 +868,7 @@ export class ASTVisitors {
864
868
  )
865
869
  const isOrdinal = !!ordinalAttr
866
870
 
867
- const contextValues = this.resolvePossibleStringValues(contextExpression)
871
+ const contextValues = this.resolvePossibleContextStringValues(contextExpression)
868
872
  const contextSeparator = this.config.extract.contextSeparator ?? '_'
869
873
 
870
874
  // Generate all combinations of context and plural forms
@@ -884,7 +888,7 @@ export class ASTVisitors {
884
888
  extractedKeys.forEach(extractedKey => this.generatePluralKeysForTrans(extractedKey.key, extractedKey.defaultValue, extractedKey.ns, isOrdinal, optionsNode))
885
889
  }
886
890
  } else if (contextExpression) {
887
- const contextValues = this.resolvePossibleStringValues(contextExpression)
891
+ const contextValues = this.resolvePossibleContextStringValues(contextExpression)
888
892
  const contextSeparator = this.config.extract.contextSeparator ?? '_'
889
893
 
890
894
  if (contextValues.length > 0) {
@@ -1075,13 +1079,48 @@ export class ASTVisitors {
1075
1079
  return null
1076
1080
  }
1077
1081
 
1082
+ /**
1083
+ * Resolves an expression to one or more possible context string values that can be
1084
+ * determined statically from the AST. This is a wrapper around the plugin hook
1085
+ * `extractContextFromExpression` and {@link resolvePossibleStringValuesFromExpression}.
1086
+ *
1087
+ * @param expression - The SWC AST expression node to resolve
1088
+ * @returns An array of possible context string values that the expression may produce.
1089
+ *
1090
+ * @private
1091
+ */
1092
+ private resolvePossibleContextStringValues (expression: Expression) {
1093
+ const strings = this.hooks.resolvePossibleContextStringValues?.(expression) ?? []
1094
+
1095
+ return [...strings, ...this.resolvePossibleStringValuesFromExpression(expression)]
1096
+ }
1097
+
1098
+ /**
1099
+ * Resolves an expression to one or more possible key string values that can be
1100
+ * determined statically from the AST. This is a wrapper around the plugin hook
1101
+ * `extractKeysFromExpression` and {@link resolvePossibleStringValuesFromExpression}.
1102
+ *
1103
+ * @param expression - The SWC AST expression node to resolve
1104
+ * @returns An array of possible key string values that the expression may produce.
1105
+ *
1106
+ * @private
1107
+ */
1108
+ private resolvePossibleKeyStringValues (expression: Expression) {
1109
+ const strings = this.hooks.resolvePossibleKeyStringValues?.(expression) ?? []
1110
+
1111
+ return [...strings, ...this.resolvePossibleStringValuesFromExpression(expression)]
1112
+ }
1113
+
1078
1114
  /**
1079
1115
  * Resolves an expression to one or more possible string values that can be
1080
1116
  * determined statically from the AST.
1081
1117
  *
1082
1118
  * Supports:
1083
1119
  * - StringLiteral -> single value (filtered to exclude empty strings for context)
1120
+ * - NumericLiteral -> single value
1121
+ * - BooleanLiteral -> single value
1084
1122
  * - ConditionalExpression (ternary) -> union of consequent and alternate resolved values
1123
+ * - TemplateLiteral -> union of all possible string values
1085
1124
  * - The identifier `undefined` -> empty array
1086
1125
  *
1087
1126
  * For any other expression types (identifiers, function calls, member expressions,
@@ -1092,15 +1131,15 @@ export class ASTVisitors {
1092
1131
  * @param returnEmptyStrings - Whether to include empty strings in the result
1093
1132
  * @returns An array of possible string values that the expression may produce.
1094
1133
  */
1095
- private resolvePossibleStringValues (expression: Expression, returnEmptyStrings = false): string[] {
1134
+ private resolvePossibleStringValuesFromExpression (expression: Expression, returnEmptyStrings = false): string[] {
1096
1135
  if (expression.type === 'StringLiteral') {
1097
1136
  // Filter out empty strings as they should be treated as "no context" like i18next does
1098
1137
  return expression.value || returnEmptyStrings ? [expression.value] : []
1099
1138
  }
1100
1139
 
1101
1140
  if (expression.type === 'ConditionalExpression') { // This is a ternary operator
1102
- const consequentValues = this.resolvePossibleStringValues(expression.consequent, returnEmptyStrings)
1103
- const alternateValues = this.resolvePossibleStringValues(expression.alternate, returnEmptyStrings)
1141
+ const consequentValues = this.resolvePossibleStringValuesFromExpression(expression.consequent, returnEmptyStrings)
1142
+ const alternateValues = this.resolvePossibleStringValuesFromExpression(expression.alternate, returnEmptyStrings)
1104
1143
  return [...consequentValues, ...alternateValues]
1105
1144
  }
1106
1145
 
@@ -1142,7 +1181,7 @@ export class ASTVisitors {
1142
1181
  (heads, expression, i) => {
1143
1182
  return heads.flatMap((head) => {
1144
1183
  const tail = tails[i]?.cooked ?? ''
1145
- return this.resolvePossibleStringValues(expression, true).map(
1184
+ return this.resolvePossibleStringValuesFromExpression(expression, true).map(
1146
1185
  (expressionValue) => `${head}${expressionValue}${tail}`
1147
1186
  )
1148
1187
  })
@@ -43,6 +43,8 @@ export function extractKeysFromComments (
43
43
  const remainder = text.slice(match.index + match[0].length)
44
44
 
45
45
  const defaultValue = parseDefaultValueFromComment(remainder)
46
+ const context = parseContextFromComment(remainder)
47
+ const count = parseCountFromComment(remainder)
46
48
 
47
49
  // 1. Check for namespace in options object first (e.g., { ns: 'common' })
48
50
  ns = parseNsFromComment(remainder)
@@ -67,11 +69,89 @@ export function extractKeysFromComments (
67
69
  // 4. Final fallback to configured default namespace
68
70
  if (!ns) ns = config.extract.defaultNS
69
71
 
70
- pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key })
72
+ // 5. Handle context and count combinations
73
+ if (context && count) {
74
+ // Generate all combinations: base plural + context+plural
75
+ generatePluralKeys(key, defaultValue ?? key, ns, pluginContext, config)
76
+ generateContextPluralKeys(key, defaultValue ?? key, ns, context, pluginContext, config)
77
+ } else if (context) {
78
+ // Just context variants
79
+ pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key })
80
+ pluginContext.addKey({ key: `${key}_${context}`, ns, defaultValue: defaultValue ?? key })
81
+ } else if (count) {
82
+ // Just plural variants
83
+ generatePluralKeys(key, defaultValue ?? key, ns, pluginContext, config)
84
+ } else {
85
+ // Simple key
86
+ pluginContext.addKey({ key, ns, defaultValue: defaultValue ?? key })
87
+ }
71
88
  }
72
89
  }
73
90
  }
74
91
 
92
+ /**
93
+ * Generates plural keys for a given base key
94
+ */
95
+ function generatePluralKeys (
96
+ key: string,
97
+ defaultValue: string,
98
+ ns: string | undefined,
99
+ pluginContext: PluginContext,
100
+ config: I18nextToolkitConfig
101
+ ): void {
102
+ const primaryLanguage = config.extract.primaryLanguage || config.locales[0] || 'en'
103
+ const pluralRules = new Intl.PluralRules(primaryLanguage)
104
+
105
+ // Get all possible plural categories for the primary language
106
+ const testNumbers = [0, 1, 2, 3, 5, 100] // Test various numbers to find all categories
107
+ const categories = new Set<string>()
108
+
109
+ for (const num of testNumbers) {
110
+ categories.add(pluralRules.select(num))
111
+ }
112
+
113
+ // Generate keys for each plural category
114
+ for (const category of categories) {
115
+ pluginContext.addKey({
116
+ key: `${key}_${category}`,
117
+ ns,
118
+ defaultValue
119
+ })
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Generates context + plural combination keys
125
+ */
126
+ function generateContextPluralKeys (
127
+ key: string,
128
+ defaultValue: string,
129
+ ns: string | undefined,
130
+ context: string,
131
+ pluginContext: PluginContext,
132
+ config: I18nextToolkitConfig
133
+ ): void {
134
+ const primaryLanguage = config.extract.primaryLanguage || config.locales[0] || 'en'
135
+ const pluralRules = new Intl.PluralRules(primaryLanguage)
136
+
137
+ // Get all possible plural categories for the primary language
138
+ const testNumbers = [0, 1, 2, 3, 5, 100]
139
+ const categories = new Set<string>()
140
+
141
+ for (const num of testNumbers) {
142
+ categories.add(pluralRules.select(num))
143
+ }
144
+
145
+ // Generate keys for each context + plural combination
146
+ for (const category of categories) {
147
+ pluginContext.addKey({
148
+ key: `${key}_${context}_${category}`,
149
+ ns,
150
+ defaultValue
151
+ })
152
+ }
153
+ }
154
+
75
155
  /**
76
156
  * Parses default value from the remainder of a comment after a translation function call.
77
157
  * Supports both string literals and object syntax with defaultValue property.
@@ -136,3 +216,37 @@ function collectCommentTexts (src: string): string[] {
136
216
 
137
217
  return texts
138
218
  }
219
+
220
+ /**
221
+ * Parses context from the remainder of a comment after a translation function call.
222
+ * Looks for context specified in options object syntax.
223
+ *
224
+ * @param remainder - The remaining text after the translation key
225
+ * @returns The parsed context value or undefined if none found
226
+ *
227
+ * @internal
228
+ */
229
+ function parseContextFromComment (remainder: string): string | undefined {
230
+ // Look for context in an options object, e.g., { context: 'male' }
231
+ const contextObj = /^\s*,\s*\{[^}]*context\s*:\s*(['"])(.*?)\1/.exec(remainder)
232
+ if (contextObj) return contextObj[2]
233
+
234
+ return undefined
235
+ }
236
+
237
+ /**
238
+ * Parses count from the remainder of a comment after a translation function call.
239
+ * Looks for count specified in options object syntax.
240
+ *
241
+ * @param remainder - The remaining text after the translation key
242
+ * @returns The parsed count value or undefined if none found
243
+ *
244
+ * @internal
245
+ */
246
+ function parseCountFromComment (remainder: string): number | undefined {
247
+ // Look for count in an options object, e.g., { count: 1 }
248
+ const countObj = /^\s*,\s*\{[^}]*count\s*:\s*(\d+)/.exec(remainder)
249
+ if (countObj) return parseInt(countObj[1], 10)
250
+
251
+ return undefined
252
+ }
package/src/types.ts CHANGED
@@ -196,6 +196,32 @@ export interface Plugin {
196
196
  /** Unique name for the plugin */
197
197
  name: string;
198
198
 
199
+ /**
200
+ * Custom function to extract keys from an AST expression. Useful
201
+ * for plugins that need to extract key patterns from `t(..., options)`
202
+ * or `<Trans i18nKey={...} />`.
203
+ *
204
+ * @param expression - An expression to extract keys from
205
+ * @param config - The i18next toolkit configuration object
206
+ * @param logger - Logger instance for output
207
+ *
208
+ * @returns An array of extracted keys
209
+ */
210
+ extractKeysFromExpression?: (expression: Expression, config: Omit<I18nextToolkitConfig, 'plugins'>, logger: Logger) => string[];
211
+
212
+ /**
213
+ * Custom function to extract context from an AST expression. Useful
214
+ * for plugins that need to extract context patterns from `t('key', { context: ... })`
215
+ * or `<Trans i18nKey="key" context={...} />`.
216
+ *
217
+ * @param expression - An expression to extract context from
218
+ * @param config - The i18next toolkit configuration object
219
+ * @param logger - Logger instance for output
220
+ *
221
+ * @returns An array of extracted context values
222
+ */
223
+ extractContextFromExpression?: (expression: Expression, config: Omit<I18nextToolkitConfig, 'plugins'>, logger: Logger) => string[];
224
+
199
225
  /**
200
226
  * Hook called once at the beginning of the extraction process.
201
227
  * Use for initialization tasks like setting up resources or validating configuration.
@@ -1 +1 @@
1
- {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/extractor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAKtF,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAKrD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,EACE,WAAmB,EACnB,QAAgB,EACjB,GAAE;IACD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACf,EACN,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,OAAO,CAAC,CAuDlB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CAoCf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,sDAO1D"}
1
+ {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/extractor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAKtF,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAKrD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,EACE,WAAmB,EACnB,QAAgB,EACjB,GAAE;IACD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACf,EACN,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,OAAO,CAAC,CAyDlB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CAoCf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,OAAO,CAAE,MAAM,EAAE,oBAAoB,sDAO1D"}
@@ -1 +1 @@
1
- {"version":3,"file":"key-finder.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/key-finder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAM7E;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,oBAAoB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC;IAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CA4C1E"}
1
+ {"version":3,"file":"key-finder.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/key-finder.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAM7E;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,oBAAoB,EAC5B,MAAM,GAAE,MAA4B,GACnC,OAAO,CAAC;IAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CAgE1E"}
@@ -1,8 +1,10 @@
1
- import type { Module, Node } from '@swc/core';
1
+ import type { Module, Node, Expression } from '@swc/core';
2
2
  import type { PluginContext, I18nextToolkitConfig, Logger, ScopeInfo } from '../../types';
3
3
  export interface ASTVisitorHooks {
4
4
  onBeforeVisitNode?: (node: Node) => void;
5
5
  onAfterVisitNode?: (node: Node) => void;
6
+ resolvePossibleContextStringValues?: (expression: Expression, returnEmptyStrings?: boolean) => string[];
7
+ resolvePossibleKeyStringValues?: (expression: Expression, returnEmptyStrings?: boolean) => string[];
6
8
  }
7
9
  /**
8
10
  * AST visitor class that traverses JavaScript/TypeScript syntax trees to extract translation keys.
@@ -256,13 +258,38 @@ export declare class ASTVisitors {
256
258
  * @private
257
259
  */
258
260
  private extractKeyFromSelector;
261
+ /**
262
+ * Resolves an expression to one or more possible context string values that can be
263
+ * determined statically from the AST. This is a wrapper around the plugin hook
264
+ * `extractContextFromExpression` and {@link resolvePossibleStringValuesFromExpression}.
265
+ *
266
+ * @param expression - The SWC AST expression node to resolve
267
+ * @returns An array of possible context string values that the expression may produce.
268
+ *
269
+ * @private
270
+ */
271
+ private resolvePossibleContextStringValues;
272
+ /**
273
+ * Resolves an expression to one or more possible key string values that can be
274
+ * determined statically from the AST. This is a wrapper around the plugin hook
275
+ * `extractKeysFromExpression` and {@link resolvePossibleStringValuesFromExpression}.
276
+ *
277
+ * @param expression - The SWC AST expression node to resolve
278
+ * @returns An array of possible key string values that the expression may produce.
279
+ *
280
+ * @private
281
+ */
282
+ private resolvePossibleKeyStringValues;
259
283
  /**
260
284
  * Resolves an expression to one or more possible string values that can be
261
285
  * determined statically from the AST.
262
286
  *
263
287
  * Supports:
264
288
  * - StringLiteral -> single value (filtered to exclude empty strings for context)
289
+ * - NumericLiteral -> single value
290
+ * - BooleanLiteral -> single value
265
291
  * - ConditionalExpression (ternary) -> union of consequent and alternate resolved values
292
+ * - TemplateLiteral -> union of all possible string values
266
293
  * - The identifier `undefined` -> empty array
267
294
  *
268
295
  * For any other expression types (identifiers, function calls, member expressions,
@@ -273,7 +300,7 @@ export declare class ASTVisitors {
273
300
  * @param returnEmptyStrings - Whether to include empty strings in the result
274
301
  * @returns An array of possible string values that the expression may produce.
275
302
  */
276
- private resolvePossibleStringValues;
303
+ private resolvePossibleStringValuesFromExpression;
277
304
  /**
278
305
  * Resolves a template literal to one or more possible string values that can be
279
306
  * determined statically from the AST.
@@ -1 +1 @@
1
- {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAA0H,MAAM,WAAW,CAAA;AACrK,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,aAAa,CAAA;AAUvG,MAAM,WAAW,eAAe;IAC9B,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IACxC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;CACxC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,KAAK,CAAiB;IAEvB,UAAU,cAAoB;IAErC,OAAO,CAAC,KAAK,CAAqE;IAElF;;;;;;OAMG;gBAED,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,eAAe;IAWzB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IA2DZ;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;;;OAQG;IACI,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkB5D;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,wBAAwB;IAsChC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,+BAA+B;IAmEvC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,8BAA8B;IAwDtC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IAoK5B;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,gBAAgB;IA4DxB;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAkB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IA4IxB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,0BAA0B;IA6DlC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,2BAA2B;IA4BnC;;;;;;;OAOG;IACH,OAAO,CAAC,6CAA6C;IAyBrD;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
1
+ {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAA6F,UAAU,EAAmB,MAAM,WAAW,CAAA;AACrK,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAgB,SAAS,EAAE,MAAM,aAAa,CAAA;AAUvG,MAAM,WAAW,eAAe;IAC9B,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IACxC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IACvC,kCAAkC,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,kBAAkB,CAAC,EAAE,OAAO,KAAK,MAAM,EAAE,CAAA;IACvG,8BAA8B,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,kBAAkB,CAAC,EAAE,OAAO,KAAK,MAAM,EAAE,CAAA;CACpG;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAe;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,KAAK,CAAiB;IAEvB,UAAU,cAAoB;IAErC,OAAO,CAAC,KAAK,CAAqE;IAElF;;;;;;OAMG;gBAED,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,eAAe;IAazB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IA2DZ;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;;;OAQG;IACI,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkB5D;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,wBAAwB;IAsChC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,+BAA+B;IAmEvC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,8BAA8B;IAwDtC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,yBAAyB;IAoBjC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IAoK5B;;;;;;OAMG;IACH,OAAO,CAAC,4BAA4B;IA8BpC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,gBAAgB;IA4DxB;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAkB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IA4IxB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,0BAA0B;IA6DlC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,sBAAsB;IA2C9B;;;;;;;;;OASG;IACH,OAAO,CAAC,kCAAkC;IAM1C;;;;;;;;;OASG;IACH,OAAO,CAAC,8BAA8B;IAMtC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,yCAAyC;IA4BjD;;;;;;;OAOG;IACH,OAAO,CAAC,6CAA6C;IAyBrD;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAoB/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,OAAO,CAAC,eAAe;CA2BxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"comment-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/comment-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEtE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,oBAAoB,EAC5B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC1F,IAAI,CA4CN"}
1
+ {"version":3,"file":"comment-parser.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/comment-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEtE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,oBAAoB,EAC5B,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC1F,IAAI,CA6DN"}
package/types/types.d.ts CHANGED
@@ -158,6 +158,30 @@ export interface I18nextToolkitConfig {
158
158
  export interface Plugin {
159
159
  /** Unique name for the plugin */
160
160
  name: string;
161
+ /**
162
+ * Custom function to extract keys from an AST expression. Useful
163
+ * for plugins that need to extract key patterns from `t(..., options)`
164
+ * or `<Trans i18nKey={...} />`.
165
+ *
166
+ * @param expression - An expression to extract keys from
167
+ * @param config - The i18next toolkit configuration object
168
+ * @param logger - Logger instance for output
169
+ *
170
+ * @returns An array of extracted keys
171
+ */
172
+ extractKeysFromExpression?: (expression: Expression, config: Omit<I18nextToolkitConfig, 'plugins'>, logger: Logger) => string[];
173
+ /**
174
+ * Custom function to extract context from an AST expression. Useful
175
+ * for plugins that need to extract context patterns from `t('key', { context: ... })`
176
+ * or `<Trans i18nKey="key" context={...} />`.
177
+ *
178
+ * @param expression - An expression to extract context from
179
+ * @param config - The i18next toolkit configuration object
180
+ * @param logger - Logger instance for output
181
+ *
182
+ * @returns An array of extracted context values
183
+ */
184
+ extractContextFromExpression?: (expression: Expression, config: Omit<I18nextToolkitConfig, 'plugins'>, logger: Logger) => string[];
161
185
  /**
162
186
  * Hook called once at the beginning of the extraction process.
163
187
  * Use for initialization tasks like setting up resources or validating configuration.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}