Skip to main content
Glama
index.ts4.18 kB
import { readFile, writeFile } from 'node:fs/promises'; import { parse as parseVue } from '@vue/compiler-sfc'; import MagicString from 'magic-string'; import { type Node, Project, type SourceFile } from 'ts-morph'; type TsReplacement = { node: Node; key: string; type: 'jsx-text' | 'jsx-attribute' | 'string-literal'; }; type Tools = { generateKey: (text: string, existingKeys: Set<string>) => string; shouldExtract: (text: string) => boolean; extractTsContent: ( sourceFile: SourceFile, existingKeys: Set<string> ) => { extractedContent: Record<string, string>; replacements: TsReplacement[]; }; }; // Kept local as it's specific to Vue attributes, though shared list is 'title', 'placeholder' etc. // If we want to strictly mutualize, we can pass it too. const ATTRIBUTES_TO_EXTRACT = [ 'title', 'placeholder', 'alt', 'aria-label', 'label', ]; export const processVueFile = async ( filePath: string, componentKey: string, packageName: string, tools: Tools, save: boolean = true ) => { const { generateKey, shouldExtract, extractTsContent } = tools; const code = await readFile(filePath, 'utf-8'); const sfc = parseVue(code); const magic = new MagicString(code); const extractedContent: Record<string, string> = {}; const existingKeys = new Set<string>(); if (sfc.descriptor.template) { const walkVueAst = (node: any) => { if (node.type === 2) { // NodeTypes.TEXT const text = node.content; if (shouldExtract(text)) { const key = generateKey(text, existingKeys); existingKeys.add(key); extractedContent[key] = text.replace(/\s+/g, ' ').trim(); magic.overwrite( node.loc.start.offset, node.loc.end.offset, `{{ content.${key} }}` ); } } else if (node.type === 1) { // NodeTypes.ELEMENT node.props.forEach((prop: any) => { if ( prop.type === 6 && // NodeTypes.ATTRIBUTE ATTRIBUTES_TO_EXTRACT.includes(prop.name) && prop.value ) { const text = prop.value.content; if (shouldExtract(text)) { const key = generateKey(text, existingKeys); existingKeys.add(key); extractedContent[key] = text.trim(); magic.overwrite( prop.loc.start.offset, prop.loc.end.offset, `:${prop.name}="content.${key}"` ); } } }); } if (node.children) { node.children.forEach(walkVueAst); } }; walkVueAst(sfc.descriptor.template.ast); } const scriptBlock = sfc.descriptor.scriptSetup || sfc.descriptor.script; if (scriptBlock) { const scriptContent = scriptBlock.content; const scriptOffset = scriptBlock.loc.start.offset; const project = new Project({ skipAddingFilesFromTsConfig: true }); const sourceFile = project.createSourceFile('temp.ts', scriptContent); const { extractedContent: scriptExtracted, replacements } = extractTsContent(sourceFile, existingKeys); Object.assign(extractedContent, scriptExtracted); for (const { node, key } of replacements) { // Calculate absolute pos const start = scriptOffset + node.getStart(); const end = scriptOffset + node.getEnd(); magic.overwrite(start, end, `content.${key}`); } } if (Object.keys(extractedContent).length === 0) return null; // Inject Script const importStmt = `import { useIntlayer } from '${packageName}';`; const contentDecl = `const content = useIntlayer('${componentKey}');`; if (sfc.descriptor.scriptSetup) { magic.appendLeft( sfc.descriptor.scriptSetup.loc.start.offset, `\n${importStmt}\n${contentDecl}\n` ); } else if (sfc.descriptor.script) { magic.appendLeft( sfc.descriptor.script.loc.start.offset, `\n${importStmt}\n${contentDecl}\n` ); } else { magic.prepend(`<script setup>\n${importStmt}\n${contentDecl}\n</script>\n`); } if (save) { await writeFile(filePath, magic.toString()); } return extractedContent; };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server