mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* feat(pro): realign /pro page copy with shipped product reality Marketing was selling 2024 positioning (equity research first) while the app ships a 2026 AI-native product (MCP on Pro, WM Analyst chat, Custom Widget Builder, 30-item AI digest with multi-channel push). Realigns hero, capabilities grid, FAQ, and showcase copy to honestly reflect what Pro actually delivers. Highlights: - Hero tagline: "The intelligence geopolitical AI layer — ask it, subscribe to it, build on it." - New Pillars block (Ask it / Subscribe to it / Build on it) below hero. - New Delivery Desk section reframing the 30-min digest as a personal analyst. - whyUpgrade quadrants rewritten as outcomes (Know first / Ask anything / Build anything / Wake up informed). - twoPath capabilities grid: WM Analyst, Custom Widget Builder, MCP connectors, flight search, market watchlist, AES-256 alerts; cuts WSB Ticker / Telegram Intel / OREF noise. - proShowcase: orbital surveillance moved to roadmap with inline Soon badge; morningBriefs reframed as Personal Intelligence Desk. - FAQ q8 corrected (MCP is Pro, not Enterprise-only); adds q9-q13 covering Custom Widget Builder, MCP, digest personalization, refresh rate, AES-256. - Pricing table proF4: "Early access pricing" replaced with roadmap-flagged "Priority data refresh (Soon)"; canonical price keys added for downstream use (PricingSection already reads tiers.json which has the real numbers). - Page spine reordered: Hero, Pillars, WhyUpgrade, TwoPath, ProShowcase, DeliveryDesk, AudiencePersonas, SocialProof, LivePreview, Pricing, FAQ. Plan: docs/plans/2026-04-17-001-copy-pro-page-rewrite-plan.md English-only this PR; other locales ship stale by design (tracked as follow-up). * chore(pro): rebuild /pro bundle for copy rewrite Regenerates public/pro/* artifacts so the AI-native copy realignment (see preceding commit) actually ships through the Vercel deploy. Per commit05b8f66ec, forgetting this rebuild is a known repeat bug. * feat(pro): translate /pro page to 20 locales matching new en.json copy Brings ar, bg, cs, de, el, es, fr, it, ja, ko, nl, pl, pt, ro, ru, sv, th, tr, vi, zh up to the new AI-native positioning shipped in en.json (PR #3151). Without this, non-English visitors still saw the stale 2024 'equity research + geopolitical + macro analyst' marketing copy. Product names (WorldMonitor, MCP, Slack, Claude, GPT, Brent, AIS, FRED, ACLED, etc.) kept in English. 'Soon' badge translated per-locale (Próximamente / Bientôt / Bald / 即将推出 / 近日公開 / قريباً …). Also rebuilds public/pro bundle with updated locale chunks. * fix(pro): repair static SEO layer (prerender, JSON-LD, OG, noscript) Three problems shipping to crawlers and non-JS users: 1. pro-test/prerender.mjs read deleted hero keys (en.hero.title1/title2/subtitle/missionLine), so public/pro/index.html literally contained <h1>undefined undefined</h1> and undefined paragraphs. Now reads existing keys (noiseWord, signalWord, valueProps, launchingDate) and surfaces the new pillars + deliveryDesk blocks. Added a guard that fails the build if any interpolation resolves to 'undefined'. 2. pro-test/prerender.mjs only emitted q1-q8. Extended to q1-q13 to match the new FAQ. 3. pro-test/index.html static head still carried 2024 positioning: title/description/keywords, OG/Twitter tags, SoftwareApplication ld+json (description, featureList), FAQ ld+json (q8 said MCP was Enterprise-only, missing q9-q13), and noscript fallback. All updated to AI-native positioning matching the new en.json. Without this fix, every rebuild reintroduced stale SEO/social cards even though the React app rendered the new copy. * fix(pro): drop pre-launch messaging — Pro is shipped The PR repositioned /pro around already-shipped AI-native features, but two strings still said the opposite: - pricingTable.proHeader = 'Pro (Early Access)' rendered in the comparison table at App.tsx:776 - hero.launchingDate = 'Launching March 2026' got prerendered into public/pro/index.html for crawlers Both gone, across all 21 locales: - proHeader → 'Pro ($39.99)' (matches free 'Free ($0)' pattern, surfaces real pricing) - launchingDate → per-locale 'Live now / Disponible ahora / Disponible maintenant / Jetzt verfügbar / 现已上线 / 現在ご利用可能 / 지금 이용 가능 / متاح الآن / …' Pro is launched (per commit7693a4fa4'cut /pro over from waitlist → Pro-launched messaging'); waitlist phrasing in the hero/SEO contradicted that. Bundle rebuilt. * fix(pro): match marketing to actual notification capabilities Two overclaims removed across all 21 locales + static SEO: 1. WhatsApp delivery does not exist. Supported channels in src/services/notification-channels.ts are telegram | slack | email | discord | webhook. Removed WhatsApp from every delivery-channel listing (deliveryDesk channels list, pillars subscribeItDesc, twoPath proF2, proShowcase morningBriefsDesc, OG/Twitter descriptions, JSON-LD featureList, noscript fallback). Kept referral.whatsapp because that's a 'share this page to WhatsApp' button, not a delivery claim. 2. 'AI digest every 30 minutes' was wrong. The 30-min cron in scripts/seed-digest-notifications.mjs is just the scheduler frequency; user-facing cadence is daily | twice_daily | weekly per src/services/notification-channels.ts. Replaced with truthful 'daily, twice-daily, or weekly cadence' across deliveryDesk body, pillars subscribeItDesc, twoPath proF2, proShowcase morningBriefsDesc, faq a11, plus all static SEO copies. 'Real-time alerts' surfaced where appropriate (the realtime mode is real and separate from digest). Channel list now: Slack, Discord, Telegram, Email, webhook. * fix(pro): align rendered UI with shipped channels + i18n SoonBadge Three remaining mismatches with src/services/notification-channels.ts: 1. App.tsx delivery icon strip (proShowcase) still rendered a hardcoded WhatsApp icon and omitted Webhook. Replaced with Slack/Discord/Telegram/Email/Webhook (Plug icon for webhook), matching the actual channel enum. 2. pricingTable.fSlackTgWa value was 'Slack/TG/WA/Email' across all 21 locales, rendered in the comparison table at App.tsx:758. Updated to 'Slack/Discord/TG/Email/Webhook' (per-locale email word). Key name kept (renaming would touch every locale's structure unnecessarily); only the value changed. 3. SoonBadge component hardcoded 'Soon' in English, leaking through localized /pro?lang=… pages. Now uses t('soonBadge'); added a soonBadge i18n key per locale (Próximamente / Bientôt / Bald / Presto / Em breve / Binnenkort / Snart / Wkrótce / Brzy / În curând / Σύντομα / Скоро / Скоро / Yakında / 即将推出 / 近日公開 / 출시 예정 / เร็วๆ นี้ / Sắp ra mắt / قريباً). Remaining 'WhatsApp' string in bundle is referral.whatsapp — the share button label, which is correct (people share TO WhatsApp; we don't deliver via it).
119 lines
4.9 KiB
JavaScript
119 lines
4.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Postbuild prerender script — injects critical SEO content into the built HTML
|
|
* so search engines see real content without executing JavaScript.
|
|
*
|
|
* Reads only keys that exist in pro-test/src/locales/en.json. If you remove a
|
|
* key, also remove it here, otherwise the build will inject the literal string
|
|
* "undefined" into the page that crawlers index.
|
|
*/
|
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import { resolve, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const htmlPath = resolve(__dirname, '../public/pro/index.html');
|
|
|
|
const en = JSON.parse(readFileSync(resolve(__dirname, 'src/locales/en.json'), 'utf-8'));
|
|
|
|
const seoContent = `
|
|
<div id="seo-prerender" style="position:absolute;left:-9999px;top:-9999px;overflow:hidden;width:1px;height:1px;">
|
|
<h1>World Monitor Pro — From ${en.hero.noiseWord} to ${en.hero.signalWord}</h1>
|
|
<p>${en.hero.valueProps}</p>
|
|
<p>${en.hero.launchingDate}</p>
|
|
|
|
<h2>Three pillars</h2>
|
|
<h3>${en.pillars.askIt}</h3><p>${en.pillars.askItDesc}</p>
|
|
<h3>${en.pillars.subscribeIt}</h3><p>${en.pillars.subscribeItDesc}</p>
|
|
<h3>${en.pillars.buildOnIt}</h3><p>${en.pillars.buildOnItDesc}</p>
|
|
|
|
<h2>Plans</h2>
|
|
<h3>${en.twoPath.proTitle}</h3>
|
|
<p>${en.twoPath.proDesc}</p>
|
|
<p>${en.twoPath.proF1}</p>
|
|
<p>${en.twoPath.proF2}</p>
|
|
<p>${en.twoPath.proF3}</p>
|
|
<p>${en.twoPath.proF4}</p>
|
|
<p>${en.twoPath.proF5}</p>
|
|
<p>${en.twoPath.proF6}</p>
|
|
<p>${en.twoPath.proF7}</p>
|
|
<p>${en.twoPath.proF8}</p>
|
|
<p>${en.twoPath.proF9}</p>
|
|
|
|
<h3>${en.twoPath.entTitle}</h3>
|
|
<p>${en.twoPath.entDesc}</p>
|
|
|
|
<h2>${en.whyUpgrade.title}</h2>
|
|
<h3>${en.whyUpgrade.noiseTitle}</h3><p>${en.whyUpgrade.noiseDesc}</p>
|
|
<h3>${en.whyUpgrade.fasterTitle}</h3><p>${en.whyUpgrade.fasterDesc}</p>
|
|
<h3>${en.whyUpgrade.controlTitle}</h3><p>${en.whyUpgrade.controlDesc}</p>
|
|
<h3>${en.whyUpgrade.deeperTitle}</h3><p>${en.whyUpgrade.deeperDesc}</p>
|
|
|
|
<h2>${en.proShowcase.title}</h2>
|
|
<p>${en.proShowcase.subtitle}</p>
|
|
<h3>${en.proShowcase.equityResearch}</h3><p>${en.proShowcase.equityResearchDesc}</p>
|
|
<h3>${en.proShowcase.geopoliticalAnalysis}</h3><p>${en.proShowcase.geopoliticalAnalysisDesc}</p>
|
|
<h3>${en.proShowcase.economyAnalytics}</h3><p>${en.proShowcase.economyAnalyticsDesc}</p>
|
|
<h3>${en.proShowcase.riskMonitoring}</h3><p>${en.proShowcase.riskMonitoringDesc}</p>
|
|
<h3>${en.proShowcase.orbitalSurveillance}</h3><p>${en.proShowcase.orbitalSurveillanceDesc}</p>
|
|
<h3>${en.proShowcase.morningBriefs}</h3><p>${en.proShowcase.morningBriefsDesc}</p>
|
|
<h3>${en.proShowcase.oneKey}</h3><p>${en.proShowcase.oneKeyDesc}</p>
|
|
|
|
<h2>${en.deliveryDesk.title}</h2>
|
|
<p>${en.deliveryDesk.body}</p>
|
|
<p>${en.deliveryDesk.closer}</p>
|
|
<p>${en.deliveryDesk.channels}</p>
|
|
|
|
<h2>${en.audience.title}</h2>
|
|
<h3>${en.audience.investorsTitle}</h3><p>${en.audience.investorsDesc}</p>
|
|
<h3>${en.audience.tradersTitle}</h3><p>${en.audience.tradersDesc}</p>
|
|
<h3>${en.audience.researchersTitle}</h3><p>${en.audience.researchersDesc}</p>
|
|
<h3>${en.audience.journalistsTitle}</h3><p>${en.audience.journalistsDesc}</p>
|
|
<h3>${en.audience.govTitle}</h3><p>${en.audience.govDesc}</p>
|
|
<h3>${en.audience.teamsTitle}</h3><p>${en.audience.teamsDesc}</p>
|
|
|
|
<h2>${en.dataCoverage.title}</h2>
|
|
<p>${en.dataCoverage.subtitle}</p>
|
|
|
|
<h2>${en.apiSection.title}</h2>
|
|
<p>${en.apiSection.subtitle}</p>
|
|
|
|
<h2>${en.enterpriseShowcase.title}</h2>
|
|
<p>${en.enterpriseShowcase.subtitle}</p>
|
|
|
|
<h2>${en.pricingTable.title}</h2>
|
|
<p>${en.tiers.priceMonthly} · ${en.tiers.priceAnnual} (${en.tiers.annualSavingsNote})</p>
|
|
|
|
<h2>${en.faq.title}</h2>
|
|
<dl>
|
|
<dt>${en.faq.q1}</dt><dd>${en.faq.a1}</dd>
|
|
<dt>${en.faq.q2}</dt><dd>${en.faq.a2}</dd>
|
|
<dt>${en.faq.q3}</dt><dd>${en.faq.a3}</dd>
|
|
<dt>${en.faq.q4}</dt><dd>${en.faq.a4}</dd>
|
|
<dt>${en.faq.q5}</dt><dd>${en.faq.a5}</dd>
|
|
<dt>${en.faq.q6}</dt><dd>${en.faq.a6}</dd>
|
|
<dt>${en.faq.q7}</dt><dd>${en.faq.a7}</dd>
|
|
<dt>${en.faq.q8}</dt><dd>${en.faq.a8}</dd>
|
|
<dt>${en.faq.q9}</dt><dd>${en.faq.a9}</dd>
|
|
<dt>${en.faq.q10}</dt><dd>${en.faq.a10}</dd>
|
|
<dt>${en.faq.q11}</dt><dd>${en.faq.a11}</dd>
|
|
<dt>${en.faq.q12}</dt><dd>${en.faq.a12}</dd>
|
|
<dt>${en.faq.q13}</dt><dd>${en.faq.a13}</dd>
|
|
</dl>
|
|
|
|
<h2>${en.finalCta.title}</h2>
|
|
<p>${en.finalCta.subtitle}</p>
|
|
</div>`;
|
|
|
|
// Fail loudly if any key resolved to undefined — this prevents the build from
|
|
// silently shipping "undefined" strings to crawlers.
|
|
if (seoContent.includes('undefined')) {
|
|
console.error('[prerender] ERROR: SEO content contains literal "undefined". Check that all en.json keys referenced in this file exist.');
|
|
process.exit(1);
|
|
}
|
|
|
|
let html = readFileSync(htmlPath, 'utf-8');
|
|
html = html.replace('<div id="root"></div>', `<div id="root">${seoContent}</div>`);
|
|
writeFileSync(htmlPath, html, 'utf-8');
|
|
console.log('[prerender] Injected SEO content into public/pro/index.html');
|