Skip to content

Conversation

@mwec-optimatik
Copy link

When trying to use multiple next-intl namespaces in one file, only the last one is used to resolve keys. For example, it used to be:

const t = useTranslations('common');
const tSpecific = useTranslations('components.specific');
...
t('someKeyInCommon') // doesn't work
tSpecific('someKeyInSpecific') // doesn't work as the variable is not called 't' but 'tSpecific'
t('someKeyInSpecific') // resolves to the components.specifc.someKeyInSpecific translation preview despite using the common 't' variable

With this PR, these problems should be fixed so that both t and tSpecific are resolved to the correct translation previews.

Should be fix to #1076, #1226 and #1353

@coderabbitai
Copy link

coderabbitai bot commented Oct 27, 2025

📝 Walkthrough

Summary by CodeRabbit

  • Improvements
    • Improved translation function name detection and scope extraction for better translation key accuracy.
    • Enhanced key extraction logic to handle framework-specific translation patterns more effectively.
    • Better scope matching for translation functions across supported frameworks.

Walkthrough

The changes enhance scope range tracking for next-intl to capture per-function translation namespaces. A new NextIntlScopeRange interface extends the base ScopeRange with an optional functionName property, accompanied by a type guard. The scope detection regex and extraction logic have been expanded to capture full function names (e.g., tFoo) alongside namespace data from useTranslations/getTranslations calls. The Regex utility module is updated to accept both ScopeRange[] and NextIntlScopeRange[], applying function name matching logic to determine key extraction behavior based on scope type.

Sequence Diagram

sequenceDiagram
    participant Parser
    participant NextIntl
    participant RegexUtil
    
    rect rgb(240, 248, 255)
    Note over Parser,NextIntl: Scope Detection (Enhanced)
    Parser->>NextIntl: getScopeRange(document)
    activate NextIntl
    NextIntl->>NextIntl: Match useTranslations/getTranslations
    NextIntl->>NextIntl: Extract namespace & functionName (e.g., tFoo)
    NextIntl->>Parser: NextIntlScopeRange[] (with functionName)
    deactivate NextIntl
    end
    
    rect rgb(255, 245, 238)
    Note over Parser,RegexUtil: Key Extraction (Function-Aware)
    Parser->>RegexUtil: handleRegexMatch(scopes, ...) 
    activate RegexUtil
    RegexUtil->>RegexUtil: isNextIntlScopeRange(scope)?
    alt NextIntl Scope
        RegexUtil->>RegexUtil: Capture nextIntlFunctionName
        RegexUtil->>RegexUtil: Match captured name vs scope.functionName
        RegexUtil->>RegexUtil: Use adjusted keyIndex if matched
    else Generic Scope
        RegexUtil->>RegexUtil: Use standard key extraction
    end
    RegexUtil->>Parser: Extracted keys
    deactivate RegexUtil
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Regex pattern accuracy: Verify the updated usageMatchRegex correctly captures full function names across all usage types (basic, rich, markup, raw)
  • Type guard implementation: Ensure isNextIntlScopeRange correctly distinguishes NextIntlScopeRange instances at runtime
  • Scope matching logic: Review the conditional logic in handleRegexMatch that compares captured function names with scope functionName properties
  • Parameter type union handling: Confirm the ScopeRange[] | NextIntlScopeRange[] union is properly narrowed and processed throughout the utility function

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "Enhance NextIntl integration to handle multiple scopes within one file" directly reflects the main changes in the changeset. The modifications add support for capturing and matching function names (like t, tSpecific) alongside namespaces from useTranslations calls, which enables proper resolution of multiple scopes/namespaces within a single file. The title is concise, specific, and accurately describes the primary improvement being made.
Description Check ✅ Passed The PR description is directly related to the changeset, explaining the problem being solved: when multiple next-intl namespaces were used in one file, only the last one was used and variable names like tSpecific weren't being considered for key resolution. The description illustrates the issue with concrete examples and explains that the PR fixes this by enabling both t and tSpecific to resolve to their correct translation previews. This aligns well with the code changes that add functionName tracking to NextIntlScopeRange and update scope matching logic.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
src/utils/Regex.ts (4)

2-9: Fix import order to satisfy import/order

Reorder relative/index imports before ~/ aliases per linter hints.

-import { Config, CurrentFile } from '~/core'
-import { isNextIntlScopeRange, NextIntlScopeRange } from '~/frameworks/next-intl'
-import i18n from '~/i18n'
-import { Log } from '.'
-import { KeyInDocument, RewriteKeyContext } from '../core/types'
-import { ScopeRange } from '../frameworks/base'
-import { QUOTE_SYMBOLS } from '../meta'
+import { Log } from '.'
+import { KeyInDocument, RewriteKeyContext } from '../core/types'
+import { ScopeRange } from '../frameworks/base'
+import { QUOTE_SYMBOLS } from '../meta'
+import { Config, CurrentFile } from '~/core'
+import { isNextIntlScopeRange, NextIntlScopeRange } from '~/frameworks/next-intl'
+import i18n from '~/i18n'

35-35: Guard previous-char access when computing quoted

Avoid passing undefined into includes.

-  const quoted = QUOTE_SYMBOLS.includes(text[start - 1])
+  const quoted = QUOTE_SYMBOLS.includes(text[start - 1] ?? '')

