i18next-cli 1.56.1 → 1.56.2

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.2'); // 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
@@ -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.2'); // 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
@@ -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.2",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {