UNPKG

@analogjs/platform

Version:

The fullstack meta-framework for Angular

288 lines 19.8 kB
import { resolve, dirname, extname } from 'node:path'; import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, } from 'node:fs'; const FORMAT_EXTENSIONS = { json: '.json', xliff: '.xlf', xliff2: '.xlf', xmb: '.xmb', }; /** * Vite plugin that extracts i18n messages from compiled JavaScript output. * * After the client build completes, this plugin scans all `.js` files in the * output directory for `$localize` tagged template literals and extracts the * message IDs and source text. It then writes the extracted messages to a * translation source file. * * Uses `@angular/localize/tools` MessageExtractor when available, * falling back to a regex-based extractor. */ export function i18nExtractPlugin(i18nOptions) { let config; let isSSRBuild = false; const extractConfig = i18nOptions.extract; if (!extractConfig) { return { name: 'analog-i18n-extract-noop' }; } const format = extractConfig.format ?? 'json'; return { name: 'analog-i18n-extract', apply: 'build', configResolved(resolvedConfig) { config = resolvedConfig; isSSRBuild = !!config.build.ssr; }, async closeBundle() { // Only run on the client build, not SSR if (isSSRBuild) { return; } const outDir = resolve(config.root, config.build.outDir); const jsFiles = collectJsFiles(outDir); const filesWithLocalize = jsFiles.filter((file) => { const content = readFileSync(file, 'utf-8'); return content.includes('$localize'); }); if (filesWithLocalize.length === 0) { return; } let messages; try { messages = await extractWithLocalizeTools(filesWithLocalize, config.root); } catch { // @angular/localize/tools not available, use regex fallback messages = extractWithRegex(filesWithLocalize); } if (messages.length === 0) { return; } const ext = FORMAT_EXTENSIONS[format] ?? '.json'; const defaultOutFile = `src/i18n/messages${ext}`; const outFile = resolve(config.root, extractConfig.outFile ?? defaultOutFile); mkdirSync(dirname(outFile), { recursive: true }); const output = serializeMessages(messages, format, i18nOptions.defaultLocale); writeFileSync(outFile, output, 'utf-8'); console.log(`\n[@analogjs/platform] Extracted ${messages.length} i18n message(s) to ${outFile}\n`); }, }; } /** * Extracts messages using @angular/localize/tools MessageExtractor. * Throws if the package is not installed. */ async function extractWithLocalizeTools(files, basePath) { // @ts-ignore - @angular/localize/tools is an optional dependency const localizeTools = await import('@angular/localize/tools'); const { MessageExtractor, ɵParsedMessage } = localizeTools; const fs = { readFile: (path) => readFileSync(path, 'utf-8'), readFileBuffer: (path) => readFileSync(path), relative: (from, to) => { const { relative } = require('node:path'); return relative(from, to); }, resolve: (...paths) => resolve(...paths), exists: (path) => { try { statSync(path); return true; } catch { return false; } }, dirname: (path) => dirname(path), }; const logger = { debug: () => { }, info: () => { }, warn: (msg) => console.warn(msg), error: (msg) => console.error(msg), level: 0, }; const extractor = new MessageExtractor(fs, logger, { basePath, useSourceMaps: false, }); const messages = []; for (const file of files) { try { const extracted = extractor.extractMessages(file); if (extracted?.messages) { for (const msg of extracted.messages) { messages.push({ id: msg.id || msg.customId || msg.messageString, text: msg.messageString || msg.text || '', description: msg.description, }); } } } catch { // Skip files that can't be parsed } } return messages; } /** * Regex-based fallback extractor for when @angular/localize/tools is not available. * * Parses `$localize` tagged template literals to extract message IDs and text. * Handles the common forms: * $localize`:@@messageId:text` * $localize`:description@@messageId:text` * $localize`text` */ export function extractWithRegex(files) { const messages = []; const seen = new Set(); // Match $localize`...` or $localize(__makeTemplateObject([...], [...])) // The tagged template form: $localize`:[description@@]id:text` const taggedTemplateRegex = /\$localize\s*`([^`]*)`/g; // Match the metadata block: :[@description@@]id: const metadataRegex = /^:((?:([^@]*)@@)?([^:]*)):(.*)$/s; for (const file of files) { const content = readFileSync(file, 'utf-8'); let match; taggedTemplateRegex.lastIndex = 0; while ((match = taggedTemplateRegex.exec(content)) !== null) { const body = match[1]; const metaMatch = metadataRegex.exec(body); let id; let text; let description; if (metaMatch) { description = metaMatch[2]?.trim() || undefined; id = metaMatch[3]; text = metaMatch[4]; } else { // No metadata block — use the text as both id and text text = body; id = body; } if (id && !seen.has(id)) { seen.add(id); messages.push({ id, text, description }); } } } return messages; } /** * Serializes extracted messages into the specified format. */ export function serializeMessages(messages, format, sourceLocale) { switch (format) { case 'xliff': return serializeXliff1(messages, sourceLocale); case 'xliff2': return serializeXliff2(messages, sourceLocale); case 'xmb': return serializeXmb(messages); case 'json': default: return serializeJson(messages); } } function serializeJson(messages) { const obj = {}; for (const msg of messages) { obj[msg.id] = msg.text; } return JSON.stringify(obj, null, 2) + '\n'; } function serializeXliff1(messages, sourceLocale) { const units = messages .map((msg) => { const note = msg.description ? `\n <note>${escapeXml(msg.description)}</note>` : ''; return ` <trans-unit id="${escapeXml(msg.id)}" datatype="html"> <source>${escapeXml(msg.text)}</source>${note} </trans-unit>`; }) .join('\n'); return `<?xml version="1.0" encoding="UTF-8"?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="${sourceLocale}" datatype="plaintext" original="ng2.template"> <body> ${units} </body> </file> </xliff> `; } function serializeXliff2(messages, sourceLocale) { const units = messages .map((msg) => { const notes = msg.description ? `\n <notes><note>${escapeXml(msg.description)}</note></notes>` : ''; return ` <unit id="${escapeXml(msg.id)}">${notes} <segment> <source>${escapeXml(msg.text)}</source> </segment> </unit>`; }) .join('\n'); return `<?xml version="1.0" encoding="UTF-8"?> <xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="${sourceLocale}"> <file id="ngi18n" original="ng.template"> ${units} </file> </xliff> `; } function serializeXmb(messages) { const msgs = messages .map((msg) => { const desc = msg.description ? ` desc="${escapeXml(msg.description)}"` : ''; return ` <msg id="${escapeXml(msg.id)}"${desc}>${escapeXml(msg.text)}</msg>`; }) .join('\n'); return `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE messagebundle [ <!ELEMENT messagebundle (msg)*> <!ELEMENT msg (#PCDATA)> <!ATTLIST msg id CDATA #REQUIRED> <!ATTLIST msg desc CDATA #IMPLIED> ]> <messagebundle> ${msgs} </messagebundle> `; } function escapeXml(str) { return str .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;'); } /** * Recursively collects all .js files from a directory. */ function collectJsFiles(dir) { const files = []; try { const entries = readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = resolve(dir, entry.name); if (entry.isDirectory()) { files.push(...collectJsFiles(fullPath)); } else if (entry.isFile() && extname(entry.name) === '.js') { files.push(fullPath); } } } catch { // Directory doesn't exist yet (e.g., first build) } return files; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaTE4bi1leHRyYWN0LXBsdWdpbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL3BsYXRmb3JtL3NyYy9saWIvaTE4bi1leHRyYWN0LXBsdWdpbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFDdEQsT0FBTyxFQUNMLFlBQVksRUFDWixhQUFhLEVBQ2IsU0FBUyxFQUNULFdBQVcsRUFDWCxRQUFRLEdBQ1QsTUFBTSxTQUFTLENBQUM7QUFJakIsTUFBTSxpQkFBaUIsR0FBMkI7SUFDaEQsSUFBSSxFQUFFLE9BQU87SUFDYixLQUFLLEVBQUUsTUFBTTtJQUNiLE1BQU0sRUFBRSxNQUFNO0lBQ2QsR0FBRyxFQUFFLE1BQU07Q0FDWixDQUFDO0FBRUY7Ozs7Ozs7Ozs7R0FVRztBQUNILE1BQU0sVUFBVSxpQkFBaUIsQ0FBQyxXQUF3QjtJQUN4RCxJQUFJLE1BQXNCLENBQUM7SUFDM0IsSUFBSSxVQUFVLEdBQUcsS0FBSyxDQUFDO0lBRXZCLE1BQU0sYUFBYSxHQUFHLFdBQVcsQ0FBQyxPQUFPLENBQUM7SUFDMUMsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBQ25CLE9BQU8sRUFBRSxJQUFJLEVBQUUsMEJBQTBCLEVBQVksQ0FBQztJQUN4RCxDQUFDO0lBRUQsTUFBTSxNQUFNLEdBQUcsYUFBYSxDQUFDLE1BQU0sSUFBSSxNQUFNLENBQUM7SUFFOUMsT0FBTztRQUNMLElBQUksRUFBRSxxQkFBcUI7UUFDM0IsS0FBSyxFQUFFLE9BQU87UUFFZCxjQUFjLENBQUMsY0FBYztZQUMzQixNQUFNLEdBQUcsY0FBYyxDQUFDO1lBQ3hCLFVBQVUsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUM7UUFDbEMsQ0FBQztRQUVELEtBQUssQ0FBQyxXQUFXO1lBQ2Ysd0NBQXdDO1lB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