Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
6.9 kB ยท 251 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252#!/usr/bin/env node
const fs = require("fs");const path = require("path");const { parse: parseFtl } = require("@fluent/syntax");
// Load language manifestconst MANIFEST_PATH = path.join(__dirname, "..", "locales", "manifest.json");const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, "utf-8"));
// Configurationconst LOCALES_SOURCE_DIR = path.join(__dirname, "..", "locales");const LOCALES_OUTPUT_DIR = path.join(__dirname, "..", "public", "locales");
/** * Find all .ftl files in a directory (non-recursive, just top-level) */function findFtlFiles(dir) { const files = []; const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) { if (entry.isFile() && entry.name.endsWith(".ftl")) { files.push({ path: path.join(dir, entry.name), namespace: path.basename(entry.name, ".ftl"), }); } }
return files;}
/** * Parse FTL content using @fluent/syntax and extract messages */function extractMessagesFromFtl(content) { const messages = {};
try { const resource = parseFtl(content);
for (const entry of resource.body) { // Only process Message entries (not Comments, Terms, etc.) if (entry.type === "Message" && entry.id) { const key = entry.id.name;
// For now, just serialize the pattern back to FTL format // i18next-fluent will handle the actual parsing at runtime if (entry.value) { messages[key] = serializePattern(entry.value); } } } } catch (error) { console.error("Error parsing FTL:", error.message); throw error; }
return messages;}
/** * Serialize a Fluent pattern back to FTL string format * This is a simple serializer - @fluent/syntax has a full serializer * but we just need the pattern text for JSON storage */function serializePattern(pattern) { if (!pattern || !pattern.elements) { return ""; }
let result = "";
for (const element of pattern.elements) { if (element.type === "TextElement") { result += element.value; } else if (element.type === "Placeable") { result += serializePlaceable(element); } }
return result;}
/** * Serialize a placeable (variables, select expressions, etc.) */function serializePlaceable(placeable) { if (!placeable.expression) { return "{}"; }
const expr = placeable.expression;
if (expr.type === "VariableReference") { return `{$${expr.id.name}}`; } else if (expr.type === "SelectExpression") { let result = `{${serializeInlineExpression(expr.selector)} ->`;
for (const variant of expr.variants) { const key = variant.key.type === "Identifier" ? `[${variant.key.name}]` : `[${variant.key.value}]`;
result += `\n ${variant.default ? "*" : ""}${key} ${serializePattern(variant.value)}`; }
result += "\n }"; return result; } else if (expr.type === "FunctionReference") { const args = expr.arguments.positional .map((arg) => serializeInlineExpression(arg)) .join(", "); return `{${expr.id.name}(${args})}`; }
return "{}";}
/** * Serialize inline expressions (for function arguments, etc.) */function serializeInlineExpression(expr) { if (expr.type === "VariableReference") { return `$${expr.id.name}`; } else if (expr.type === "NumberLiteral") { return expr.value; } else if (expr.type === "StringLiteral") { return `"${expr.value}"`; }
return "";}
/** * Main compilation function */function compileTranslations() { console.log("๐ Compiling translation files...");
if (!fs.existsSync(LOCALES_SOURCE_DIR)) { console.error(`โ Locales directory not found: ${LOCALES_SOURCE_DIR}`); process.exit(1); }
// Create output directory if it doesn't exist if (!fs.existsSync(LOCALES_OUTPUT_DIR)) { fs.mkdirSync(LOCALES_OUTPUT_DIR, { recursive: true }); }
// Get supported locales from manifest const manifestLocales = manifest.supportedLocales; const locales = manifestLocales.filter((locale) => { const localeDir = path.join(LOCALES_SOURCE_DIR, locale); return fs.existsSync(localeDir) && fs.statSync(localeDir).isDirectory(); });
if (locales.length === 0) { console.error(`โ No locale directories found in ${LOCALES_SOURCE_DIR}`); process.exit(1); }
let totalFiles = 0;
// Process each locale for (const locale of locales) { const localeSourceDir = path.join(LOCALES_SOURCE_DIR, locale); const localeOutputDir = path.join(LOCALES_OUTPUT_DIR, locale);
console.log(`๐ฆ Processing locale: ${locale}`);
// Create locale output directory if (!fs.existsSync(localeOutputDir)) { fs.mkdirSync(localeOutputDir, { recursive: true }); }
// Find all .ftl files for this locale const ftlFiles = findFtlFiles(localeSourceDir);
if (ftlFiles.length === 0) { console.warn(`โ ๏ธ No .ftl files found in ${localeSourceDir}`); continue; }
// Process each .ftl file as a separate namespace for (const { path: ftlPath, namespace } of ftlFiles) { try { const content = fs.readFileSync(ftlPath, "utf-8"); const messages = extractMessagesFromFtl(content);
if (Object.keys(messages).length === 0) { console.warn(`โ ๏ธ No messages found in ${ftlPath}`); continue; }
// Write namespace JSON file const outputPath = path.join(localeOutputDir, `${namespace}.json`); fs.writeFileSync( outputPath, JSON.stringify(messages, null, 2) + "\n", "utf-8", );
console.log( ` โ
${namespace}: ${Object.keys(messages).length} keys โ ${path.relative(process.cwd(), outputPath)}`, ); totalFiles++; } catch (error) { console.error(`โ Error processing ${ftlPath}:`, error.message); } } }
console.log( `๐ Compilation complete! ${totalFiles} namespace files generated.`, );}
/** * Copy compiled translations to app/public/locales and web/public/locales */function copyToConsumers() { const targets = [ path.join(__dirname, "..", "..", "app", "public", "locales"), path.join(__dirname, "..", "..", "web", "public", "locales"), ];
for (const target of targets) { const targetDir = path.dirname(target); if (!fs.existsSync(targetDir)) continue;
console.log( `\n๐ Copying translations to ${path.relative(process.cwd(), target)}...`, );
if (fs.existsSync(target)) { fs.rmSync(target, { recursive: true, force: true }); }
fs.cpSync(LOCALES_OUTPUT_DIR, target, { recursive: true }); console.log(`โ
Copied to ${path.relative(process.cwd(), target)}`); }}
// Run the compilationcompileTranslations();
// Copy to consumers if they existcopyToConsumers();