zonemaster-gui/scripts/generate-messages.ts
2025-12-15 21:49:27 +01:00

166 lines
5.3 KiB
TypeScript

import fs from 'fs';
import path from 'path';
// Regex to find placeholders like {variableName}
const placeholderRegex = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
interface Messages {
[key: string]: string;
}
interface AllMessages {
[lang: string]: Messages;
}
interface GenerateConfig {
defaultLanguage: string;
enabledLanguages: string[];
}
function getFileHeader(): string {
return `/* Auto-generated by vite-plugin-messages - DO NOT EDIT */\n/* Generated: ${new Date().toISOString()} */\n`;
}
export function generateMessages(langDir: string, outDir: string, config: GenerateConfig) {
const { defaultLanguage, enabledLanguages } = config;
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true });
}
const allMessages: AllMessages = {};
for (const lang of enabledLanguages) {
const filePath = path.join(langDir, `${lang}.json`);
try {
const content = fs.readFileSync(filePath, 'utf-8');
allMessages[lang] = JSON.parse(content);
} catch (e) {
console.warn(`⚠️ Failed to load ${lang}.json:`, e instanceof Error ? e.message : String(e));
allMessages[lang] = {};
}
}
if (Object.keys(allMessages).length === 0) {
console.warn('⚠️ No language files found');
return;
}
const defaultMessages = allMessages[defaultLanguage] || {};
const allKeys = Object.keys(defaultMessages);
for (const lang of enabledLanguages) {
const messages = allMessages[lang] || {};
const functions: string[] = [];
const reExports: string[] = [];
for (const key of allKeys) {
const hasTranslation = key in messages;
if (hasTranslation) {
functions.push(createMessageFunction(key, messages[key]));
} else if (lang !== defaultLanguage) {
reExports.push(key);
}
}
let content = getFileHeader();
if (reExports.length > 0) {
content += `\nexport { ${reExports.join(', ')} } from './${defaultLanguage}.ts';\n`;
}
if (functions.length > 0) {
content += `\n${functions.join('\n\n')}\n`;
}
const filePath = path.join(outDir, `${lang}.ts`);
fs.writeFileSync(filePath, content, 'utf-8');
console.log(`✅ Generated messages/${lang}.ts (${functions.length} translated, ${reExports.length} re-exported)`);
}
generateIndexFile(outDir, allKeys, defaultMessages, config);
}
function extractPlaceholders(value: string): string[] {
const matches = value.matchAll(placeholderRegex);
const placeholders = new Set<string>();
for (const match of matches) {
placeholders.add(match[1]);
}
return Array.from(placeholders);
}
function escapeTemplateString(value: string): string {
// Escape backticks and ${} that are not our placeholders
return value
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`');
}
function createMessageFunction(key: string, value: string): string {
const placeholders = extractPlaceholders(value);
const templateString = value.replace(placeholderRegex, '${params.$1}');
const escapedTemplate = escapeTemplateString(templateString);
if (placeholders.length > 0) {
const paramsType = placeholders.map(p => `${p}: string | number`).join(', ');
return `export const ${key} = (params: { ${paramsType} }): string => \`${escapedTemplate}\`;`;
}
return `export const ${key} = (): string => \`${escapedTemplate}\`;`;
}
function generateIndexFile(outDir: string, allKeys: string[], defaultMessages: Messages, config: GenerateConfig) {
const { enabledLanguages } = config;
const langImports = enabledLanguages
.map(lang => `import * as ${lang} from './${lang}.ts';`)
.join('\n');
const langObject = enabledLanguages.join(', ');
const languageType = enabledLanguages.map(l => `'${l}'`).join(' | ');
const messageExports: string[] = [];
for (const key of allKeys) {
const value = defaultMessages[key] || '';
const placeholders = extractPlaceholders(value);
const paramsType = placeholders.length > 0
? `params: { ${placeholders.map(p => `${p}: string | number`).join(', ')} }`
: '';
const paramsArg = paramsType ? 'params' : '';
messageExports.push(
`export const ${key} = (${paramsType}): string => allMessages[getLocale()].${key}(${paramsArg});`
);
}
const content = `${getFileHeader()}
import config from '@/config';
${langImports}
const allMessages = { ${langObject} } as const;
export type Language = ${languageType};
export const defaultLanguage: Language = '${config.defaultLanguage}';
export const enabledLanguages: readonly Language[] = ${JSON.stringify(config.enabledLanguages)} as const;
let _locale: Language = defaultLanguage;
export const getLocale = (): Language => _locale;
export const isValidLocale = (locale: unknown): locale is Language => enabledLanguages.includes(locale as Language);
export const setLocale = (locale: unknown): void => {
if (isValidLocale(locale)) {
_locale = locale as Language;
}
};
// Message functions
${messageExports.join('\n\n')}
`;
const filePath = path.join(outDir, 'index.ts');
fs.writeFileSync(filePath, content, 'utf-8');
console.log('✅ Generated messages/index.ts');
}