@@ -2,7 +2,6 @@ import type { FileParser, TextSegment, TranslationEntry } from '../types'
22import type { RangeSegment } from './range'
33import { parse as parseVue } from '@vue/compiler-sfc'
44import { extractStyleSegments } from './css'
5- import { extractHtmlAttrSegments } from './html'
65import { dedupeSegments , finalizeSegments , leadingSpaces , lineColumn , replaceTranslations } from './range'
76import { extractScriptSegments } from './script'
87
@@ -21,9 +20,8 @@ export function extractVueSegments(content: string, filePath: string): RangeSegm
2120 const segments : RangeSegment [ ] = [ ]
2221
2322 if ( descriptor . template ) {
24- const offset = descriptor . template . loc . start . offset
2523 segments . push ( ...extractVueTemplateTextSegments ( descriptor . template . ast , content , filePath ) )
26- segments . push ( ...extractHtmlAttrSegments ( descriptor . template . content , filePath , offset ) )
24+ segments . push ( ...extractVueTemplateAttrSegments ( descriptor . template . ast , content , filePath ) )
2725 }
2826
2927 for ( const block of [ descriptor . script , descriptor . scriptSetup ] ) {
@@ -64,6 +62,59 @@ interface VueTemplateNode {
6462 source : string
6563 }
6664 children ?: unknown [ ]
65+ props ?: unknown [ ]
66+ }
67+
68+ interface VueTemplateAttr {
69+ type : number
70+ name ?: string
71+ value ?: {
72+ content ?: string
73+ loc ?: {
74+ start : { offset : number }
75+ end : { offset : number }
76+ source : string
77+ }
78+ }
79+ }
80+
81+ function extractVueTemplateAttrSegments ( ast : unknown , content : string , filePath : string ) : RangeSegment [ ] {
82+ const segments : RangeSegment [ ] = [ ]
83+
84+ collectVueTemplateAttrSegments ( ast , content , segments )
85+ return dedupeSegments ( segments , filePath )
86+ }
87+
88+ function collectVueTemplateAttrSegments (
89+ node : unknown ,
90+ content : string ,
91+ segments : RangeSegment [ ] ,
92+ ) : void {
93+ if ( ! isVueTemplateNode ( node ) )
94+ return
95+
96+ for ( const prop of node . props ?? [ ] ) {
97+ if ( ! isVueStaticAttr ( prop ) || ! prop . name || shouldSkipVueStaticAttr ( prop . name ) || ! prop . value ?. content || ! prop . value . loc )
98+ continue
99+
100+ const raw = prop . value . loc . source
101+ const quoteOffset = isQuoted ( raw ) ? 1 : 0
102+ const start = prop . value . loc . start . offset + quoteOffset
103+ const end = prop . value . loc . end . offset - quoteOffset
104+ const position = lineColumn ( content , start )
105+ segments . push ( {
106+ text : prop . value . content ,
107+ start,
108+ end,
109+ line : position . line ,
110+ column : position . column ,
111+ context : 'html-attr' ,
112+ nodeType : 'VueStaticAttribute' ,
113+ } )
114+ }
115+
116+ for ( const child of node . children ?? [ ] )
117+ collectVueTemplateAttrSegments ( child , content , segments )
67118}
68119
69120function extractVueTemplateTextSegments ( ast : unknown , content : string , filePath : string ) : RangeSegment [ ] {
@@ -151,3 +202,21 @@ function isInlineTemplateTextNode(node: unknown): node is VueTemplateNode {
151202function isVueTemplateNode ( value : unknown ) : value is VueTemplateNode {
152203 return Boolean ( value && typeof value === 'object' && 'type' in value )
153204}
205+
206+ function isVueStaticAttr ( value : unknown ) : value is VueTemplateAttr {
207+ return Boolean ( value && typeof value === 'object' && ( value as { type ?: unknown } ) . type === 6 )
208+ }
209+
210+ function isQuoted ( value : string ) : boolean {
211+ return ( value . startsWith ( '"' ) && value . endsWith ( '"' ) ) || ( value . startsWith ( '\'' ) && value . endsWith ( '\'' ) )
212+ }
213+
214+ function shouldSkipVueStaticAttr ( name : string ) : boolean {
215+ return name === 'class'
216+ || name === 'style'
217+ || name === 'id'
218+ || name === 'key'
219+ || name === 'ref'
220+ || name . startsWith ( 'data-' )
221+ || name . startsWith ( 'aria-' )
222+ }
0 commit comments