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 +5 -0
- package/README.md +108 -46
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/extractor/core/extractor.js +1 -1
- package/dist/cjs/extractor/core/key-finder.js +1 -1
- package/dist/cjs/extractor/parsers/ast-visitors.js +1 -1
- package/dist/cjs/extractor/parsers/comment-parser.js +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/extractor/core/extractor.js +1 -1
- package/dist/esm/extractor/core/key-finder.js +1 -1
- package/dist/esm/extractor/parsers/ast-visitors.js +1 -1
- package/dist/esm/extractor/parsers/comment-parser.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/extractor/core/extractor.ts +4 -2
- package/src/extractor/core/key-finder.ts +21 -0
- package/src/extractor/parsers/ast-visitors.ts +50 -11
- package/src/extractor/parsers/comment-parser.ts +115 -1
- package/src/types.ts +26 -0
- package/types/extractor/core/extractor.d.ts.map +1 -1
- package/types/extractor/core/key-finder.d.ts.map +1 -1
- package/types/extractor/parsers/ast-visitors.d.ts +29 -2
- package/types/extractor/parsers/ast-visitors.d.ts.map +1 -1
- package/types/extractor/parsers/comment-parser.d.ts.map +1 -1
- package/types/types.d.ts +24 -0
- package/types/types.d.ts.map +1 -1
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
|
-
**
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
|
415
|
-
for (const match of
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
|
519
|
+
**Configuration:**
|
|
459
520
|
|
|
460
521
|
```typescript
|
|
461
522
|
import { defineConfig } from 'i18next-cli';
|
|
462
|
-
import { myCustomPlugin } from './my-
|
|
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.
|
|
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);
|
|
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"),
|
|
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(
|
|
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.
|
|
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);
|
|
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
|
|
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
|
|
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
package/src/cli.ts
CHANGED
|
@@ -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 (
|
|
91
|
+
if (plugins.length > 0) {
|
|
90
92
|
spinner.text = 'Running post-extraction plugins...'
|
|
91
|
-
for (const plugin of
|
|
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.
|
|
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.
|
|
659
|
+
keysToProcess.push(...this.resolvePossibleKeyStringValues(element.expression))
|
|
656
660
|
}
|
|
657
661
|
}
|
|
658
662
|
} else {
|
|
659
|
-
keysToProcess.push(...this.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
1103
|
-
const alternateValues = this.
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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":"
|
|
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
|
|
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,
|
|
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,
|
|
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.
|
package/types/types.d.ts.map
CHANGED
|
@@ -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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"}
|