import fs from 'node:fs/promises'; import path from 'node:path'; import vm from 'node:vm'; const root = path.resolve(import.meta.dirname, '..'); export async function loadI18n(context) { for (const name of ['texts-de', 'i18n']) { const filename = path.join(root, `public/js/${name}.js`); vm.runInNewContext(await fs.readFile(filename, 'utf8'), context, { filename }); } return context.Wegwichtel.I18n; } const escapeText = value => value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>'); export async function localizedHtml(source) { const i18n = await loadI18n({}); let result = source.replace(/(<[^>]+\bdata-i18n="([^"]+)"[^>]*>)([^<]*)(<\/)/g, (_match, opening, key, _text, closing) => opening + escapeText(i18n.t(key)) + closing); result = result.replace(/<[^>]+>/g, opening => { let translated = opening; for (const attribute of ['title', 'aria-label', 'placeholder', 'alt']) { const key = new RegExp(`data-i18n-${attribute}="([^"]+)"`).exec(opening)?.[1]; if (key) translated = translated.replace(new RegExp(`\\b${attribute}="[^"]*"`), `${attribute}="${escapeText(i18n.t(key)).replaceAll('"', '"')}"`); } return translated; }); return result.replace(/\sdata-i18n(?:-[\w-]+)?="[^"]*"/g, ''); } export async function readAppFile(relativePath) { const source = await fs.readFile(path.join(root, relativePath), 'utf8'); if (relativePath === 'public/index.html') return localizedHtml(source); if (relativePath.startsWith('public/js/')) { // Static text assertions read the centralized wording; runtime tests use the real class. return source + '\n' + await fs.readFile(path.join(root, 'public/js/texts-de.js'), 'utf8'); } return source; }