i18next-cli 1.56.8 → 1.56.10

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/README.md CHANGED
@@ -44,7 +44,7 @@ A unified, high-performance i18next CLI toolchain, powered by SWC.
44
44
  - **Translation Status**: Get a high-level overview or a detailed, key-by-key report of your project's translation completeness.
45
45
  - **Plugin System**: Extensible architecture for custom extraction patterns and file types (e.g., HTML, Handlebars).
46
46
  - **Legacy Migration**: Automatic migration from `i18next-parser` configurations.
47
- - **Cloud Integration**: Seamless integration with the [Locize](https://locize.com) translation management platform.
47
+ - **Cloud Integration**: Seamless integration with the [Locize](https://www.locize.com?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme) translation management platform.
48
48
 
49
49
  ## Installation
50
50
 
@@ -253,7 +253,7 @@ npx i18next-cli lint
253
253
 
254
254
  ### `instrument`
255
255
 
256
- Scans your source code for hardcoded user-facing strings and instruments them with i18next translation calls. This is useful for adding i18next instrumentation to an existing codebase that wasn't built with internationalization in mind. You can see this in action in [this video](https://youtu.be/aWZnZXwGg34) or in [this blog post](https://www.locize.com/blog/i18next-cli-instrument).
256
+ Scans your source code for hardcoded user-facing strings and instruments them with i18next translation calls. This is useful for adding i18next instrumentation to an existing codebase that wasn't built with internationalization in mind. You can see this in action in [this video](https://youtu.be/aWZnZXwGg34) or in [this blog post](https://www.locize.com/blog/i18next-cli-instrument?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme).
257
257
 
258
258
  > **⚠️ First-Step Tool:** The `instrument` command uses heuristic-based detection and is designed as a **first pass** to identify and suggest transformation candidates. It will **not catch 100% of cases**, and you should expect both false positives and false negatives. Always review the suggested transformations carefully before committing them to your codebase. Think of it as an intelligent code assistant, not an automated compiler.
259
259
 
@@ -1507,21 +1507,21 @@ This programmatic API gives you the same power as the CLI but with full control
1507
1507
  <h3 align="center">Gold Sponsors</h3>
1508
1508
 
1509
1509
  <p align="center">
1510
- <a href="https://www.locize.com/" target="_blank">
1510
+ <a href="https://www.locize.com/?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme" target="_blank">
1511
1511
  <img src="https://raw.githubusercontent.com/i18next/i18next/master/assets/locize_sponsor_240.gif" width="240px">
1512
1512
  </a>
1513
1513
  </p>
1514
1514
 
1515
1515
  ---
1516
1516
 
1517
- **From the creators of i18next: localization as a service - [Locize](https://www.locize.com)**
1517
+ **From the creators of i18next: localization as a service - [Locize](https://www.locize.com?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme)**
1518
1518
 
1519
- A translation management system built around the i18next ecosystem - [Locize](https://locize.com).
1519
+ A translation management system built around the i18next ecosystem - [Locize](https://www.locize.com?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme).
1520
1520
 
1521
- **Now with a [Free plan](https://locize.com/pricing) for small projects!** Perfect for hobbyists or getting started.
1521
+ **Now with a [Free plan](https://www.locize.com/pricing?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme) for small projects!** Perfect for hobbyists or getting started.
1522
1522
 
1523
1523
  ![Locize](https://www.locize.com/img/ads/github_locize.png)
1524
1524
 
1525
- With using [Locize](https://www.locize.com/?utm_source=i18next_cli_readme&utm_medium=github) you directly support the future of i18next.
1525
+ With using [Locize](https://www.locize.com/?utm_source=i18next_cli_readme&utm_medium=github&utm_campaign=readme) you directly support the future of i18next.
1526
1526
 
1527
1527
  ---
package/dist/cjs/cli.js CHANGED
@@ -32,7 +32,7 @@ const program = new commander.Command();
32
32
  program
33
33
  .name('i18next-cli')
34
34
  .description('A unified, high-performance i18next CLI.')
35
- .version('1.56.8'); // This string is replaced with the actual version at build time by rollup
35
+ .version('1.56.10'); // This string is replaced with the actual version at build time by rollup
36
36
  // new: global config override option
37
37
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
38
38
  program
@@ -165,7 +165,37 @@ class ASTVisitors {
165
165
  if (!node)
166
166
  return;
167
167
  let isNewScope = false;
168
+ let isNewClassScope = false;
168
169
  let paramTemporaries;
170
+ // ENTER CLASS SCOPE for class declarations / expressions and pre-register
171
+ // class field initializers so that later `this.<field>` references inside
172
+ // method bodies can inherit the namespace/keyPrefix from the field.
173
+ if (node.type === 'ClassDeclaration' ||
174
+ node.type === 'ClassExpression' ||
175
+ node.type === 'ClassDecl' ||
176
+ node.type === 'ClassExpr') {
177
+ this.scopeManager.enterClassScope();
178
+ isNewClassScope = true;
179
+ const classBody = Array.isArray(node.body) ? node.body : (node.body?.body ?? []);
180
+ for (const member of classBody) {
181
+ if (!member || typeof member !== 'object')
182
+ continue;
183
+ if (member.type === 'ClassProperty' ||
184
+ member.type === 'PrivateProperty' ||
185
+ member.type === 'PropertyDefinition' ||
186
+ member.type === 'ClassPrivateProperty') {
187
+ let fieldName;
188
+ const key = member.key;
189
+ if (key?.type === 'Identifier')
190
+ fieldName = key.value ?? key.name;
191
+ else if (key?.type === 'PrivateName')
192
+ fieldName = '#' + (key.value ?? key.name ?? key.id?.value ?? key.id?.name);
193
+ if (!fieldName)
194
+ continue;
195
+ this.scopeManager.registerClassField(fieldName, member.value);
196
+ }
197
+ }
198
+ }
169
199
  // ENTER SCOPE for functions
170
200
  // Accept many SWC/TS AST variants for function-like nodes (declarations, expressions, arrow functions)
171
201
  if (node.type === 'Function' ||
@@ -540,6 +570,10 @@ class ASTVisitors {
540
570
  }
541
571
  this.scopeManager.exitScope();
542
572
  }
573
+ // LEAVE CLASS SCOPE for classes
574
+ if (isNewClassScope) {
575
+ this.scopeManager.exitClassScope();
576
+ }
543
577
  }
544
578
  /**
545
579
  * If `node` is a call like `ARRAY.map(param => ...)` where ARRAY is a known
@@ -6,6 +6,11 @@ class ScopeManager {
6
6
  scopeStack = [];
7
7
  config;
8
8
  scope = new Map();
9
+ // Stack of class scopes; each entry maps a class field name (e.g. `myField`
10
+ // or `#privateField`) to the ScopeInfo derived from its initializer. Used to
11
+ // resolve `const { t } = this.field` and `const { t } = this.field()` patterns
12
+ // inside class methods.
13
+ thisFieldStack = [];
9
14
  // Track simple local constants with string literal values to resolve identifier args
10
15
  simpleConstants = new Map();
11
16
  // Track simple local constant objects with string literal property values
@@ -63,6 +68,49 @@ class ScopeManager {
63
68
  this.scope = new Map();
64
69
  this.simpleConstants.clear();
65
70
  this.simpleConstantObjects.clear();
71
+ this.thisFieldStack = [];
72
+ }
73
+ /**
74
+ * Pushes a new class scope used for tracking class field → ScopeInfo
75
+ * mappings. Should be called when entering a ClassDeclaration or
76
+ * ClassExpression.
77
+ */
78
+ enterClassScope() {
79
+ this.thisFieldStack.push(new Map());
80
+ }
81
+ /**
82
+ * Pops the current class scope. Should be called when leaving a
83
+ * ClassDeclaration or ClassExpression.
84
+ */
85
+ exitClassScope() {
86
+ this.thisFieldStack.pop();
87
+ }
88
+ /**
89
+ * Records a class field's ScopeInfo so that later `this.<field>` or
90
+ * `this.<field>()` accesses inside the class can inherit the namespace
91
+ * and keyPrefix from its initializer.
92
+ *
93
+ * @param fieldName - Field name (private fields are prefixed with `#`)
94
+ * @param info - Scope information derived from the field's initializer
95
+ */
96
+ setThisField(fieldName, info) {
97
+ if (this.thisFieldStack.length > 0) {
98
+ this.thisFieldStack[this.thisFieldStack.length - 1].set(fieldName, info);
99
+ }
100
+ }
101
+ /**
102
+ * Looks up a class field's ScopeInfo from the innermost class scope outward.
103
+ *
104
+ * @param fieldName - Field name (private fields are prefixed with `#`)
105
+ * @returns Scope information if found, undefined otherwise
106
+ */
107
+ getThisField(fieldName) {
108
+ for (let i = this.thisFieldStack.length - 1; i >= 0; i--) {
109
+ if (this.thisFieldStack[i].has(fieldName)) {
110
+ return this.thisFieldStack[i].get(fieldName);
111
+ }
112
+ }
113
+ return undefined;
66
114
  }
67
115
  /**
68
116
  * Enters a new variable scope by pushing a new scope map onto the stack.
@@ -219,6 +267,17 @@ class ScopeManager {
219
267
  }
220
268
  // continue processing; still may be a useTranslation/getFixedT call below
221
269
  }
270
+ // Handle: const { t } = this.#field OR const { t } = this.#field()
271
+ // Resolve the source `this.<field>` to its previously-registered ScopeInfo
272
+ // and propagate it onto the destructured variables.
273
+ const thisFieldName = ScopeManager.extractThisFieldName(init);
274
+ if (thisFieldName !== undefined) {
275
+ const sourceScope = this.getThisField(thisFieldName);
276
+ if (sourceScope) {
277
+ this.attachScopeToDestructuredVars(node, sourceScope);
278
+ return;
279
+ }
280
+ }
222
281
  // Determine the actual call expression, looking inside AwaitExpressions.
223
282
  const callExpr = init.type === 'AwaitExpression' && init.argument.type === 'CallExpression'
224
283
  ? init.argument
@@ -545,6 +604,165 @@ class ScopeManager {
545
604
  this.setVarInScope(targetVarName, { defaultNs: finalNs, keyPrefix: finalKeyPrefix });
546
605
  }
547
606
  }
607
+ /**
608
+ * Returns the field name being referenced when an expression takes the form
609
+ * `this.<field>` or `this.<field>()`. Strips the call wrapper if present and
610
+ * prefixes private fields with `#`.
611
+ *
612
+ * Returns undefined if the expression is not such an access.
613
+ */
614
+ static extractThisFieldName(init) {
615
+ let memberExpr;
616
+ if (init.type === 'MemberExpression') {
617
+ memberExpr = init;
618
+ }
619
+ else if (init.type === 'CallExpression' && init.callee.type === 'MemberExpression') {
620
+ memberExpr = init.callee;
621
+ }
622
+ if (!memberExpr)
623
+ return undefined;
624
+ if (memberExpr.object.type !== 'ThisExpression')
625
+ return undefined;
626
+ const prop = memberExpr.property;
627
+ if (prop?.type === 'Identifier')
628
+ return prop.value;
629
+ if (prop?.type === 'PrivateName')
630
+ return '#' + prop.value;
631
+ return undefined;
632
+ }
633
+ /**
634
+ * Attaches the given ScopeInfo to all destructured variables of a variable
635
+ * declarator (`const { t } = ...`, `const [t] = ...`, or `const t = ...`).
636
+ */
637
+ attachScopeToDestructuredVars(node, info) {
638
+ if (node.id.type === 'ObjectPattern') {
639
+ for (const prop of node.id.properties) {
640
+ if (prop.type === 'AssignmentPatternProperty' && prop.key.type === 'Identifier') {
641
+ this.setVarInScope(prop.key.value, info);
642
+ }
643
+ if (prop.type === 'KeyValuePatternProperty' && prop.value.type === 'Identifier') {
644
+ this.setVarInScope(prop.value.value, info);
645
+ // Also store under the pattern key name so legacy comment-scope
646
+ // lookups by the original property name continue to work.
647
+ if (prop.key.type === 'Identifier' && (prop.key.value === 't' || prop.key.value === 'getFixedT')) {
648
+ this.scope.set(prop.value.value, { defaultNs: info.defaultNs, keyPrefix: info.keyPrefix });
649
+ }
650
+ }
651
+ }
652
+ }
653
+ else if (node.id.type === 'Identifier') {
654
+ this.setVarInScope(node.id.value, info);
655
+ }
656
+ else if (node.id.type === 'ArrayPattern') {
657
+ const firstElement = node.id.elements[0];
658
+ if (firstElement?.type === 'Identifier') {
659
+ this.setVarInScope(firstElement.value, info);
660
+ }
661
+ }
662
+ }
663
+ /**
664
+ * Registers a class field's initializer with the current class scope so that
665
+ * later `const { t } = this.<field>` or `const { t } = this.<field>()` calls
666
+ * inside the class body inherit the namespace and keyPrefix from the field
667
+ * initializer.
668
+ *
669
+ * Recognizes the same useTranslation-like patterns supported on regular
670
+ * variable declarations (configured via `useTranslationNames`).
671
+ *
672
+ * @param fieldName - Field name (private fields are prefixed with `#`)
673
+ * @param value - The field initializer expression
674
+ */
675
+ registerClassField(fieldName, value) {
676
+ if (!value)
677
+ return;
678
+ if (this.thisFieldStack.length === 0)
679
+ return;
680
+ const callExpr = value.type === 'AwaitExpression' && value.argument.type === 'CallExpression'
681
+ ? value.argument
682
+ : value.type === 'CallExpression'
683
+ ? value
684
+ : null;
685
+ if (!callExpr)
686
+ return;
687
+ const callee = callExpr.callee;
688
+ if (callee.type !== 'Identifier')
689
+ return;
690
+ const hookConfig = this.getUseTranslationConfig(callee.value);
691
+ if (!hookConfig)
692
+ return;
693
+ const info = this.computeScopeInfoFromHookCall(callExpr, hookConfig);
694
+ if (info.defaultNs || info.keyPrefix) {
695
+ this.setThisField(fieldName, info);
696
+ }
697
+ }
698
+ /**
699
+ * Computes ScopeInfo (defaultNs, keyPrefix) for a useTranslation-style call
700
+ * given a hook configuration that describes argument positions. Mirrors the
701
+ * extraction performed by handleUseTranslationDeclarator without attaching
702
+ * the result to any variable.
703
+ */
704
+ computeScopeInfoFromHookCall(callExpr, hookConfig) {
705
+ const nsArgIndex = hookConfig.nsArg ?? 0;
706
+ const kpArgIndex = hookConfig.keyPrefixArg ?? 1;
707
+ let defaultNs;
708
+ let keyPrefix;
709
+ const first = callExpr.arguments?.[0]?.expression;
710
+ const second = callExpr.arguments?.[1]?.expression;
711
+ const third = callExpr.arguments?.[2]?.expression;
712
+ const looksLikeLanguage = (s) => /^[a-z]{2,3}([-_][A-Za-z0-9-]+)?$/i.test(s);
713
+ const isBuiltInLngNsForm = hookConfig.name === 'useTranslation' &&
714
+ first?.type === 'StringLiteral' &&
715
+ second?.type === 'StringLiteral' &&
716
+ looksLikeLanguage(first.value);
717
+ let kpArg;
718
+ if (isBuiltInLngNsForm) {
719
+ defaultNs = second.value;
720
+ kpArg = third;
721
+ }
722
+ else {
723
+ if (nsArgIndex !== -1) {
724
+ const nsNode = callExpr.arguments?.[nsArgIndex]?.expression;
725
+ if (nsNode?.type === 'StringLiteral') {
726
+ defaultNs = nsNode.value;
727
+ }
728
+ else if (nsNode?.type === 'Identifier') {
729
+ defaultNs = this.resolveSimpleStringIdentifier(nsNode.value);
730
+ }
731
+ else if (nsNode?.type === 'MemberExpression') {
732
+ defaultNs = this.resolveSimpleMemberExpression(nsNode);
733
+ }
734
+ else if (nsNode?.type === 'ArrayExpression') {
735
+ const firstEl = nsNode.elements[0]?.expression;
736
+ if (firstEl?.type === 'StringLiteral') {
737
+ defaultNs = firstEl.value;
738
+ }
739
+ else if (firstEl?.type === 'Identifier') {
740
+ defaultNs = this.resolveSimpleStringIdentifier(firstEl.value);
741
+ }
742
+ }
743
+ }
744
+ kpArg = kpArgIndex === -1 ? undefined : callExpr.arguments?.[kpArgIndex]?.expression;
745
+ }
746
+ if (kpArg?.type === 'ObjectExpression') {
747
+ const kp = astUtils.getObjectPropValue(kpArg, 'keyPrefix', this.resolveSimpleStringIdentifier.bind(this));
748
+ if (typeof kp === 'string') {
749
+ keyPrefix = kp;
750
+ }
751
+ }
752
+ else if (kpArg?.type === 'StringLiteral') {
753
+ keyPrefix = kpArg.value;
754
+ }
755
+ else if (kpArg?.type === 'Identifier') {
756
+ keyPrefix = this.resolveSimpleStringIdentifier(kpArg.value);
757
+ }
758
+ else if (kpArg?.type === 'TemplateLiteral') {
759
+ const tpl = kpArg;
760
+ if ((tpl.expressions || []).length === 0) {
761
+ keyPrefix = tpl.quasis?.[0]?.cooked ?? undefined;
762
+ }
763
+ }
764
+ return { defaultNs, keyPrefix };
765
+ }
548
766
  }
549
767
 
550
768
  exports.ScopeManager = ScopeManager;
@@ -835,6 +835,7 @@ function findHardcodedStrings(ast, code, config) {
835
835
  issues.push({
836
836
  text: node.value.trim(),
837
837
  line: getLineNumber(position),
838
+ type: 'hardcoded',
838
839
  });
839
840
  lastSearchIndex = position + searchText.length;
840
841
  }
package/dist/esm/cli.js CHANGED
@@ -30,7 +30,7 @@ const program = new Command();
30
30
  program
31
31
  .name('i18next-cli')
32
32
  .description('A unified, high-performance i18next CLI.')
33
- .version('1.56.8'); // This string is replaced with the actual version at build time by rollup
33
+ .version('1.56.10'); // This string is replaced with the actual version at build time by rollup
34
34
  // new: global config override option
35
35
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
36
36
  program
@@ -163,7 +163,37 @@ class ASTVisitors {
163
163
  if (!node)
164
164
  return;
165
165
  let isNewScope = false;
166
+ let isNewClassScope = false;
166
167
  let paramTemporaries;
168
+ // ENTER CLASS SCOPE for class declarations / expressions and pre-register
169
+ // class field initializers so that later `this.<field>` references inside
170
+ // method bodies can inherit the namespace/keyPrefix from the field.
171
+ if (node.type === 'ClassDeclaration' ||
172
+ node.type === 'ClassExpression' ||
173
+ node.type === 'ClassDecl' ||
174
+ node.type === 'ClassExpr') {
175
+ this.scopeManager.enterClassScope();
176
+ isNewClassScope = true;
177
+ const classBody = Array.isArray(node.body) ? node.body : (node.body?.body ?? []);
178
+ for (const member of classBody) {
179
+ if (!member || typeof member !== 'object')
180
+ continue;
181
+ if (member.type === 'ClassProperty' ||
182
+ member.type === 'PrivateProperty' ||
183
+ member.type === 'PropertyDefinition' ||
184
+ member.type === 'ClassPrivateProperty') {
185
+ let fieldName;
186
+ const key = member.key;
187
+ if (key?.type === 'Identifier')
188
+ fieldName = key.value ?? key.name;
189
+ else if (key?.type === 'PrivateName')
190
+ fieldName = '#' + (key.value ?? key.name ?? key.id?.value ?? key.id?.name);
191
+ if (!fieldName)
192
+ continue;
193
+ this.scopeManager.registerClassField(fieldName, member.value);
194
+ }
195
+ }
196
+ }
167
197
  // ENTER SCOPE for functions
168
198
  // Accept many SWC/TS AST variants for function-like nodes (declarations, expressions, arrow functions)
169
199
  if (node.type === 'Function' ||
@@ -538,6 +568,10 @@ class ASTVisitors {
538
568
  }
539
569
  this.scopeManager.exitScope();
540
570
  }
571
+ // LEAVE CLASS SCOPE for classes
572
+ if (isNewClassScope) {
573
+ this.scopeManager.exitClassScope();
574
+ }
541
575
  }
542
576
  /**
543
577
  * If `node` is a call like `ARRAY.map(param => ...)` where ARRAY is a known
@@ -4,6 +4,11 @@ class ScopeManager {
4
4
  scopeStack = [];
5
5
  config;
6
6
  scope = new Map();
7
+ // Stack of class scopes; each entry maps a class field name (e.g. `myField`
8
+ // or `#privateField`) to the ScopeInfo derived from its initializer. Used to
9
+ // resolve `const { t } = this.field` and `const { t } = this.field()` patterns
10
+ // inside class methods.
11
+ thisFieldStack = [];
7
12
  // Track simple local constants with string literal values to resolve identifier args
8
13
  simpleConstants = new Map();
9
14
  // Track simple local constant objects with string literal property values
@@ -61,6 +66,49 @@ class ScopeManager {
61
66
  this.scope = new Map();
62
67
  this.simpleConstants.clear();
63
68
  this.simpleConstantObjects.clear();
69
+ this.thisFieldStack = [];
70
+ }
71
+ /**
72
+ * Pushes a new class scope used for tracking class field → ScopeInfo
73
+ * mappings. Should be called when entering a ClassDeclaration or
74
+ * ClassExpression.
75
+ */
76
+ enterClassScope() {
77
+ this.thisFieldStack.push(new Map());
78
+ }
79
+ /**
80
+ * Pops the current class scope. Should be called when leaving a
81
+ * ClassDeclaration or ClassExpression.
82
+ */
83
+ exitClassScope() {
84
+ this.thisFieldStack.pop();
85
+ }
86
+ /**
87
+ * Records a class field's ScopeInfo so that later `this.<field>` or
88
+ * `this.<field>()` accesses inside the class can inherit the namespace
89
+ * and keyPrefix from its initializer.
90
+ *
91
+ * @param fieldName - Field name (private fields are prefixed with `#`)
92
+ * @param info - Scope information derived from the field's initializer
93
+ */
94
+ setThisField(fieldName, info) {
95
+ if (this.thisFieldStack.length > 0) {
96
+ this.thisFieldStack[this.thisFieldStack.length - 1].set(fieldName, info);
97
+ }
98
+ }
99
+ /**
100
+ * Looks up a class field's ScopeInfo from the innermost class scope outward.
101
+ *
102
+ * @param fieldName - Field name (private fields are prefixed with `#`)
103
+ * @returns Scope information if found, undefined otherwise
104
+ */
105
+ getThisField(fieldName) {
106
+ for (let i = this.thisFieldStack.length - 1; i >= 0; i--) {
107
+ if (this.thisFieldStack[i].has(fieldName)) {
108
+ return this.thisFieldStack[i].get(fieldName);
109
+ }
110
+ }
111
+ return undefined;
64
112
  }
65
113
  /**
66
114
  * Enters a new variable scope by pushing a new scope map onto the stack.
@@ -217,6 +265,17 @@ class ScopeManager {
217
265
  }
218
266
  // continue processing; still may be a useTranslation/getFixedT call below
219
267
  }
268
+ // Handle: const { t } = this.#field OR const { t } = this.#field()
269
+ // Resolve the source `this.<field>` to its previously-registered ScopeInfo
270
+ // and propagate it onto the destructured variables.
271
+ const thisFieldName = ScopeManager.extractThisFieldName(init);
272
+ if (thisFieldName !== undefined) {
273
+ const sourceScope = this.getThisField(thisFieldName);
274
+ if (sourceScope) {
275
+ this.attachScopeToDestructuredVars(node, sourceScope);
276
+ return;
277
+ }
278
+ }
220
279
  // Determine the actual call expression, looking inside AwaitExpressions.
221
280
  const callExpr = init.type === 'AwaitExpression' && init.argument.type === 'CallExpression'
222
281
  ? init.argument
@@ -543,6 +602,165 @@ class ScopeManager {
543
602
  this.setVarInScope(targetVarName, { defaultNs: finalNs, keyPrefix: finalKeyPrefix });
544
603
  }
545
604
  }
605
+ /**
606
+ * Returns the field name being referenced when an expression takes the form
607
+ * `this.<field>` or `this.<field>()`. Strips the call wrapper if present and
608
+ * prefixes private fields with `#`.
609
+ *
610
+ * Returns undefined if the expression is not such an access.
611
+ */
612
+ static extractThisFieldName(init) {
613
+ let memberExpr;
614
+ if (init.type === 'MemberExpression') {
615
+ memberExpr = init;
616
+ }
617
+ else if (init.type === 'CallExpression' && init.callee.type === 'MemberExpression') {
618
+ memberExpr = init.callee;
619
+ }
620
+ if (!memberExpr)
621
+ return undefined;
622
+ if (memberExpr.object.type !== 'ThisExpression')
623
+ return undefined;
624
+ const prop = memberExpr.property;
625
+ if (prop?.type === 'Identifier')
626
+ return prop.value;
627
+ if (prop?.type === 'PrivateName')
628
+ return '#' + prop.value;
629
+ return undefined;
630
+ }
631
+ /**
632
+ * Attaches the given ScopeInfo to all destructured variables of a variable
633
+ * declarator (`const { t } = ...`, `const [t] = ...`, or `const t = ...`).
634
+ */
635
+ attachScopeToDestructuredVars(node, info) {
636
+ if (node.id.type === 'ObjectPattern') {
637
+ for (const prop of node.id.properties) {
638
+ if (prop.type === 'AssignmentPatternProperty' && prop.key.type === 'Identifier') {
639
+ this.setVarInScope(prop.key.value, info);
640
+ }
641
+ if (prop.type === 'KeyValuePatternProperty' && prop.value.type === 'Identifier') {
642
+ this.setVarInScope(prop.value.value, info);
643
+ // Also store under the pattern key name so legacy comment-scope
644
+ // lookups by the original property name continue to work.
645
+ if (prop.key.type === 'Identifier' && (prop.key.value === 't' || prop.key.value === 'getFixedT')) {
646
+ this.scope.set(prop.value.value, { defaultNs: info.defaultNs, keyPrefix: info.keyPrefix });
647
+ }
648
+ }
649
+ }
650
+ }
651
+ else if (node.id.type === 'Identifier') {
652
+ this.setVarInScope(node.id.value, info);
653
+ }
654
+ else if (node.id.type === 'ArrayPattern') {
655
+ const firstElement = node.id.elements[0];
656
+ if (firstElement?.type === 'Identifier') {
657
+ this.setVarInScope(firstElement.value, info);
658
+ }
659
+ }
660
+ }
661
+ /**
662
+ * Registers a class field's initializer with the current class scope so that
663
+ * later `const { t } = this.<field>` or `const { t } = this.<field>()` calls
664
+ * inside the class body inherit the namespace and keyPrefix from the field
665
+ * initializer.
666
+ *
667
+ * Recognizes the same useTranslation-like patterns supported on regular
668
+ * variable declarations (configured via `useTranslationNames`).
669
+ *
670
+ * @param fieldName - Field name (private fields are prefixed with `#`)
671
+ * @param value - The field initializer expression
672
+ */
673
+ registerClassField(fieldName, value) {
674
+ if (!value)
675
+ return;
676
+ if (this.thisFieldStack.length === 0)
677
+ return;
678
+ const callExpr = value.type === 'AwaitExpression' && value.argument.type === 'CallExpression'
679
+ ? value.argument
680
+ : value.type === 'CallExpression'
681
+ ? value
682
+ : null;
683
+ if (!callExpr)
684
+ return;
685
+ const callee = callExpr.callee;
686
+ if (callee.type !== 'Identifier')
687
+ return;
688
+ const hookConfig = this.getUseTranslationConfig(callee.value);
689
+ if (!hookConfig)
690
+ return;
691
+ const info = this.computeScopeInfoFromHookCall(callExpr, hookConfig);
692
+ if (info.defaultNs || info.keyPrefix) {
693
+ this.setThisField(fieldName, info);
694
+ }
695
+ }
696
+ /**
697
+ * Computes ScopeInfo (defaultNs, keyPrefix) for a useTranslation-style call
698
+ * given a hook configuration that describes argument positions. Mirrors the
699
+ * extraction performed by handleUseTranslationDeclarator without attaching
700
+ * the result to any variable.
701
+ */
702
+ computeScopeInfoFromHookCall(callExpr, hookConfig) {
703
+ const nsArgIndex = hookConfig.nsArg ?? 0;
704
+ const kpArgIndex = hookConfig.keyPrefixArg ?? 1;
705
+ let defaultNs;
706
+ let keyPrefix;
707
+ const first = callExpr.arguments?.[0]?.expression;
708
+ const second = callExpr.arguments?.[1]?.expression;
709
+ const third = callExpr.arguments?.[2]?.expression;
710
+ const looksLikeLanguage = (s) => /^[a-z]{2,3}([-_][A-Za-z0-9-]+)?$/i.test(s);
711
+ const isBuiltInLngNsForm = hookConfig.name === 'useTranslation' &&
712
+ first?.type === 'StringLiteral' &&
713
+ second?.type === 'StringLiteral' &&
714
+ looksLikeLanguage(first.value);
715
+ let kpArg;
716
+ if (isBuiltInLngNsForm) {
717
+ defaultNs = second.value;
718
+ kpArg = third;
719
+ }
720
+ else {
721
+ if (nsArgIndex !== -1) {
722
+ const nsNode = callExpr.arguments?.[nsArgIndex]?.expression;
723
+ if (nsNode?.type === 'StringLiteral') {
724
+ defaultNs = nsNode.value;
725
+ }
726
+ else if (nsNode?.type === 'Identifier') {
727
+ defaultNs = this.resolveSimpleStringIdentifier(nsNode.value);
728
+ }
729
+ else if (nsNode?.type === 'MemberExpression') {
730
+ defaultNs = this.resolveSimpleMemberExpression(nsNode);
731
+ }
732
+ else if (nsNode?.type === 'ArrayExpression') {
733
+ const firstEl = nsNode.elements[0]?.expression;
734
+ if (firstEl?.type === 'StringLiteral') {
735
+ defaultNs = firstEl.value;
736
+ }
737
+ else if (firstEl?.type === 'Identifier') {
738
+ defaultNs = this.resolveSimpleStringIdentifier(firstEl.value);
739
+ }
740
+ }
741
+ }
742
+ kpArg = kpArgIndex === -1 ? undefined : callExpr.arguments?.[kpArgIndex]?.expression;
743
+ }
744
+ if (kpArg?.type === 'ObjectExpression') {
745
+ const kp = getObjectPropValue(kpArg, 'keyPrefix', this.resolveSimpleStringIdentifier.bind(this));
746
+ if (typeof kp === 'string') {
747
+ keyPrefix = kp;
748
+ }
749
+ }
750
+ else if (kpArg?.type === 'StringLiteral') {
751
+ keyPrefix = kpArg.value;
752
+ }
753
+ else if (kpArg?.type === 'Identifier') {
754
+ keyPrefix = this.resolveSimpleStringIdentifier(kpArg.value);
755
+ }
756
+ else if (kpArg?.type === 'TemplateLiteral') {
757
+ const tpl = kpArg;
758
+ if ((tpl.expressions || []).length === 0) {
759
+ keyPrefix = tpl.quasis?.[0]?.cooked ?? undefined;
760
+ }
761
+ }
762
+ return { defaultNs, keyPrefix };
763
+ }
546
764
  }
547
765
 
548
766
  export { ScopeManager };
@@ -833,6 +833,7 @@ function findHardcodedStrings(ast, code, config) {
833
833
  issues.push({
834
834
  text: node.value.trim(),
835
835
  line: getLineNumber(position),
836
+ type: 'hardcoded',
836
837
  });
837
838
  lastSearchIndex = position + searchText.length;
838
839
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.56.8",
3
+ "version": "1.56.10",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +1 @@
1
- {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAQ,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC7G,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AAItE;;;;;;;;;;;;;;;;;;;;;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,KAAK,CAAiB;IAE9B,IAAW,UAAU,gBAEpB;IAED,SAAgB,YAAY,EAAE,YAAY,CAAA;IAC1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoB;IACvD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAuB;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,WAAW,CAAa;IAEhC;;;;;;OAMG;gBAED,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,eAAe,EACvB,kBAAkB,CAAC,EAAE,kBAAkB;IAiCzC;;;;;;;;;;;;;OAaG;IACI,mBAAmB,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAK/C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IA8CzB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAUjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IAoXZ;;;;;;;;OAQG;IACH,OAAO,CAAC,gCAAgC;IAqDxC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;;;;;;;OAQG;IACI,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI5D;;OAEG;IACI,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxD;;;;;;OAMG;IACI,cAAc,IAAK,MAAM;IAIhC;;OAEG;IACI,cAAc,IAAK,MAAM;CAGjC"}
1
+ {"version":3,"file":"ast-visitors.d.ts","sourceRoot":"","sources":["../../../src/extractor/core/ast-visitors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAQ,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC7G,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AAItE;;;;;;;;;;;;;;;;;;;;;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,KAAK,CAAiB;IAE9B,IAAW,UAAU,gBAEpB;IAED,SAAgB,YAAY,EAAE,YAAY,CAAA;IAC1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoB;IACvD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAuB;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,WAAW,CAAa;IAEhC;;;;;;OAMG;gBAED,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,eAAe,EACvB,kBAAkB,CAAC,EAAE,kBAAkB;IAiCzC;;;;;;;;;;;;;OAaG;IACI,mBAAmB,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAK/C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IA8CzB;;;;;OAKG;IACI,KAAK,CAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAUjC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,IAAI;IAyZZ;;;;;;;;OAQG;IACH,OAAO,CAAC,gCAAgC;IAqDxC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;;;;;;;OAQG;IACI,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI5D;;OAEG;IACI,cAAc,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKxD;;;;;;OAMG;IACI,cAAc,IAAK,MAAM;IAIhC;;OAEG;IACI,cAAc,IAAK,MAAM;CAGjC"}
@@ -1,9 +1,10 @@
1
- import type { VariableDeclarator } from '@swc/core';
1
+ import type { Expression, VariableDeclarator } from '@swc/core';
2
2
  import type { ScopeInfo, I18nextToolkitConfig } from '../../types.js';
3
3
  export declare class ScopeManager {
4
4
  private scopeStack;
5
5
  private config;
6
6
  private scope;
7
+ private thisFieldStack;
7
8
  private simpleConstants;
8
9
  private simpleConstantObjects;
9
10
  private sharedConstants;
@@ -27,6 +28,33 @@ export declare class ScopeManager {
27
28
  * leak between files.
28
29
  */
29
30
  reset(): void;
31
+ /**
32
+ * Pushes a new class scope used for tracking class field → ScopeInfo
33
+ * mappings. Should be called when entering a ClassDeclaration or
34
+ * ClassExpression.
35
+ */
36
+ enterClassScope(): void;
37
+ /**
38
+ * Pops the current class scope. Should be called when leaving a
39
+ * ClassDeclaration or ClassExpression.
40
+ */
41
+ exitClassScope(): void;
42
+ /**
43
+ * Records a class field's ScopeInfo so that later `this.<field>` or
44
+ * `this.<field>()` accesses inside the class can inherit the namespace
45
+ * and keyPrefix from its initializer.
46
+ *
47
+ * @param fieldName - Field name (private fields are prefixed with `#`)
48
+ * @param info - Scope information derived from the field's initializer
49
+ */
50
+ setThisField(fieldName: string, info: ScopeInfo): void;
51
+ /**
52
+ * Looks up a class field's ScopeInfo from the innermost class scope outward.
53
+ *
54
+ * @param fieldName - Field name (private fields are prefixed with `#`)
55
+ * @returns Scope information if found, undefined otherwise
56
+ */
57
+ getThisField(fieldName: string): ScopeInfo | undefined;
30
58
  /**
31
59
  * Enters a new variable scope by pushing a new scope map onto the stack.
32
60
  * Used when entering functions to isolate variable declarations.
@@ -131,5 +159,38 @@ export declare class ScopeManager {
131
159
  * resulting scope to the newly declared variable.
132
160
  */
133
161
  private handleGetFixedTFromVariableDeclarator;
162
+ /**
163
+ * Returns the field name being referenced when an expression takes the form
164
+ * `this.<field>` or `this.<field>()`. Strips the call wrapper if present and
165
+ * prefixes private fields with `#`.
166
+ *
167
+ * Returns undefined if the expression is not such an access.
168
+ */
169
+ private static extractThisFieldName;
170
+ /**
171
+ * Attaches the given ScopeInfo to all destructured variables of a variable
172
+ * declarator (`const { t } = ...`, `const [t] = ...`, or `const t = ...`).
173
+ */
174
+ private attachScopeToDestructuredVars;
175
+ /**
176
+ * Registers a class field's initializer with the current class scope so that
177
+ * later `const { t } = this.<field>` or `const { t } = this.<field>()` calls
178
+ * inside the class body inherit the namespace and keyPrefix from the field
179
+ * initializer.
180
+ *
181
+ * Recognizes the same useTranslation-like patterns supported on regular
182
+ * variable declarations (configured via `useTranslationNames`).
183
+ *
184
+ * @param fieldName - Field name (private fields are prefixed with `#`)
185
+ * @param value - The field initializer expression
186
+ */
187
+ registerClassField(fieldName: string, value: Expression | null | undefined): void;
188
+ /**
189
+ * Computes ScopeInfo (defaultNs, keyPrefix) for a useTranslation-style call
190
+ * given a hook configuration that describes argument positions. Mirrors the
191
+ * extraction performed by handleUseTranslationDeclarator without attaching
192
+ * the result to any variable.
193
+ */
194
+ private computeScopeInfoFromHookCall;
134
195
  }
135
196
  //# sourceMappingURL=scope-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/scope-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,kBAAkB,EAMnB,MAAM,WAAW,CAAA;AAClB,OAAO,KAAK,EAAE,SAAS,EAA4B,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAG/F,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,KAAK,CAAqE;IAGlF,OAAO,CAAC,eAAe,CAAiC;IAGxD,OAAO,CAAC,qBAAqB,CAAiD;IAI9E,OAAO,CAAC,eAAe,CAAiC;IACxD,OAAO,CAAC,qBAAqB,CAAiD;gBAEjE,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC;IAI1D;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAcjC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,+BAA+B;IAgB9C;;;;;;OAMG;IACI,KAAK,IAAK,IAAI;IAOrB;;;OAGG;IACH,UAAU,IAAK,IAAI;IAInB;;;OAGG;IACH,SAAS,IAAK,IAAI;IAIlB;;;;;;OAMG;IACH,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAUnD;;;;;;OAMG;IACH,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkBrD,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACI,6BAA6B,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIvE;;;OAGG;IACH,OAAO,CAAC,6BAA6B;IAqBrC;;;;;;;;;;OAUG;IACH,wBAAwB,CAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI;IA0FzD;;;;;;;;OAQG;IACH,OAAO,CAAC,+BAA+B;IA0GvC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,8BAA8B;IAmFtC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,yBAAyB;IAiBjC;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAexB;;;;;;;;;OASG;IACH,OAAO,CAAC,qCAAqC;CAoB9C"}
1
+ {"version":3,"file":"scope-manager.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/scope-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EAEV,kBAAkB,EAMnB,MAAM,WAAW,CAAA;AAClB,OAAO,KAAK,EAAE,SAAS,EAA4B,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAG/F,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,KAAK,CAAqE;IAMlF,OAAO,CAAC,cAAc,CAAoC;IAG1D,OAAO,CAAC,eAAe,CAAiC;IAGxD,OAAO,CAAC,qBAAqB,CAAiD;IAI9E,OAAO,CAAC,eAAe,CAAiC;IACxD,OAAO,CAAC,qBAAqB,CAAiD;gBAEjE,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC;IAI1D;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAcjC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,+BAA+B;IAgB9C;;;;;;OAMG;IACI,KAAK,IAAK,IAAI;IAQrB;;;;OAIG;IACH,eAAe,IAAK,IAAI;IAIxB;;;OAGG;IACH,cAAc,IAAK,IAAI;IAIvB;;;;;;;OAOG;IACH,YAAY,CAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAMvD;;;;;OAKG;IACH,YAAY,CAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IASvD;;;OAGG;IACH,UAAU,IAAK,IAAI;IAInB;;;OAGG;IACH,SAAS,IAAK,IAAI;IAIlB;;;;;;OAMG;IACH,aAAa,CAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAUnD;;;;;;OAMG;IACH,eAAe,CAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkBrD,OAAO,CAAC,uBAAuB;IAoB/B;;OAEG;IACI,6BAA6B,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIvE;;;OAGG;IACH,OAAO,CAAC,6BAA6B;IAqBrC;;;;;;;;;;OAUG;IACH,wBAAwB,CAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAsGzD;;;;;;;;OAQG;IACH,OAAO,CAAC,+BAA+B;IA0GvC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,8BAA8B;IAmFtC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,yBAAyB;IAiBjC;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAexB;;;;;;;;;OASG;IACH,OAAO,CAAC,qCAAqC;IAqB7C;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAgBnC;;;OAGG;IACH,OAAO,CAAC,6BAA6B;IAyBrC;;;;;;;;;;;OAWG;IACH,kBAAkB,CAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAwBlF;;;;;OAKG;IACH,OAAO,CAAC,4BAA4B;CA+DrC"}