65-72: Align parameter type with new union support

handleRegexMatch accepts ScopeRange[] | NextIntlScopeRange[], but regexFindKeys still narrows to ScopeRange[]. Widen for clarity and stronger types.

-  scopes: ScopeRange[] = [],
+  scopes: (ScopeRange | NextIntlScopeRange)[] = [],

98-99: Typo: interpatedinterpolated

Minor rename for clarity.

-        const interpated = i.replace(/{key}/g, Config.regexKey)
-        return new RegExp(interpated, 'gm')
+        const interpolated = i.replace(/{key}/g, Config.regexKey)
+        return new RegExp(interpolated, 'gm')
src/frameworks/next-intl.ts (2)

38-50: Allow matches at start-of-line without shifting capture groups

Current patterns require a preceding character. Use a non‑capturing group to also match line start while preserving group indexes (1: functionName, 2: key).

-    '[^\\w\\d](t(?:[A-Z]\\w*)?)\\s*\\(\\s*[\'"`]({key})[\'"`]',
+    '(?:^|[^\\w\\d])(t(?:[A-Z]\\w*)?)\\s*\\(\\s*[\'"`]({key})[\'"`]',
-    '[^\\w\\d](t(?:[A-Z]\\w*)?)\\s*\\.rich\\s*\\(\\s*[\'"`]({key})[\'"`]',
+    '(?:^|[^\\w\\d])(t(?:[A-Z]\\w*)?)\\s*\\.rich\\s*\\(\\s*[\'"`]({key})[\'"`]',
-    '[^\\w\\d](t(?:[A-Z]\\w*)?)\\s*\\.markup\\s*\\(\\s*[\'"`]({key})[\'"`]',
+    '(?:^|[^\\w\\d])(t(?:[A-Z]\\w*)?)\\s*\\.markup\\s*\\(\\s*[\'"`]({key})[\'"`]',
-    '[^\\w\\d](t(?:[A-Z]\\w*)?)\\s*\\.raw\\s*\\(\\s*[\'"`]({key})[\'"`]',
+    '(?:^|[^\\w\\d])(t(?:[A-Z]\\w*)?)\\s*\\.raw\\s*\\(\\s*[\'"`]({key})[\'"`]',

99-115: Tighten scope end to the next declaration for performance and precision

Using text.length as end makes scopes span the whole file. You can bound each scope to the next declaration to reduce overlap and scanning.

-    for (const match of text.matchAll(regex)) {
-      if (typeof match.index !== 'number')
-        continue
-      const variableName = match[1]
-      const namespace = match[3]
-      // Add a new scope if a namespace is provided
-      if (namespace) {
-        ranges.push({
-          start: match.index,
-          end: text.length,
-          namespace,
-          functionName: variableName,
-        })
-      }
-    }
+    const matches = [...text.matchAll(regex)]
+    for (let i = 0; i < matches.length; i++) {
+      const match = matches[i]
+      if (typeof match.index !== 'number')
+        continue
+      const variableName = match[1]
+      const namespace = match[3]
+      const nextIndex = i < matches.length - 1 && typeof matches[i + 1].index === 'number'
+        ? (matches[i + 1].index as number)
+        : text.length
+      if (namespace) {
+        ranges.push({
+          start: match.index,
+          end: nextIndex,
+          namespace,
+          functionName: variableName,
+        })
+      }
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4c504c9 and 6440f8c.

📒 Files selected for processing (2)
  • src/frameworks/next-intl.ts (3 hunks)
  • src/utils/Regex.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/utils/Regex.ts (3)
src/core/types.ts (2)
  • RewriteKeyContext (141-145)
  • KeyInDocument (113-118)
src/frameworks/base.ts (1)
  • ScopeRange (16-20)
src/frameworks/next-intl.ts (2)
  • NextIntlScopeRange (6-8)
  • isNextIntlScopeRange (10-12)
src/frameworks/next-intl.ts (1)
src/frameworks/base.ts (1)
  • ScopeRange (16-20)
🪛 ESLint
src/utils/Regex.ts

[error] 5-5: . import should occur before import of ~/core

(import/order)


[error] 6-6: ../core/types import should occur before import of ~/core

(import/order)


[error] 7-7: ../frameworks/base import should occur before import of ~/core

(import/order)


[error] 8-8: ../meta import should occur before import of ~/core

(import/order)


[error] 23-23: It's not necessary to initialize 'nextIntlFunctionName' to undefined.

(no-undef-init)

🔇 Additional comments (4)
src/utils/Regex.ts (1)

34-34: Scope matching by functionName looks correct

Filtering NextIntl scopes by functionName ensures multiple namespaces per file resolve properly. LGTM.

src/frameworks/next-intl.ts (3)

6-12: Type guard and extended scope range are solid

NextIntlScopeRange + isNextIntlScopeRange enable safe runtime narrowing. LGTM.


87-99: API and detection flow look good

Return type widened to NextIntlScopeRange[] and regex captures the variable name and namespace. LGTM.


38-50: Confirm naming convention: restrict to t/tPascal?

Patterns require t or t + uppercase. If tfoo (lowercase) or other names are allowed in your codebase, consider t\\w*. If not, ignore.

Would you like me to widen the regex to t\\w* and add tests accordingly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant