i18next-cli 1.56.1 → 1.56.3

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/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.1'); // This string is replaced with the actual version at build time by rollup
35
+ .version('1.56.3'); // 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
@@ -30,6 +30,64 @@ class JSXHandler {
30
30
  return undefined;
31
31
  return astUtils.lineColumnFromOffset(this.getCurrentCode(), node.span.start);
32
32
  }
33
+ /**
34
+ * Warns about `<Trans>Hello <b>{name}</b></Trans>` style children where a
35
+ * bare identifier is used as a React child. react-i18next inlines the value
36
+ * at runtime, producing a key like `"Hello <1>meow</1>"`, but the extractor
37
+ * serializes the identifier name as `"{{name}}"`. The two never match, and
38
+ * even when an `i18nKey` is set, the placeholder `{{name}}` cannot be
39
+ * interpolated without a `values={{ name }}` prop — it renders literally.
40
+ *
41
+ * We keep the existing extraction behaviour so projects that already rely on
42
+ * the `{{name}}` output (with a matching `values` prop) aren't broken, and
43
+ * instead surface a diagnostic pointing users at the runtime mismatch.
44
+ */
45
+ warnOnBareIdentifierTransChildren(node, elementName) {
46
+ const bareIdentifiers = [];
47
+ const visit = (children) => {
48
+ for (const child of children) {
49
+ if (child.type === 'JSXExpressionContainer') {
50
+ const inner = this.unwrapExpression(child.expression);
51
+ if (inner && inner.type === 'Identifier') {
52
+ bareIdentifiers.push({ name: inner.value, span: child.span });
53
+ }
54
+ }
55
+ else if (child.type === 'JSXElement') {
56
+ visit(child.children);
57
+ }
58
+ else if (child.type === 'JSXFragment') {
59
+ visit(child.children);
60
+ }
61
+ }
62
+ };
63
+ visit(node.children);
64
+ if (bareIdentifiers.length === 0)
65
+ return;
66
+ const warn = this.pluginContext?.logger?.warn?.bind(this.pluginContext.logger) ??
67
+ console.warn.bind(console);
68
+ for (const { name, span } of bareIdentifiers) {
69
+ const loc = astUtils.lineColumnFromOffset(this.getCurrentCode(), span.start);
70
+ const where = loc
71
+ ? `${this.getCurrentFile()}:${loc.line}:${loc.column}`
72
+ : this.getCurrentFile();
73
+ warn(`<${elementName}> child {${name}} at ${where} won't match at runtime — react-i18next inlines the value (e.g. "<1>meow</1>"), but extraction produces "<1>{{${name}}}</1>". Use {{${name}}} (double braces) with values={{ ${name} }} for interpolation, or inline the value if it isn't meant to be translated.`);
74
+ }
75
+ }
76
+ /**
77
+ * Unwraps TS type-assertion and parenthesis wrappers so we can inspect the
78
+ * underlying expression type (mirrors behaviour in jsx-parser).
79
+ */
80
+ unwrapExpression(expr) {
81
+ if (!expr)
82
+ return expr;
83
+ if (expr.type === 'TsAsExpression' || expr.type === 'TsSatisfiesExpression') {
84
+ return this.unwrapExpression(expr.expression);
85
+ }
86
+ if (expr.type === 'ParenthesisExpression') {
87
+ return this.unwrapExpression(expr.expression);
88
+ }
89
+ return expr;
90
+ }
33
91
  /**
34
92
  * Processes JSX elements to extract translation keys from Trans components.
35
93
  *
@@ -42,6 +100,7 @@ class JSXHandler {
42
100
  handleJSXElement(node, getScopeInfo) {
43
101
  const elementName = this.getElementName(node);
44
102
  if (elementName && (this.config.extract.transComponents || ['Trans']).includes(elementName)) {
103
+ this.warnOnBareIdentifierTransChildren(node, elementName);
45
104
  let extractedAttributes = null;
46
105
  try {
47
106
  extractedAttributes = jsxParser.extractFromTransComponent(node, this.config);
@@ -486,14 +486,37 @@ class ScopeManager {
486
486
  const args = callExpr.arguments;
487
487
  // getFixedT(lng, ns, keyPrefix)
488
488
  // We ignore the first argument (lng) for key extraction.
489
- const nsArg = args[1]?.expression;
490
- const keyPrefixArg = args[2]?.expression;
491
- const defaultNs = (nsArg?.type === 'StringLiteral') ? nsArg.value : undefined;
492
- const keyPrefix = (keyPrefixArg?.type === 'StringLiteral') ? keyPrefixArg.value : undefined;
489
+ const defaultNs = this.resolveStringArg(args[1]?.expression);
490
+ const keyPrefix = this.resolveStringArg(args[2]?.expression);
493
491
  if (defaultNs || keyPrefix) {
494
492
  this.setVarInScope(variableName, { defaultNs, keyPrefix });
495
493
  }
496
494
  }
495
+ /**
496
+ * Resolves a call argument expression to a string value by handling string
497
+ * literals, previously-declared local/shared constants (Identifier), member
498
+ * expressions referencing constant objects, and simple template literals
499
+ * without interpolation. Returns undefined when the expression cannot be
500
+ * resolved statically.
501
+ */
502
+ resolveStringArg(node) {
503
+ if (!node)
504
+ return undefined;
505
+ const unwrapped = ScopeManager.unwrapTsExpression(node);
506
+ if (unwrapped.type === 'StringLiteral')
507
+ return unwrapped.value;
508
+ if (unwrapped.type === 'Identifier')
509
+ return this.resolveSimpleStringIdentifier(unwrapped.value);
510
+ if (unwrapped.type === 'MemberExpression')
511
+ return this.resolveSimpleMemberExpression(unwrapped);
512
+ if (unwrapped.type === 'TemplateLiteral') {
513
+ const tpl = unwrapped;
514
+ if ((tpl.expressions || []).length === 0) {
515
+ return tpl.quasis?.[0]?.cooked ?? undefined;
516
+ }
517
+ }
518
+ return undefined;
519
+ }
497
520
  /**
498
521
  * Handles cases where a getFixedT-like function is a variable (from a custom hook)
499
522
  * and is invoked to produce a bound `t` function, e.g.:
@@ -513,10 +536,8 @@ class ScopeManager {
513
536
  return;
514
537
  const args = callExpr.arguments;
515
538
  // getFixedT(lng, ns, keyPrefix)
516
- const nsArg = args[1]?.expression;
517
- const keyPrefixArg = args[2]?.expression;
518
- const nsFromCall = (nsArg?.type === 'StringLiteral') ? nsArg.value : undefined;
519
- const keyPrefixFromCall = (keyPrefixArg?.type === 'StringLiteral') ? keyPrefixArg.value : undefined;
539
+ const nsFromCall = this.resolveStringArg(args[1]?.expression);
540
+ const keyPrefixFromCall = this.resolveStringArg(args[2]?.expression);
520
541
  // Merge: call args take precedence over source scope values
521
542
  const finalNs = nsFromCall ?? sourceScope.defaultNs;
522
543
  const finalKeyPrefix = keyPrefixFromCall ?? sourceScope.keyPrefix;
@@ -755,14 +755,22 @@ function findHardcodedStrings(ast, code, config) {
755
755
  }
756
756
  if (node.type === 'StringLiteral') {
757
757
  const parent = currentAncestors[currentAncestors.length - 2];
758
+ const grandparent = currentAncestors[currentAncestors.length - 3];
758
759
  // Determine whether this attribute is inside any ignored element (handles nested Trans etc.)
759
760
  const insideIgnored = isWithinIgnoredElement(currentAncestors);
760
- if (parent?.type === 'JSXAttribute' && !insideIgnored) {
761
- const rawAttrName = extractAttrName(parent.name);
761
+ // A StringLiteral can be an attribute value in two forms:
762
+ // <tag attr="value" /> → parent is JSXAttribute
763
+ // <tag attr={"value"} /> → parent is JSXExpressionContainer, grandparent is JSXAttribute
764
+ const attrNode = parent?.type === 'JSXAttribute'
765
+ ? parent
766
+ : (parent?.type === 'JSXExpressionContainer' && grandparent?.type === 'JSXAttribute' ? grandparent : null);
767
+ if (attrNode && !insideIgnored) {
768
+ const rawAttrName = extractAttrName(attrNode.name);
762
769
  const attrNameLower = rawAttrName ? String(rawAttrName).toLowerCase() : null;
763
770
  // Check tag-level acceptance if acceptedTagsSet provided: attributes should only be considered
764
- // when the nearest enclosing element is accepted.
765
- const parentElement = currentAncestors.slice(0, -2).reverse().find(a => a && typeof a === 'object' && (a.type === 'JSXElement' || a.type === 'JSXOpeningElement' || a.type === 'JSXSelfClosingElement'));
771
+ // when the nearest enclosing element is accepted. Use the ancestors above the attrNode.
772
+ const attrNodeIdx = currentAncestors.indexOf(attrNode);
773
+ const parentElement = currentAncestors.slice(0, attrNodeIdx).reverse().find(a => a && typeof a === 'object' && (a.type === 'JSXElement' || a.type === 'JSXOpeningElement' || a.type === 'JSXSelfClosingElement'));
766
774
  if (acceptedTagsSet && parentElement) {
767
775
  const parentName = extractJSXName(parentElement);
768
776
  if (!parentName || !acceptedTagsSet.has(String(parentName).toLowerCase())) {
@@ -786,6 +794,21 @@ function findHardcodedStrings(ast, code, config) {
786
794
  }
787
795
  }
788
796
  }
797
+ // Hardcoded string inside a JSX expression container used as element child:
798
+ // <tag>{"hello"}</tag> → parent is JSXExpressionContainer, grandparent is JSXElement/JSXFragment
799
+ // Apply the same filters used for JSXText so this behaves like raw text.
800
+ const isJsxChildExpression = parent?.type === 'JSXExpressionContainer' &&
801
+ (grandparent?.type === 'JSXElement' || grandparent?.type === 'JSXFragment');
802
+ if (isJsxChildExpression && !insideIgnored) {
803
+ // Respect attribute-only mode: when acceptedAttributes is set without acceptedTags,
804
+ // only attribute strings are linted — skip JSX child text.
805
+ if (!(acceptedAttributesSet && !acceptedTagsSet)) {
806
+ const text = node.value.trim();
807
+ if (text && text.length > 1 && text !== '...' && !isUrlOrPath(text) && isNaN(Number(text)) && !text.startsWith('{{')) {
808
+ nodesToLint.push(node);
809
+ }
810
+ }
811
+ }
789
812
  }
790
813
  // Recurse into children
791
814
  for (const key of Object.keys(node)) {
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.1'); // This string is replaced with the actual version at build time by rollup
33
+ .version('1.56.3'); // 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
@@ -28,6 +28,64 @@ class JSXHandler {
28
28
  return undefined;
29
29
  return lineColumnFromOffset(this.getCurrentCode(), node.span.start);
30
30
  }
31
+ /**
32
+ * Warns about `<Trans>Hello <b>{name}</b></Trans>` style children where a
33
+ * bare identifier is used as a React child. react-i18next inlines the value
34
+ * at runtime, producing a key like `"Hello <1>meow</1>"`, but the extractor
35
+ * serializes the identifier name as `"{{name}}"`. The two never match, and
36
+ * even when an `i18nKey` is set, the placeholder `{{name}}` cannot be
37
+ * interpolated without a `values={{ name }}` prop — it renders literally.
38
+ *
39
+ * We keep the existing extraction behaviour so projects that already rely on
40
+ * the `{{name}}` output (with a matching `values` prop) aren't broken, and
41
+ * instead surface a diagnostic pointing users at the runtime mismatch.
42
+ */
43
+ warnOnBareIdentifierTransChildren(node, elementName) {
44
+ const bareIdentifiers = [];
45
+ const visit = (children) => {
46
+ for (const child of children) {
47
+ if (child.type === 'JSXExpressionContainer') {
48
+ const inner = this.unwrapExpression(child.expression);
49
+ if (inner && inner.type === 'Identifier') {
50
+ bareIdentifiers.push({ name: inner.value, span: child.span });
51
+ }
52
+ }
53
+ else if (child.type === 'JSXElement') {
54
+ visit(child.children);
55
+ }
56
+ else if (child.type === 'JSXFragment') {
57
+ visit(child.children);
58
+ }
59
+ }
60
+ };
61
+ visit(node.children);
62
+ if (bareIdentifiers.length === 0)
63
+ return;
64
+ const warn = this.pluginContext?.logger?.warn?.bind(this.pluginContext.logger) ??
65
+ console.warn.bind(console);
66
+ for (const { name, span } of bareIdentifiers) {
67
+ const loc = lineColumnFromOffset(this.getCurrentCode(), span.start);
68
+ const where = loc
69
+ ? `${this.getCurrentFile()}:${loc.line}:${loc.column}`
70
+ : this.getCurrentFile();
71
+ warn(`<${elementName}> child {${name}} at ${where} won't match at runtime — react-i18next inlines the value (e.g. "<1>meow</1>"), but extraction produces "<1>{{${name}}}</1>". Use {{${name}}} (double braces) with values={{ ${name} }} for interpolation, or inline the value if it isn't meant to be translated.`);
72
+ }
73
+ }
74
+ /**
75
+ * Unwraps TS type-assertion and parenthesis wrappers so we can inspect the
76
+ * underlying expression type (mirrors behaviour in jsx-parser).
77
+ */
78
+ unwrapExpression(expr) {
79
+ if (!expr)
80
+ return expr;
81
+ if (expr.type === 'TsAsExpression' || expr.type === 'TsSatisfiesExpression') {
82
+ return this.unwrapExpression(expr.expression);
83
+ }
84
+ if (expr.type === 'ParenthesisExpression') {
85
+ return this.unwrapExpression(expr.expression);
86
+ }
87
+ return expr;
88
+ }
31
89
  /**
32
90
  * Processes JSX elements to extract translation keys from Trans components.
33
91
  *
@@ -40,6 +98,7 @@ class JSXHandler {
40
98
  handleJSXElement(node, getScopeInfo) {
41
99
  const elementName = this.getElementName(node);
42
100
  if (elementName && (this.config.extract.transComponents || ['Trans']).includes(elementName)) {
101
+ this.warnOnBareIdentifierTransChildren(node, elementName);
43
102
  let extractedAttributes = null;
44
103
  try {
45
104
  extractedAttributes = extractFromTransComponent(node, this.config);
@@ -484,14 +484,37 @@ class ScopeManager {
484
484
  const args = callExpr.arguments;
485
485
  // getFixedT(lng, ns, keyPrefix)
486
486
  // We ignore the first argument (lng) for key extraction.
487
- const nsArg = args[1]?.expression;
488
- const keyPrefixArg = args[2]?.expression;
489
- const defaultNs = (nsArg?.type === 'StringLiteral') ? nsArg.value : undefined;
490
- const keyPrefix = (keyPrefixArg?.type === 'StringLiteral') ? keyPrefixArg.value : undefined;
487
+ const defaultNs = this.resolveStringArg(args[1]?.expression);
488
+ const keyPrefix = this.resolveStringArg(args[2]?.expression);
491
489
  if (defaultNs || keyPrefix) {
492
490
  this.setVarInScope(variableName, { defaultNs, keyPrefix });
493
491
  }
494
492
  }
493
+ /**
494
+ * Resolves a call argument expression to a string value by handling string
495
+ * literals, previously-declared local/shared constants (Identifier), member
496
+ * expressions referencing constant objects, and simple template literals
497
+ * without interpolation. Returns undefined when the expression cannot be
498
+ * resolved statically.
499
+ */
500
+ resolveStringArg(node) {
501
+ if (!node)
502
+ return undefined;
503
+ const unwrapped = ScopeManager.unwrapTsExpression(node);
504
+ if (unwrapped.type === 'StringLiteral')
505
+ return unwrapped.value;
506
+ if (unwrapped.type === 'Identifier')
507
+ return this.resolveSimpleStringIdentifier(unwrapped.value);
508
+ if (unwrapped.type === 'MemberExpression')
509
+ return this.resolveSimpleMemberExpression(unwrapped);
510
+ if (unwrapped.type === 'TemplateLiteral') {
511
+ const tpl = unwrapped;
512
+ if ((tpl.expressions || []).length === 0) {
513
+ return tpl.quasis?.[0]?.cooked ?? undefined;
514
+ }
515
+ }
516
+ return undefined;
517
+ }
495
518
  /**
496
519
  * Handles cases where a getFixedT-like function is a variable (from a custom hook)
497
520
  * and is invoked to produce a bound `t` function, e.g.:
@@ -511,10 +534,8 @@ class ScopeManager {
511
534
  return;
512
535
  const args = callExpr.arguments;
513
536
  // getFixedT(lng, ns, keyPrefix)
514
- const nsArg = args[1]?.expression;
515
- const keyPrefixArg = args[2]?.expression;
516
- const nsFromCall = (nsArg?.type === 'StringLiteral') ? nsArg.value : undefined;
517
- const keyPrefixFromCall = (keyPrefixArg?.type === 'StringLiteral') ? keyPrefixArg.value : undefined;
537
+ const nsFromCall = this.resolveStringArg(args[1]?.expression);
538
+ const keyPrefixFromCall = this.resolveStringArg(args[2]?.expression);
518
539
  // Merge: call args take precedence over source scope values
519
540
  const finalNs = nsFromCall ?? sourceScope.defaultNs;
520
541
  const finalKeyPrefix = keyPrefixFromCall ?? sourceScope.keyPrefix;
@@ -753,14 +753,22 @@ function findHardcodedStrings(ast, code, config) {
753
753
  }
754
754
  if (node.type === 'StringLiteral') {
755
755
  const parent = currentAncestors[currentAncestors.length - 2];
756
+ const grandparent = currentAncestors[currentAncestors.length - 3];
756
757
  // Determine whether this attribute is inside any ignored element (handles nested Trans etc.)
757
758
  const insideIgnored = isWithinIgnoredElement(currentAncestors);
758
- if (parent?.type === 'JSXAttribute' && !insideIgnored) {
759
- const rawAttrName = extractAttrName(parent.name);
759
+ // A StringLiteral can be an attribute value in two forms:
760
+ // <tag attr="value" /> → parent is JSXAttribute
761
+ // <tag attr={"value"} /> → parent is JSXExpressionContainer, grandparent is JSXAttribute
762
+ const attrNode = parent?.type === 'JSXAttribute'
763
+ ? parent
764
+ : (parent?.type === 'JSXExpressionContainer' && grandparent?.type === 'JSXAttribute' ? grandparent : null);
765
+ if (attrNode && !insideIgnored) {
766
+ const rawAttrName = extractAttrName(attrNode.name);
760
767
  const attrNameLower = rawAttrName ? String(rawAttrName).toLowerCase() : null;
761
768
  // Check tag-level acceptance if acceptedTagsSet provided: attributes should only be considered
762
- // when the nearest enclosing element is accepted.
763
- const parentElement = currentAncestors.slice(0, -2).reverse().find(a => a && typeof a === 'object' && (a.type === 'JSXElement' || a.type === 'JSXOpeningElement' || a.type === 'JSXSelfClosingElement'));
769
+ // when the nearest enclosing element is accepted. Use the ancestors above the attrNode.
770
+ const attrNodeIdx = currentAncestors.indexOf(attrNode);
771
+ const parentElement = currentAncestors.slice(0, attrNodeIdx).reverse().find(a => a && typeof a === 'object' && (a.type === 'JSXElement' || a.type === 'JSXOpeningElement' || a.type === 'JSXSelfClosingElement'));
764
772
  if (acceptedTagsSet && parentElement) {
765
773
  const parentName = extractJSXName(parentElement);
766
774
  if (!parentName || !acceptedTagsSet.has(String(parentName).toLowerCase())) {
@@ -784,6 +792,21 @@ function findHardcodedStrings(ast, code, config) {
784
792
  }
785
793
  }
786
794
  }
795
+ // Hardcoded string inside a JSX expression container used as element child:
796
+ // <tag>{"hello"}</tag> → parent is JSXExpressionContainer, grandparent is JSXElement/JSXFragment
797
+ // Apply the same filters used for JSXText so this behaves like raw text.
798
+ const isJsxChildExpression = parent?.type === 'JSXExpressionContainer' &&
799
+ (grandparent?.type === 'JSXElement' || grandparent?.type === 'JSXFragment');
800
+ if (isJsxChildExpression && !insideIgnored) {
801
+ // Respect attribute-only mode: when acceptedAttributes is set without acceptedTags,
802
+ // only attribute strings are linted — skip JSX child text.
803
+ if (!(acceptedAttributesSet && !acceptedTagsSet)) {
804
+ const text = node.value.trim();
805
+ if (text && text.length > 1 && text !== '...' && !isUrlOrPath(text) && isNaN(Number(text)) && !text.startsWith('{{')) {
806
+ nodesToLint.push(node);
807
+ }
808
+ }
809
+ }
787
810
  }
788
811
  // Recurse into children
789
812
  for (const key of Object.keys(node)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.56.1",
3
+ "version": "1.56.3",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,24 @@ export declare class JSXHandler {
14
14
  * so we can use them directly.
15
15
  */
