i18next-cli 1.67.1 → 1.67.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/README.md CHANGED
@@ -317,7 +317,7 @@ npx i18next-cli lint
317
317
 
318
318
  - **Hardcoded strings** *(error)* — user-facing text in JSX elements and attributes that isn't wrapped in `t()`/`<Trans>`.
319
319
  - **Interpolation parameters** *(error)* — mismatches between `{{placeholders}}` in a translation and the params passed to `t()` (missing or unused). Toggle with `lint.checkInterpolationParams` (default: `true`).
320
- - **String concatenation** *(warning by default)* — translated strings glued together with `+`, or a sentence split across multiple `<Trans>` components. This breaks in languages that reorder or inflect the pieces; use a single key with placeholders instead. Configure with `lint.checkConcatenation`: `'warn'` / `true` (default) reports it without failing the run, `'error'` makes it fail (exit non-zero, useful for CI), and `'off'` / `false` disables it.
320
+ - **String concatenation** *(warning by default)* — translated strings glued together with `+`, or a sentence split across multiple adjacent translations (`<Trans>` components and/or `{t()}` expressions rendered as siblings). This breaks in languages that reorder or inflect the pieces; use a single key with placeholders instead. Configure with `lint.checkConcatenation`: `'warn'` / `true` (default) reports it without failing the run, `'error'` makes it fail (exit non-zero, useful for CI), and `'off'` / `false` disables it.
321
321
 
