Files
boc/quixzoom-asia-pages/scripts/build.js
T
Bernt ddc71dc7f2 feat(quixzoom-asia): Full rebuild v1.0.0 — 9 languages, Landvex auth, light theme
- Rebuilt all 9 Asian language landing pages (zh-CN, zh-TW, ja, ko, th, vi, id, ms, hi)
- Switched from dark to light theme per MOBILE_FIRST_STANDARD
- Added Landvex Authorization section (VW-style certification flow)
- Added full copy: hero, stats, how-it-works, missions, earnings, FAQ, footer
- Localized currencies and payment methods per market
- Build system: template + JSON translations → static HTML
- Validation script ensures no missing translations or placeholder leaks
- CI/CD pipeline: GitHub Actions → S3 + CloudFront
- Version management: npm version patch/minor/major

BREAKING CHANGE: Old placeholder pages replaced with full content
2026-07-08 10:27:21 +00:00

131 lines
4.1 KiB
JavaScript

#!/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();