16
16
  private getLocationFromNode;
17
+ /**
18
+ * Warns about `<Trans>Hello <b>{name}</b></Trans>` style children where a
19
+ * bare identifier is used as a React child. react-i18next inlines the value
20
+ * at runtime, producing a key like `"Hello <1>meow</1>"`, but the extractor
21
+ * serializes the identifier name as `"{{name}}"`. The two never match, and
22
+ * even when an `i18nKey` is set, the placeholder `{{name}}` cannot be
23
+ * interpolated without a `values={{ name }}` prop — it renders literally.
24
+ *
25
+ * We keep the existing extraction behaviour so projects that already rely on
26
+ * the `{{name}}` output (with a matching `values` prop) aren't broken, and
27
+ * instead surface a diagnostic pointing users at the runtime mismatch.
28
+ */
29
+ private warnOnBareIdentifierTransChildren;
30
+ /**
31
+ * Unwraps TS type-assertion and parenthesis wrappers so we can inspect the
32
+ * underlying expression type (mirrors behaviour in jsx-parser).
33
+ */
34
+ private unwrapExpression;
17
35
  /**
18
36
  * Processes JSX elements to extract translation keys from Trans components.
19
37
  *
@@ -1 +1 @@
1
- {"version":3,"file":"jsx-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAoB,MAAM,WAAW,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAgB,MAAM,gBAAgB,CAAA;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAS7D,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;gBAGlC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM;IAS9B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;;;;;;;OAQG;IACH,gBAAgB,CAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,GAAG,IAAI;IAyUjI;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,0BAA0B;IAkIlC;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;CAevB"}
1
+ {"version":3,"file":"jsx-handler.d.ts","sourceRoot":"","sources":["../../../src/extractor/parsers/jsx-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,UAAU,EAAqC,MAAM,WAAW,CAAA;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAgB,MAAM,gBAAgB,CAAA;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAS7D,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,cAAc,CAAc;gBAGlC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,EAC7C,aAAa,EAAE,aAAa,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,MAAM,MAAM,EAC5B,cAAc,EAAE,MAAM,MAAM;IAS9B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAK3B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,iCAAiC;IAgCzC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;;;;;;OAQG;IACH,gBAAgB,CAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,GAAG,IAAI;IA2UjI;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,0BAA0B;IAkIlC;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;CAevB"}
@@ -112,6 +112,14 @@ export declare class ScopeManager {
112
112
  * @param callExpr - The CallExpression node representing the getFixedT invocation
113
113
  */
114
114
  private handleGetFixedTDeclarator;
115
+ /**
116
+ * Resolves a call argument expression to a string value by handling string
117
+ * literals, previously-declared local/shared constants (Identifier), member
118
+ * expressions referencing constant objects, and simple template literals
119
+ * without interpolation. Returns undefined when the expression cannot be
120
+ * resolved statically.
121
+ */
122
+ private resolveStringArg;
115
123
  /**
116
124
  * Handles cases where a getFixedT-like function is a variable (from a custom hook)
117
125
  * and is invoked to produce a bound `t` function, e.g.:
@@ -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;IAoBjC;;;;;;;;;OASG;IACH,OAAO,CAAC,qCAAqC;CAuB9C"}
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"}