#!/usr/bin/env node /** * Build quiXzoom Asia pages from templates + translations * Outputs to dist/ with versioned assets */ const fs = require('fs'); const path = require('path'); const LANGUAGES = [ { code: 'zh-cn', name: 'Simplified Chinese', currency: 'CNY', symbol: '¥' }, { code: 'zh-tw', name: 'Traditional Chinese', currency: 'TWD', symbol: 'NT$' }, { code: 'ja', name: 'Japanese', currency: 'JPY', symbol: '¥' }, { code: 'ko', name: 'Korean', currency: 'KRW', symbol: '₩' }, { code: 'th', name: 'Thai', currency: 'THB', symbol: '฿' }, { code: 'vi', name: 'Vietnamese', currency: 'VND', symbol: '₫' }, { code: 'id', name: 'Indonesian', currency: 'IDR', symbol: 'Rp' }, { code: 'ms', name: 'Malay', currency: 'MYR', symbol: 'RM' }, { code: 'hi', name: 'Hindi', currency: 'INR', symbol: '₹' }, ]; const VERSION = process.env.VERSION || require('../package.json').version; const BUILD_TIME = new Date().toISOString(); function loadTemplate() { return fs.readFileSync(path.join(__dirname, '../src/template.html'), 'utf8'); } function loadTranslations(langCode) { const transPath = path.join(__dirname, `../src/translations/${langCode}.json`); if (!fs.existsSync(transPath)) { console.error(`❌ Missing translations: ${langCode}`); process.exit(1); } return JSON.parse(fs.readFileSync(transPath, 'utf8')); } function interpolate(template, vars) { // First pass: interpolate vars that contain other placeholders let result = template; let changed = true; let passes = 0; const MAX_PASSES = 5; while (changed && passes < MAX_PASSES) { changed = false; passes++; result = result.replace(/\{\{(\w+)\}\}/g, (match, key) => { if (vars[key] === undefined) { return match; // keep placeholder for next pass or final check } const value = vars[key]; if (value.includes('{{')) { changed = true; } return value; }); } // Final check for remaining placeholders const remaining = result.match(/\{\{(\w+)\}\}/g); if (remaining) { const unique = [...new Set(remaining.map(m => m.slice(2, -2)))].filter(k => vars[k] === undefined); for (const key of unique) { console.warn(`⚠️ Missing translation key: ${key}`); } } return result; } function build() { console.log(`🔨 Building quiXzoom Asia v${VERSION}...\n`); const distDir = path.join(__dirname, '../dist'); if (fs.existsSync(distDir)) { fs.rmSync(distDir, { recursive: true }); } fs.mkdirSync(distDir, { recursive: true }); const template = loadTemplate(); // Build root language selector const rootHtml = fs.readFileSync(path.join(__dirname, '../src/index.html'), 'utf8') .replace('{{VERSION}}', VERSION) .replace('{{BUILD_TIME}}', BUILD_TIME); fs.writeFileSync(path.join(distDir, 'index.html'), rootHtml); // Build each language for (const lang of LANGUAGES) { console.log(` 📄 ${lang.code} — ${lang.name}`); const translations = loadTranslations(lang.code); translations.VERSION = VERSION; translations.BUILD_TIME = BUILD_TIME; translations.LANG_CODE = lang.code; translations.LANG_NAME = lang.name; translations.CURRENCY = lang.currency; translations.CURRENCY_SYMBOL = lang.symbol; const html = interpolate(template, translations); const langDir = path.join(distDir, lang.code); fs.mkdirSync(langDir, { recursive: true }); fs.writeFileSync(path.join(langDir, 'index.html'), html); } // Copy static assets const assets = ['favicon.svg', 'apple-touch-icon.svg']; for (const asset of assets) { const src = path.join(__dirname, `../src/${asset}`); if (fs.existsSync(src)) { fs.copyFileSync(src, path.join(distDir, asset)); } } // Write build manifest const manifest = { version: VERSION, buildTime: BUILD_TIME, languages: LANGUAGES.map(l => l.code), gitCommit: process.env.GIT_COMMIT || 'unknown', gitBranch: process.env.GIT_BRANCH || 'unknown', }; fs.writeFileSync(path.join(distDir, 'manifest.json'), JSON.stringify(manifest, null, 2)); console.log(`\n✅ Build complete: ${distDir}/`); console.log(` ${LANGUAGES.length + 1} pages generated`); } build();