11import { TextDocument } from 'vscode'
22import { Framework , ScopeRange } from './base'
3+ import { KeyStyle , RewriteKeyContext , RewriteKeySource } from '~/core'
34import { LanguageId } from '~/utils'
4- import { RewriteKeySource , RewriteKeyContext , KeyStyle } from '~/core'
5+
6+ export interface NextIntlScopeRange extends ScopeRange {
7+ functionName ?: string
8+ }
9+
10+ export const isNextIntlScopeRange = ( scope : ScopeRange ) : scope is NextIntlScopeRange => {
11+ return ( scope as NextIntlScopeRange ) . functionName !== undefined
12+ }
513
614class NextIntlFramework extends Framework {
715 id = 'next-intl'
@@ -27,17 +35,18 @@ class NextIntlFramework extends Framework {
2735 ]
2836
2937 usageMatchRegex = [
38+ // Match: t, tSpecific, tFoo (capture the full variable name to use in scope detection)
3039 // Basic usage
31- '[^\\w\\d]t \\s*\\(\\s*[\'"`]({key})[\'"`]' ,
40+ '[^\\w\\d](t(?:[A-Z]\\w*)?) \\s*\\(\\s*[\'"`]({key})[\'"`]' ,
3241
3342 // Rich text
34- '[^\\w\\d]t\\s* \.rich\\s*\\(\\s*[\'"`]({key})[\'"`]' ,
43+ '[^\\w\\d](t(?:[A-Z]\\w*)?)\\s*\ \.rich\\s*\\(\\s*[\'"`]({key})[\'"`]' ,
3544
3645 // Markup text
37- '[^\\w\\d]t\\s* \.markup\\s*\\(\\s*[\'"`]({key})[\'"`]' ,
46+ '[^\\w\\d](t(?:[A-Z]\\w*)?)\\s*\ \.markup\\s*\\(\\s*[\'"`]({key})[\'"`]' ,
3847
3948 // Raw text
40- '[^\\w\\d]t\\s* \.raw\\s*\\(\\s*[\'"`]({key})[\'"`]' ,
49+ '[^\\w\\d](t(?:[A-Z]\\w*)?)\\s*\ \.raw\\s*\\(\\s*[\'"`]({key})[\'"`]' ,
4150 ]
4251
4352 refactorTemplates ( keypath : string ) {
@@ -75,36 +84,32 @@ class NextIntlFramework extends Framework {
7584 return dottedKey
7685 }
7786
78- getScopeRange ( document : TextDocument ) : ScopeRange [ ] | undefined {
87+ getScopeRange ( document : TextDocument ) : NextIntlScopeRange [ ] | undefined {
7988 if ( ! this . languageIds . includes ( document . languageId as any ) )
8089 return
8190
82- const ranges : ScopeRange [ ] = [ ]
91+ const ranges : NextIntlScopeRange [ ] = [ ]
8392 const text = document . getText ( )
8493
85- // Find matches of `useTranslations` and `getTranslations`. Later occurences will
86- // override previous ones (this allows for multiple components with different
87- // namespaces in the same file). Note that `getTranslations` can either be called
88- // with a single string argument or an object with a `namespace` key.
89- const regex = / ( u s e T r a n s l a t i o n s \( \s * | g e t T r a n s l a t i o n s \( \s * | n a m e s p a c e : \s + ) ( [ ' " ` ] ( .* ?) [ ' " ` ] ) ? / g
90- let prevGlobalScope = false
94+ // Find matches of `useTranslations` and `getTranslations` and extracts the variable names.
95+ // If there are multiple occurrences in the same file, there will be multiple, overlapping scopes.
96+ // During resolution, the variable name will be used to determine which scope the key belongs to, allowing multiple namespaces in the same file.
97+ const regex = / (?: c o n s t | l e t | v a r ) \s + ( t (?: [ A - Z ] \w * ) ? ) \s * = \s * (?: a w a i t \s + ) ? ( u s e T r a n s l a t i o n s | g e t T r a n s l a t i o n s ) \s * \( \s * [ ' " ` ] ( .* ?) [ ' " ` ] \) / g
98+
9199 for ( const match of text . matchAll ( regex ) ) {
92100 if ( typeof match . index !== 'number' )
93101 continue
94102
103+ const variableName = match [ 1 ]
95104 const namespace = match [ 3 ]
96105
97- // End previous scope
98- if ( prevGlobalScope )
99- ranges [ ranges . length - 1 ] . end = match . index
100-
101- // Start a new scope if a namespace is provided
106+ // Add a new scope if a namespace is provided
102107 if ( namespace ) {
103- prevGlobalScope = true
104108 ranges . push ( {
105109 start : match . index ,
106110 end : text . length ,
107111 namespace,
112+ functionName : variableName ,
108113 } )
109114 }
110115 }
0 commit comments