322
322
  ```jsx
323
323
  // ⚠️ Flagged — word order can't be translated
package/dist/cjs/cli.js CHANGED
@@ -37,7 +37,7 @@ const program = new commander.Command();
37
37
  program
38
38
  .name('i18next-cli')
39
39
  .description('A unified, high-performance i18next CLI.')
40
- .version('1.67.1'); // This string is replaced with the actual version at build time by rollup
40
+ .version('1.67.2'); // This string is replaced with the actual version at build time by rollup
41
41
  // new: global config override option
42
42
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
43
43
  program
@@ -315,8 +315,10 @@ function lintInterpolationParams(ast, code, config, translationValues) {
315
315
  * Flags two patterns:
316
316
  * JS — a `+` expression where an operand is a t() call:
317
317
  * t('greeting') + ', ' + name
318
- * JSX — a sentence split across ≥2 <Trans> components joined by literal text:
319
- * <p><Trans>Hello</Trans> and <Trans>World</Trans></p>
318
+ * JSX — a sentence split across ≥2 adjacent translation units (a <Trans>
319
+ * component or a `{t(...)}` expression) rendered as direct siblings:
320
+ * <p><Trans>Hello</Trans> <Trans>World</Trans></p>
321
+ * <p><Trans>new</Trans>{t('cat')}</p>
320
322
  *
321
323
  * The fix in both cases is a single key with placeholders:
322
324
  * t('greeting', { name }) / <Trans i18nKey="greeting">Hello {{name}}</Trans>
@@ -390,30 +392,33 @@ function lintConcatenation(ast, code, config) {
390
392
  });
391
393
  }
392
394
  }
393
- // JSX: a sentence split across ≥2 <Trans> components joined by literal text.
395
+ // JSX: a sentence split across ≥2 adjacent translation units rendered as
396
+ // direct siblings — a <Trans> component or a `{t(...)}` expression. Covers
397
+ // <Trans>…</Trans><Trans>…</Trans>, <Trans>…</Trans>{t(…)} and {t(…)}{t(…)}.
394
398
  if ((node.type === 'JSXElement' || node.type === 'JSXFragment') && Array.isArray(node.children)) {
395
- let transCount = 0;
396
- let firstTrans = null;
397
- let hasLiteralText = false;
399
+ let unitCount = 0;
400
+ let firstUnit = null;
398
401
  for (const child of node.children) {
399
402
  if (!child || typeof child !== 'object')
400
403
  continue;
404
+ let isUnit = false;
401
405
  if (child.type === 'JSXElement') {
402
406
  const name = getJsxName(child);
403
- if (name && transComponents.has(name.toLowerCase())) {
404
- transCount++;
405
- if (!firstTrans)
406
- firstTrans = child;
407
- }
407
+ isUnit = !!name && transComponents.has(name.toLowerCase());
408
+ }
409
+ else if (child.type === 'JSXExpressionContainer') {
410
+ isUnit = isTranslationCall(child.expression, config);
408
411
  }
409
- else if (child.type === 'JSXText' && child.value.trim() !== '') {
410
- hasLiteralText = true;
412
+ if (isUnit) {
413
+ unitCount++;
414
+ if (!firstUnit)
415
+ firstUnit = child;
411
416
  }
412
417
  }
413
- if (transCount >= 2 && hasLiteralText && firstTrans) {
418
+ if (unitCount >= 2 && firstUnit) {
414
419
  issues.push({
415
- text: 'Avoid splitting a sentence across multiple <Trans> components. Use a single <Trans> with placeholders instead.',
416
- line: lineOf(firstTrans),
420
+ text: 'Avoid splitting text across multiple translations (<Trans> or t()). Use a single translation with placeholders instead.',
421
+ line: lineOf(firstUnit),
417
422
  type: 'concatenation',
418
423
  severity,
419
424
  });
package/dist/esm/cli.js CHANGED
@@ -31,7 +31,7 @@ const program = new Command();
31
31
  program
32
32
  .name('i18next-cli')
33
33
  .description('A unified, high-performance i18next CLI.')
34
- .version('1.67.1'); // This string is replaced with the actual version at build time by rollup
34
+ .version('1.67.2'); // This string is replaced with the actual version at build time by rollup
35
35
  // new: global config override option
36
36
  program.option('-c, --config <path>', 'Path to i18next-cli config file (overrides detection)');
37
37
  program
@@ -313,8 +313,10 @@ function lintInterpolationParams(ast, code, config, translationValues) {
313
313
  * Flags two patterns:
314
314
  * JS — a `+` expression where an operand is a t() call:
315
315
  * t('greeting') + ', ' + name
316
- * JSX — a sentence split across ≥2 <Trans> components joined by literal text:
317
- * <p><Trans>Hello</Trans> and <Trans>World</Trans></p>
316
+ * JSX — a sentence split across ≥2 adjacent translation units (a <Trans>
317
+ * component or a `{t(...)}` expression) rendered as direct siblings:
318
+ * <p><Trans>Hello</Trans> <Trans>World</Trans></p>
319
+ * <p><Trans>new</Trans>{t('cat')}</p>
318
320
  *
319
321
  * The fix in both cases is a single key with placeholders:
320
322
  * t('greeting', { name }) / <Trans i18nKey="greeting">Hello {{name}}</Trans>
@@ -388,30 +390,33 @@ function lintConcatenation(ast, code, config) {
388
390
  });
389
391
  }
390
392
  }
391
- // JSX: a sentence split across ≥2 <Trans> components joined by literal text.
393
+ // JSX: a sentence split across ≥2 adjacent translation units rendered as
394
+ // direct siblings — a <Trans> component or a `{t(...)}` expression. Covers
395
+ // <Trans>…</Trans><Trans>…</Trans>, <Trans>…</Trans>{t(…)} and {t(…)}{t(…)}.
392
396
  if ((node.type === 'JSXElement' || node.type === 'JSXFragment') && Array.isArray(node.children)) {
393
- let transCount = 0;
394
- let firstTrans = null;
395
- let hasLiteralText = false;
397
+ let unitCount = 0;
398
+ let firstUnit = null;
396
399
  for (const child of node.children) {
397
400
  if (!child || typeof child !== 'object')
398
401
  continue;
402
+ let isUnit = false;
399
403
  if (child.type === 'JSXElement') {
400
404
  const name = getJsxName(child);
401
- if (name && transComponents.has(name.toLowerCase())) {
402
- transCount++;
403
- if (!firstTrans)
404
- firstTrans = child;
405
- }
405
+ isUnit = !!name && transComponents.has(name.toLowerCase());
406
+ }
407
+ else if (child.type === 'JSXExpressionContainer') {
408
+ isUnit = isTranslationCall(child.expression, config);
406
409
  }
407
- else if (child.type === 'JSXText' && child.value.trim() !== '') {
408
- hasLiteralText = true;
410
+ if (isUnit) {
411
+ unitCount++;
412
+ if (!firstUnit)
413
+ firstUnit = child;
409
414
  }
410
415
  }
411
- if (transCount >= 2 && hasLiteralText && firstTrans) {
416
+ if (unitCount >= 2 && firstUnit) {
412
417
  issues.push({
413
- text: 'Avoid splitting a sentence across multiple <Trans> components. Use a single <Trans> with placeholders instead.',
414
- line: lineOf(firstTrans),
418
+ text: 'Avoid splitting text across multiple translations (<Trans> or t()). Use a single translation with placeholders instead.',
419
+ line: lineOf(firstUnit),
415
420
  type: 'concatenation',
416
421
  severity,
417
422
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-cli",
3
- "version": "1.67.1",
3
+ "version": "1.67.2",
4
4
  "description": "A unified, high-performance i18next CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +1 @@
1
- {"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAO1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,SAAS,EAA6B,MAAM,YAAY,CAAA;AAibpG,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE;QAAC;YACT,OAAO,EAAE,MAAM,CAAC;SACjB;KAAC,CAAC;IACH,IAAI,EAAE;QAAC;YACL,OAAO,EAAE,OAAO,CAAC;YACjB,OAAO,EAAE,MAAM,CAAC;YAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;SACpC;KAAC,CAAC;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACvB,CAAA;AAED,eAAO,MAAM,uBAAuB,EAAE,MAAM,EAAiD,CAAA;AAC7F,eAAO,MAAM,6BAA6B,EAAE,MAAM,EAAqD,CAAA;AAKvG,qBAAa,MAAO,SAAQ,YAAY,CAAC,cAAc,CAAC;IACtD,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,MAAM,CAAQ;gBAET,MAAM,EAAE,oBAAoB,EAAE,MAAM,GAAE,MAA4B;IAM/E,SAAS,CAAE,KAAK,EAAE,OAAO;IAanB,GAAG;;;;;;;IAuIT,OAAO,CAAC,uBAAuB;YAOjB,qBAAqB;IAWnC,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,0BAA0B;YAUpB,qBAAqB;YAgBrB,uBAAuB;CAgBtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB;;;;;;GAE5D;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,iBA0CnD"}
1
+ {"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAO1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,EAAE,SAAS,EAA6B,MAAM,YAAY,CAAA;AAsbpG,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE;QAAC;YACT,OAAO,EAAE,MAAM,CAAC;SACjB;KAAC,CAAC;IACH,IAAI,EAAE;QAAC;YACL,OAAO,EAAE,OAAO,CAAC;YACjB,OAAO,EAAE,MAAM,CAAC;YAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;SACpC;KAAC,CAAC;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;CACvB,CAAA;AAED,eAAO,MAAM,uBAAuB,EAAE,MAAM,EAAiD,CAAA;AAC7F,eAAO,MAAM,6BAA6B,EAAE,MAAM,EAAqD,CAAA;AAKvG,qBAAa,MAAO,SAAQ,YAAY,CAAC,cAAc,CAAC;IACtD,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,MAAM,CAAQ;gBAET,MAAM,EAAE,oBAAoB,EAAE,MAAM,GAAE,MAA4B;IAM/E,SAAS,CAAE,KAAK,EAAE,OAAO;IAanB,GAAG;;;;;;;IAuIT,OAAO,CAAC,uBAAuB;YAOjB,qBAAqB;IAWnC,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,0BAA0B;YAUpB,qBAAqB;YAgBrB,uBAAuB;CAgBtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,SAAS,CAAE,MAAM,EAAE,oBAAoB;;;;;;GAE5D;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,iBA0CnD"}