ddc71dc7f2
- 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
131 lines
4.1 KiB
JavaScript
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();
|