17 KiB
EN Demo UI i18n Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Make website/1/en/demo hardcoded UI English, and make shared JS show zh/en copy from a lightweight t() dictionary based on URL language.
Architecture: Add getUiLang() + t(key, vars?) + a zh/en map in common.js (exposed on window). Replace user-visible Chinese in shared JS with t(). Translate EN demo HTML/static strings in place. Leave comments and CMS-bound fields alone.
Tech Stack: ThinkPHP theme HTML, shared Vite-built static JS under public/themes/dist_static/static/js/, existing URL lang prefix (/en/...).
Spec: docs/superpowers/specs/2026-09-07-en-demo-ui-i18n-design.md
Global Constraints
- Translate user-visible hardcoded UI only (not code comments).
- Do not translate CMS/tag output (
{$vo.title}, nav titles from backend, etc.). - Do not modify
website/1/zh/demo/templates for copy (shared JS only, bilingual). - Do not translate SVG internal
id="矢量…"/id="微信 1"attributes (not user-visible). - Brand in EN UI: RAYNEN / Raynen Technology.
- Currency wording in EN: use Yuan (e.g. footer unit text).
- UI lang: first path segment
en→en, elsezh(same as header EN/CN mapping). - Missing
tkey: return zh string if present, else the key; never throw. - Verification is grep + manual smoke (no PHPUnit/Jest in this theme path).
File map
| File | Responsibility |
|---|---|
public/themes/dist_static/static/js/common.js |
getUiLang, t, dictionary, stock delay string via t |
video.js / search.js / about.js / announce.js / category.js |
Replace hardcoded UI Chinese with t() |
earth.js |
Bilingual city names + Fuzhou tooltip |
public/themes/website/1/en/demo/components/*.html |
Header/footer/mask/empty EN chrome |
public/themes/website/1/en/demo/*.html |
Page EN chrome + lang="en" + inline script strings |
Task 1: Add getUiLang + t + base dictionary in common.js
Files:
- Modify:
public/themes/dist_static/static/js/common.js - Test: browser console / grep (no unit test harness)
Interfaces:
-
Produces:
getUiLang(): 'en' | 'zh't(key: string, vars?: Record<string, string|number>): stringwindow.getUiLang,window.t(same functions)
-
Consumes:
location.pathname; optional.site-header__lang[data-lang-current] -
Step 1: Insert helper + dictionary near top of the
$(function(){...})region (after Lenis/AOS init is fine; before stock refresh)
Add (exact initial keys — extend later if grep finds more):
const UI_I18N = {
zh: {
"video.play": "视频播放",
"filter.by_product": "按产品筛选",
"about.expand_more": "展开更多",
"about.collapse": "收起",
"brand.short": "睿能科技",
"stock.delay_note": "截止 {time}*报价有十五分钟或以上延迟。",
"contact.name_required": "请输入姓名",
"contact.phone_required": "请输入手机号",
"contact.company_required": "请输入公司名称",
"contact.email_required": "请输入邮箱",
"contact.submitting": "提交中...",
"contact.submit": "提交",
"contact.success": "提交成功,我们会尽快与您联系!",
"contact.fail": "提交失败,请稍后重试",
"contact.network_error": "网络错误,请稍后重试",
"earth.hq_fuzhou": "全球总部福州",
"earth.hq_fuzhou_html": "全球总部<br>福州"
},
en: {
"video.play": "Video",
"filter.by_product": "Filter by product",
"about.expand_more": "Show more",
"about.collapse": "Show less",
"brand.short": "RAYNEN",
"stock.delay_note": "As of {time}* Quotes delayed by 15 minutes or more.",
"contact.name_required": "Please enter your name",
"contact.phone_required": "Please enter your phone number",
"contact.company_required": "Please enter your company name",
"contact.email_required": "Please enter your email",
"contact.submitting": "Submitting...",
"contact.submit": "Submit",
"contact.success": "Submitted successfully. We will contact you soon.",
"contact.fail": "Submission failed. Please try again later.",
"contact.network_error": "Network error. Please try again later.",
"earth.hq_fuzhou": "Global HQ Fuzhou",
"earth.hq_fuzhou_html": "Global HQ<br>Fuzhou"
}
};
function getUiLang() {
const fromHeader = document.querySelector(".site-header__lang")?.getAttribute("data-lang-current");
if (fromHeader === "en" || fromHeader === "zh") return fromHeader;
const seg = location.pathname.replace(/^\//, "").split("/")[0] || "";
return seg === "en" ? "en" : "zh";
}
function t(key, vars) {
const lang = getUiLang();
const table = UI_I18N[lang] || UI_I18N.zh;
let str = table[key] ?? UI_I18N.zh[key] ?? key;
if (vars && typeof vars === "object") {
Object.keys(vars).forEach((k) => {
str = str.replace(new RegExp(`\\{${k}\\}`, "g"), String(vars[k]));
});
}
return str;
}
window.getUiLang = getUiLang;
window.t = t;
- Step 2: Replace stock delay assignment to use
t
Find in initStockQuoteRefresh / applyQuote:
timeEl.textContent = `截止 ${data.updateTime}*报价有十五分钟或以上延迟。`;
Replace with:
timeEl.textContent = t("stock.delay_note", { time: data.updateTime });
- Step 3: Verify helper in browser
Open /en/ any page with DevTools:
getUiLang() // "en"
t("video.play") // "Video"
t("stock.delay_note", { time: "2026-09-07 10:00:00" })
Open a zh page: getUiLang() → "zh", t("video.play") → "视频播放".
- Step 4: Commit
git add public/themes/dist_static/static/js/common.js
git commit -m "feat(i18n): add getUiLang/t dictionary for shared UI strings"
Task 2: Wire shared page JS to t()
Files:
- Modify:
public/themes/dist_static/static/js/video.js - Modify:
public/themes/dist_static/static/js/search.js - Modify:
public/themes/dist_static/static/js/about.js - Modify:
public/themes/dist_static/static/js/announce.js - Modify:
public/themes/dist_static/static/js/category.js
Interfaces:
-
Consumes:
window.t/t(common.js loaded first viaimport "./common.js") -
Step 1: Update
video.js
Replace "视频播放" defaults with t("video.play").
Replace $btn.find("span").text("按产品筛选") with t("filter.by_product").
- Step 2: Update
search.js
Replace "视频播放" fallbacks with t("video.play").
Keep the /^播放[::]/ strip for aria-label parsing (works for Chinese CMS labels); if EN aria uses Play:, also allow:
card.getAttribute("aria-label")?.replace(/^(播放|Play)\s*[::]\s*/i, "").trim() || t("video.play")
- Step 3: Update
about.js
btn.querySelector("span").textContent = expanded ? t("about.collapse") : t("about.expand_more");
- Step 4: Update
announce.js
$btn.find("span").text(t("filter.by_product"));
- Step 5: Update
category.js
if (name) document.title = `${name} - ${t("brand.short")}`;
-
Step 6: Smoke
-
/en/video.html(or routed EN video): default modal title English when empty. -
/aboutzh: toggle still 展开更多/收起. -
/en/about: Show more / Show less. -
Step 7: Commit
git add public/themes/dist_static/static/js/video.js public/themes/dist_static/static/js/search.js public/themes/dist_static/static/js/about.js public/themes/dist_static/static/js/announce.js public/themes/dist_static/static/js/category.js
git commit -m "feat(i18n): localize shared page JS UI strings via t()"
Task 3: Localize earth.js city names + HQ tooltip
Files:
- Modify:
public/themes/dist_static/static/js/earth.js
Interfaces:
-
Consumes:
getUiLang(),t() -
Produces: markers whose displayed
namematches UI lang -
Step 1: Change
markersDataentries to bilingual names
Pattern for each city:
{
nameZh: "上海",
nameEn: "Shanghai",
lat: 31.2304,
lon: 121.4737
},
Full English map (use exactly):
| nameZh | nameEn |
|---|---|
| 上海 | Shanghai |
| 东京 | Tokyo |
| 伊斯坦布尔 | Istanbul |
| 伦敦 | London |
| 加尔各答 | Kolkata |
| 北京 | Beijing |
| 华沙 | Warsaw |
| 南京 | Nanjing |
| 吉隆坡 | Kuala Lumpur |
| 圣保罗 | São Paulo |
| 圣安东尼奥 | San Antonio |
| 墨西哥城 | Mexico City |
| 多伦多 | Toronto |
| 奥克兰 | Auckland |
| 孟买 | Mumbai |
| 安地比斯 | (keep transliteration from existing intent; if unclear use original pinyin/English already known — Antananarivo only if that was the city; otherwise keep a clear English label matching lat/lon) |
| 山景城 | Mountain View |
| 巴黎 | Paris |
| 布宜诺斯艾利斯 | Buenos Aires |
| 开普敦 | Cape Town |
| 悉尼 | Sydney |
| 新加坡 | Singapore |
| 新德里 | New Delhi |
| 曼谷 | Bangkok |
| 法兰克福 | Frankfurt |
| 波哥大 | Bogotá |
| 深圳 | Shenzhen |
| 牛津 | Oxford |
| 特拉维夫 | Tel Aviv |
| 科伦坡 | Colombo |
| 米兰 | Milan |
| 纽约 | New York |
| 莫斯科 | Moscow |
| 达卡 | Dhaka |
| 迪拜 | Dubai |
| 邦加罗尔 | Bengaluru |
| 金奈 | Chennai |
| 雅加达 | Jakarta |
| 首尔 | Seoul |
| 香港 | Hong Kong |
| 全球总部福州 | Global HQ Fuzhou |
If “安地比斯” coordinates are unclear, keep nameEn as a reasonable English label and do not invent a wrong city.
- Step 2: Resolve display name when creating markers
Replace data.name usage:
function markerName(data) {
return getUiLang() === "en" ? (data.nameEn || data.nameZh) : (data.nameZh || data.nameEn);
}
Update:
if (data.name === "全球总部福州")→if (data.nameZh === "全球总部福州")marker.userData = { name: markerName(data) };fuzhouTooltip.innerHTML = t("earth.hq_fuzhou_html");
Ensure earth.js runs after common.js on pages that load both (home/about already modulepreload common via page entry). If earth.js is a standalone module without import, add at top of its runtime:
const getUiLang = window.getUiLang || (() => (location.pathname.split("/")[1] === "en" ? "en" : "zh"));
const t = window.t || ((k) => k);
-
Step 3: Smoke
-
/en/home or about: hover marker → English city name; Fuzhou tooltip English. -
zh home: Chinese names unchanged.
-
Step 4: Commit
git add public/themes/dist_static/static/js/earth.js
git commit -m "feat(i18n): bilingual earth marker city names"
Task 4: Translate EN shared components (header / footer / mask / empty)
Files:
-
Modify:
public/themes/website/1/en/demo/components/header.html -
Modify:
public/themes/website/1/en/demo/components/footer.html -
Modify:
public/themes/website/1/en/demo/components/mask.html -
Modify:
public/themes/website/1/en/demo/components/empty.html -
Step 1: Translate user-visible hardcoded strings
Examples (apply consistently; leave PHP comments Chinese):
aria-label="社交账号"→Social accountsaria-label="微信"→WeChat(popup caption官方微信→Official WeChat)aria-label="抖音"→Douyin(or TikTok if product prefers — use Douyin)微博→Weibo;知乎→Zhihu切换语言→Switch language主导航/移动导航→Main navigation/Mobile navigation搜索→Search;关闭导航菜单→Close menu展开/收起→Expand/collapse- Logo
aria-label/alt:RAYNENorRAYNEN Technology(drop Chinese in EN alt) - Footer:
股票代码→Stock code;关注我们:→Follow us:;官方微信→Official WeChat;官方视频号→Official Channels;版权所有→All rights reserved;网站地图→Sitemap;隐私声明→Privacy Policy - Footer unit
元→Yuan - Promo hardcoded
了解睿能最新展会信息→Learn about RAYNEN’s latest events(only if hardcoded, not from CMS) - Hardcoded
解决方案label if not from$vo.title→Solutions
Do not change {hcTaglib:nav} titles.
- Step 2: Grep components for leftover visible Chinese
rg -n "[\p{Han}]" public/themes/website/1/en/demo/components --glob "*.html"
Ignore: comments, SVG ids, PHP comments, CMS echoes.
- Step 3: Commit
git add public/themes/website/1/en/demo/components
git commit -m "feat(i18n): English copy for EN demo header/footer/mask/empty"
Task 5: Translate EN page templates + set lang="en"
Files:
- Modify all under
public/themes/website/1/en/demo/*.htmlthat still havelang="zh-CN"or hardcoded Chinese UI
Pages: single_index, contact, product, about, investment, solution, solution2, news, news-detail, search, single_search, download, faq, video, announce, category
- Step 1: Batch fix
htmllang
Replace on every EN demo page:
<html lang="zh-CN">
→
<html lang="en">
- Step 2: Translate page chrome (common patterns)
Apply across pages where hardcoded:
| zh | en |
|---|---|
| 探索更多 | Explore more |
| 立即下载 | Download |
| 请输入您想了解的内容 | Search what you want to know |
| 请输入您想下载的文件 | Search files to download |
| 请输入您想搜索问题的关键词 | Search FAQ keywords |
| 请输入您想搜索视频的关键词 | Search video keywords |
| 暂无相关资料 / 问题 / 视频 | No matching resources / FAQs / videos |
| 视频播放 | Video |
| 了解产品 | Learn more |
| 资料下载 | Downloads |
| 股票信息 / 股票代码 | Stock information / Stock code |
| 最高(元)等 metrics | High (Yuan), Low (Yuan), Volume (10k lots), Turnover (CNY 10k) — keep units clear |
| 客服热线 | Hotline |
| 下载中心 / 常见问题 / 视频教程 / 产品公告 | Download Center / FAQ / Video Tutorials / Product Notices |
| Contact form labels/placeholders/options/submit | Natural EN (Name, Phone, Company, Email, Industry, Business type, …) |
contact.html select options: translate both visible text and value to English in the EN template only (EN form posts English values).
- Step 3: Translate
contact.htmlinline script strings viawindow.t
if (!name) { alert(t("contact.name_required")); return; }
if (!phone) { alert(t("contact.phone_required")); return; }
if (!company) { alert(t("contact.company_required")); return; }
if (!email) { alert(t("contact.email_required")); return; }
// ...
$submitBtn.prop("disabled", true).text(t("contact.submitting"));
// success:
showFormToast(t("contact.success"));
// fail:
alert(res.msg || t("contact.fail"));
// network:
alert(t("contact.network_error"));
// complete:
$submitBtn.prop("disabled", false).text(t("contact.submit"));
Leave Chinese comments in that script untouched.
- Step 4: Grep EN demo pages
rg -n "[\p{Han}]" public/themes/website/1/en/demo --glob "*.html"
Triage each hit: comment / SVG id / CMS / must-fix UI.
-
Step 5: Smoke checklist
-
/en/contact.html: form UI English; validation English. -
/en/download.html: placeholders + Download. -
/en/home: Explore more buttons English. -
/en/investment.html: stock section labels English; delay note English after refresh. -
zh contact still Chinese.
-
Step 6: Commit
git add public/themes/website/1/en/demo
git commit -m "feat(i18n): English UI copy for EN demo pages"
Task 6: Final acceptance sweep
Files: none new (verification only)
- Step 1: Shared JS user-string grep
rg -n "[\p{Han}]" public/themes/dist_static/static/js --glob "*.js"
Allowed leftovers: comments only; earth nameZh fields; dictionary zh entries in common.js.
- Step 2: Confirm zh site not broken
Spot-check zh home, about expand, video modal, footer stock note → Chinese.
- Step 3: Confirm EN site
Spot-check EN home, contact, investment, about earth tooltips → English chrome.
- Step 4: Commit only if tiny fixes were needed; otherwise done
git status
# if fixups:
git add -u
git commit -m "fix(i18n): leftover EN demo UI strings"
Spec coverage check
| Spec requirement | Task |
|---|---|
| EN demo hardcoded UI → English | 4, 5 |
| Shared JS bilingual via dictionary | 1, 2 |
| earth city names | 3 |
| Comments not translated | Global + all tasks |
| CMS fields untouched | Global |
html lang=en |
5 |
getUiLang / URL en prefix |
1 |
| Stock delay note i18n | 1 |
| Contact inline alerts | 5 |
| Acceptance grep/smoke | 6 |
Placeholder scan
No TBD/TODO steps; commands and key tables are concrete.