Browse Source
docs: add EN demo UI i18n implementation plan
docs: add EN demo UI i18n implementation plan
Co-authored-by: Cursor <cursoragent@cursor.com>master
1 changed files with 504 additions and 0 deletions
@ -0,0 +1,504 @@ |
|||||
|
# 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`, else `zh` (same as header EN/CN mapping). |
||||
|
- Missing `t` key: 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>): string` |
||||
|
- `window.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): |
||||
|
|
||||
|
```javascript |
||||
|
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`: |
||||
|
|
||||
|
```javascript |
||||
|
timeEl.textContent = `截止 ${data.updateTime}*报价有十五分钟或以上延迟。`; |
||||
|
``` |
||||
|
|
||||
|
Replace with: |
||||
|
|
||||
|
```javascript |
||||
|
timeEl.textContent = t("stock.delay_note", { time: data.updateTime }); |
||||
|
``` |
||||
|
|
||||
|
- [ ] **Step 3: Verify helper in browser** |
||||
|
|
||||
|
Open `/en/` any page with DevTools: |
||||
|
|
||||
|
```javascript |
||||
|
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** |
||||
|
|
||||
|
```bash |
||||
|
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 via `import "./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: |
||||
|
|
||||
|
```javascript |
||||
|
card.getAttribute("aria-label")?.replace(/^(播放|Play)\s*[::]\s*/i, "").trim() || t("video.play") |
||||
|
``` |
||||
|
|
||||
|
- [ ] **Step 3: Update `about.js`** |
||||
|
|
||||
|
```javascript |
||||
|
btn.querySelector("span").textContent = expanded ? t("about.collapse") : t("about.expand_more"); |
||||
|
``` |
||||
|
|
||||
|
- [ ] **Step 4: Update `announce.js`** |
||||
|
|
||||
|
```javascript |
||||
|
$btn.find("span").text(t("filter.by_product")); |
||||
|
``` |
||||
|
|
||||
|
- [ ] **Step 5: Update `category.js`** |
||||
|
|
||||
|
```javascript |
||||
|
if (name) document.title = `${name} - ${t("brand.short")}`; |
||||
|
``` |
||||
|
|
||||
|
- [ ] **Step 6: Smoke** |
||||
|
|
||||
|
- `/en/video.html` (or routed EN video): default modal title English when empty. |
||||
|
- `/about` zh: toggle still 展开更多/收起. |
||||
|
- `/en/about`: Show more / Show less. |
||||
|
|
||||
|
- [ ] **Step 7: Commit** |
||||
|
|
||||
|
```bash |
||||
|
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 `name` matches UI lang |
||||
|
|
||||
|
- [ ] **Step 1: Change `markersData` entries to bilingual names** |
||||
|
|
||||
|
Pattern for each city: |
||||
|
|
||||
|
```javascript |
||||
|
{ |
||||
|
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: |
||||
|
|
||||
|
```javascript |
||||
|
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: |
||||
|
|
||||
|
```javascript |
||||
|
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** |
||||
|
|
||||
|
```bash |
||||
|
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 accounts` |
||||
|
- `aria-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`: `RAYNEN` or `RAYNEN 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** |
||||
|
|
||||
|
```bash |
||||
|
rg -n "[\p{Han}]" public/themes/website/1/en/demo/components --glob "*.html" |
||||
|
``` |
||||
|
|
||||
|
Ignore: comments, SVG ids, PHP comments, CMS echoes. |
||||
|
|
||||
|
- [ ] **Step 3: Commit** |
||||
|
|
||||
|
```bash |
||||
|
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/*.html` that still have `lang="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 `html` lang** |
||||
|
|
||||
|
Replace on every EN demo page: |
||||
|
|
||||
|
```html |
||||
|
<html lang="zh-CN"> |
||||
|
``` |
||||
|
|
||||
|
→ |
||||
|
|
||||
|
```html |
||||
|
<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.html` inline script strings via `window.t`** |
||||
|
|
||||
|
```javascript |
||||
|
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** |
||||
|
|
||||
|
```bash |
||||
|
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** |
||||
|
|
||||
|
```bash |
||||
|
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** |
||||
|
|
||||
|
```bash |
||||
|
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** |
||||
|
|
||||
|
```bash |
||||
|
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. |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue