#!/usr/bin/env node /** * Validate translations completeness and HTML output */ const fs = require('fs'); const path = require('path'); const LANGUAGES = ['zh-cn', 'zh-tw', 'ja', 'ko', 'th', 'vi', 'id', 'ms', 'hi']; const REQUIRED_KEYS = [ 'TITLE', 'META_DESC', 'NAV_HOW', 'NAV_MISSIONS', 'NAV_EARNINGS', 'NAV_LANDVEX', 'NAV_FAQ', 'CTA_START', 'HERO_BADGE', 'HERO_H1', 'HERO_H1_EMPH', 'HERO_SUB', 'EMAIL_PLACEHOLDER', 'TRUST_LINE', 'STAT1_NUM', 'STAT1_LBL', 'STAT2_NUM', 'STAT2_LBL', 'STAT3_NUM', 'STAT3_LBL', 'STATS_1_VAL', 'STATS_1_LBL', 'STATS_2_VAL', 'STATS_2_LBL', 'STATS_3_VAL', 'STATS_3_LBL', 'STATS_4_VAL', 'STATS_4_LBL', 'HOW_TITLE', 'HOW_SUB', 'STEP1_TITLE', 'STEP1_DESC', 'STEP2_TITLE', 'STEP2_DESC', 'STEP3_TITLE', 'STEP3_DESC', 'MISSIONS_TITLE', 'MISSIONS_SUB', 'M1_TITLE', 'M1_DESC', 'M1_CHIP', 'M2_TITLE', 'M2_DESC', 'M2_CHIP', 'M3_TITLE', 'M3_DESC', 'M3_CHIP', 'EARNINGS_TITLE', 'EARNINGS_SUB', 'TIER1_NAME', 'TIER1_MISSIONS', 'TIER1_F1', 'TIER1_F2', 'TIER1_F3', 'TIER2_NAME', 'TIER2_MISSIONS', 'TIER2_F1', 'TIER2_F2', 'TIER2_F3', 'TIER3_NAME', 'TIER3_MISSIONS', 'TIER3_F1', 'TIER3_F2', 'TIER3_F3', 'AUTH_TITLE', 'AUTH_SUB', 'AUTH_VISUAL_TITLE', 'AUTH_VISUAL_DESC', 'AUTH_B1', 'AUTH_B2', 'AUTH_B3', 'AUTH_B4', 'AUTH_S1_TITLE', 'AUTH_S1_DESC', 'AUTH_S2_TITLE', 'AUTH_S2_DESC', 'AUTH_S3_TITLE', 'AUTH_S3_DESC', 'AUTH_S4_TITLE', 'AUTH_S4_DESC', 'AUTH_CTA', 'FAQ_TITLE', 'FAQ_SUB', 'FAQ_Q1', 'FAQ_A1', 'FAQ_Q2', 'FAQ_A2', 'FAQ_Q3', 'FAQ_A3', 'FAQ_Q4', 'FAQ_A4', 'FAQ_Q5', 'FAQ_A5', 'FOOTER_TAGLINE', 'FOOTER_COL1_TITLE', 'FOOTER_COL1_L1', 'FOOTER_COL1_L2', 'FOOTER_COL1_L3', 'FOOTER_COL1_L4', 'FOOTER_COL2_TITLE', 'FOOTER_COL2_L1', 'FOOTER_COL2_L2', 'FOOTER_COL2_L3', 'FOOTER_COL3_TITLE', 'FOOTER_COL3_L1', 'FOOTER_COL3_L2', 'FOOTER_COL3_L3', 'COPYRIGHT', 'LANG_LABEL', 'PHONE_MISSION', 'PHONE_ADDR', 'PHONE_REWARD', 'PHONE_META', ]; let errors = 0; console.log('šŸ” Validating translations...\n'); for (const lang of LANGUAGES) { const transPath = path.join(__dirname, `../src/translations/${lang}.json`); if (!fs.existsSync(transPath)) { console.error(`āŒ Missing: ${lang}.json`); errors++; continue; } const trans = JSON.parse(fs.readFileSync(transPath, 'utf8')); const missing = REQUIRED_KEYS.filter(k => !trans[k] || trans[k].trim() === ''); if (missing.length > 0) { console.error(`āŒ ${lang}: Missing ${missing.length} keys`); for (const key of missing.slice(0, 5)) { console.error(` - ${key}`); } if (missing.length > 5) console.error(` ... and ${missing.length - 5} more`); errors++; } else { console.log(`āœ… ${lang}: Complete`); } } // Check for placeholder leaks const distDir = path.join(__dirname, '../dist'); if (fs.existsSync(distDir)) { console.log('\nšŸ” Checking for placeholder leaks in dist/...'); for (const lang of LANGUAGES) { const htmlPath = path.join(distDir, lang, 'index.html'); if (fs.existsSync(htmlPath)) { const html = fs.readFileSync(htmlPath, 'utf8'); const leaks = html.match(/\{\{\w+\}\}/g); if (leaks) { console.error(`āŒ ${lang}: ${leaks.length} placeholders leaked`); errors++; } } } } if (errors > 0) { console.log(`\nāŒ ${errors} error(s) found`); process.exit(1); } else { console.log('\nāœ… All validations passed'); }