241ac64c18
- Replaced Zoomer-only Landvex auth with full Partner model - New Partner section: Platform License, Local Operations, Payment Infrastructure, Revenue Share - 6 Partner Requirements: Local Entity, Payment Licenses, Technical Team, Local Network, Compliance, Capitalization - 6-step Authorization Process: Application → Due Diligence → Technical Assessment → Commercial Terms → Localization → Go-Live - Updated all 9 languages with partner translations - Added FAQ: 'Who operates quiXzoom?' + 'Can my company become a partner?' - Fixed JSON escaping issues (Chinese quotes) - Updated validation script for new partner keys BREAKING CHANGE: Landvex Authorization section replaced with Partner Program
98 lines
3.9 KiB
JavaScript
98 lines
3.9 KiB
JavaScript
#!/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_PARTNERS', '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',
|
|
'PARTNER_TITLE', 'PARTNER_SUB', 'PARTNER_HERO_DESC',
|
|
'PARTNER_MODEL_TITLE', 'PARTNER_MODEL_SUB',
|
|
'PARTNER_M1_TITLE', 'PARTNER_M1_DESC', 'PARTNER_M2_TITLE', 'PARTNER_M2_DESC',
|
|
'PARTNER_M3_TITLE', 'PARTNER_M3_DESC', 'PARTNER_M4_TITLE', 'PARTNER_M4_DESC',
|
|
'PARTNER_REQ_TITLE', 'PARTNER_REQ_SUB',
|
|
'PARTNER_R1_TITLE', 'PARTNER_R1_DESC', 'PARTNER_R2_TITLE', 'PARTNER_R2_DESC',
|
|
'PARTNER_R3_TITLE', 'PARTNER_R3_DESC', 'PARTNER_R4_TITLE', 'PARTNER_R4_DESC',
|
|
'PARTNER_R5_TITLE', 'PARTNER_R5_DESC', 'PARTNER_R6_TITLE', 'PARTNER_R6_DESC',
|
|
'PARTNER_AUTH_TITLE', 'PARTNER_AUTH_SUB',
|
|
'PARTNER_A1_TITLE', 'PARTNER_A1_DESC', 'PARTNER_A2_TITLE', 'PARTNER_A2_DESC',
|
|
'PARTNER_A3_TITLE', 'PARTNER_A3_DESC', 'PARTNER_A4_TITLE', 'PARTNER_A4_DESC',
|
|
'PARTNER_A5_TITLE', 'PARTNER_A5_DESC', 'PARTNER_A6_TITLE', 'PARTNER_A6_DESC',
|
|
'PARTNER_CTA', 'PARTNER_CTA_SUB',
|
|
'FAQ_TITLE', 'FAQ_SUB', 'FAQ_Q1', 'FAQ_A1', 'FAQ_Q2', 'FAQ_A2',
|
|
'FAQ_Q3', 'FAQ_A3', 'FAQ_Q4', 'FAQ_A4', 'FAQ_Q5', 'FAQ_A5',
|
|
'FAQ_Q6', 'FAQ_A6', 'FAQ_Q7', 'FAQ_A7',
|
|
'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');
|
|
}
|