@analogjs/platform
Version:
The fullstack meta-framework for Angular
288 lines • 19.8 kB
JavaScript
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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
/**
* 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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