docs(justsolutionsWebV2): 多地区设计回填國家選項与 global 站,Q1/Q3 标注修订

- §02 新增「地区切换:页头/页尾的國家選項」小节 + Plans 按 region.subscription 分流
  - §03 目录树加 global/,§07 币种表加「其他國家 · 无在线支付」
  - §08 config 补 subscription / regions / markets,§09 加第 8 步与生成器执行顺序
  - §12 Q1(兜底落 /global/en)、Q3(jp+hk+global)标为 2026-07-27 修订,
    原决策用删除线保留追溯;§A 组件表补国家切换器
main
fengruixiang 2026-07-27 14:56:03 +08:00
parent 1a5fb4b32d
commit 13ae4d50df
1 changed files with 44 additions and 9 deletions

View File

@ -297,6 +297,7 @@
.pill { font-family: var(--font-mono); font-size: 12px; padding: 2px 8px; border-radius: 6px; font-weight: 600; white-space: nowrap; }
.pill.jp { color: var(--jp); background: var(--jp-soft); }
.pill.hk { color: var(--hk); background: var(--hk-soft); }
.pill.gl { color: var(--ink-3); background: var(--line-soft); }
/* ---------- code / tree ---------- */
.code {
@ -349,6 +350,7 @@
.q.pend .qn { color: var(--warn); background: var(--warn-soft); }
.q .ans { display: block; margin-top: 7px; font-size: 13.5px; font-weight: 600; color: var(--accent-strong); }
.q .ans.pend { color: var(--warn); }
.q .ans s { color: var(--ink-3); font-weight: 500; } /* 被推翻的旧决策:保留可追溯,但压低视觉权重 */
.q .qb strong { display: block; margin-bottom: 3px; }
.q .qb { font-size: 14.5px; color: var(--ink-2); }
.q .qb .opts { font-family: var(--font-mono); font-size: 12.5px; color: var(--ink-3); margin-top: 4px; }
@ -375,7 +377,7 @@
<span class="tag">路由 <b>/{region}/{lang}</b></span>
<span class="tag">站点 <b>Express + 静态托管</b></span>
<span class="tag">现有 <b>geoip · translations.js · /api/*</b></span>
<span class="tag">状态 <b>Q15 已定 · Q6/7 待定</b></span>
<span class="tag">状态 <b>Q15 已定Q1·Q3 2026-07-27 修订)· Q6/7 待定</b></span>
</div>
</header>
@ -457,7 +459,8 @@
<div class="route"><span class="u"><span class="seg">/hk</span></span><span class="d">302 → <code>/hk/zh</code></span></div>
<div class="route"><span class="u"><span class="seg">/hk</span>/zh/plans</span><span class="d"><span class="pill hk">香港</span> 繁中 · Plans 页</span></div>
<div class="route"><span class="u"><span class="seg">/hk</span>/en/plans</span><span class="d"><span class="pill hk">香港</span> 英文 · Plans 页</span></div>
<div class="route"><span class="u">/</span><span class="d">302 → geoip 决定的 <code>/{region}/{lang}</code>(认不出 → 兜底 <code>/hk/en</code></span></div>
<div class="route"><span class="u"><span class="seg">/global</span>/en/plans</span><span class="d"><span class="pill gl">其他國家</span> 英文 · Plans 页(营销版,无在线订阅)</span></div>
<div class="route"><span class="u">/</span><span class="d">302 → geoip 决定的 <code>/{region}/{lang}</code>(认不出 → 兜底 <code>/global/en</code></span></div>
</div>
</div>
<div class="note">语言代码<b>采用 BCP 47</b>(已定 · Q2<code>ja</code> / <code>zh-HK</code> / <code>en</code>。旧 <code>jp</code>/<code>tc</code> 迁移期做别名映射即可不断链。</div>
@ -467,9 +470,10 @@
<table>
<thead><tr><th>请求</th><th>处理</th><th>说明</th></tr></thead>
<tbody>
<tr><td class="mono"><code>/</code></td><td>302 → geoip 命中的 <code>/{region}/{lang}</code></td><td>无法识别 → 回落 <code>/hk/en</code>Q1</td></tr>
<tr><td class="mono"><code>/</code></td><td>302 → geoip 命中的 <code>/{region}/{lang}</code></td><td>无法识别 → 回落 <code>/global/en</code>Q1 · 2026-07-27 修订</td></tr>
<tr><td class="mono"><code>/{region}</code></td><td>302 → <code>/{region}/{defaultLang}</code></td><td><code>/jp</code><code>/jp/ja</code></td></tr>
<tr><td class="mono"><code>/{region}/{lang}/…</code></td><td><code>public/{region}/</code> 提供静态内容</td><td>命中具体页面</td></tr>
<tr><td class="mono"><code>/{region}/{lang}/plans</code></td><td>该地区<b></b>在线订阅 → 内部改写成 <code>plans-plus</code><b>没有</b> → 营销页 <code>plans</code></td><td>依据 <code>region.subscription</code>(非 geoip 国家);地址栏不变</td></tr>
<tr><td class="mono"><code>/_shared/…</code></td><td>提供跨地区共享资源</td><td>css / js / img 公共部分</td></tr>
<tr><td class="mono"><code>/api/…</code></td><td>共享 BFF带地区上下文</td><td>见 §06</td></tr>
<tr><td class="mono"><code>/config.js</code></td><td>动态注入运行时配置</td><td>no-store见 §08</td></tr>
@ -478,6 +482,25 @@
</table>
</div>
<h3>地区切换:页头 / 页尾的「國家選項」<span class="pill gl">2026-07-27 增补</span></h3>
<p>geoip 只是<b>落地猜测</b>,访客必须能自己改。因此在页头(语言按钮左侧)与页尾各放一个国家选项,与语言切换器同构:<b>选择进 URL</b>,由服务端按路径重新渲染,可缓存、可分享、利于 hreflang。</p>
<div class="table-scroll">
<table>
<thead><tr><th>站点</th><th>URL</th><th>语言</th><th>在线订阅</th></tr></thead>
<tbody>
<tr><td><span class="pill hk">香港</span></td><td class="mono">/hk/{zh-HK,en}/</td><td>繁中 · 英文</td><td>有(<code>plans-plus</code>,市场 HKG</td></tr>
<tr><td><span class="pill jp">日本</span></td><td class="mono">/jp/{ja,en}/</td><td>日文 · 英文</td><td>有(<code>plans-plus</code>,市场 JPN</td></tr>
<tr><td><span class="pill gl">其他國家</span></td><td class="mono">/global/en/</td><td>仅英文</td><td>无 —— Plans 给英文营销页</td></tr>
</tbody>
</table>
</div>
<ul>
<li><strong>清单来自注册表</strong><code>/config.js</code> 下发 <code>regions</code><code>regionList()</code>),切换器据此渲染;站点名走 i18n 键 <code>region_{code}</code>(缺键回落注册表 <code>name</code>)。<b>新增地区仍只改 <code>regions.js</code> 一处</b>,页面与切换器代码不动。</li>
<li><strong>换站映射</strong>:语言沿用当前,目标站不支持则用其默认语言(<code>/jp/ja/x</code> → 香港站取 <code>zh-HK</code>);页面同名平移,目标站没有的页面回落(<code>plans-plus</code><code>plans</code><code>pay-return</code> → 首页);不带查询串 / hash —— 换国家是换上下文,另一个站读不懂本站参数。</li>
<li><strong>挂载方式</strong>:由 <code>_shared/js/main.js</code> 在地区页运行时注入到 <code>.nav-inner</code><code>footer .footer-inner</code>,不逐页改 HTML<code>plans-plus</code> 这类不再由生成器管理的页面同样自动获得)。</li>
<li><strong>选择不持久化</strong>:状态只在 URL 里,裸域访问仍走 geoip。如需「记住上次选的国家」另加 cookie 层。</li>
</ul>
<h3>为什么「地区在前、语言显式成段」</h3>
<div class="table-scroll">
<table>
@ -520,6 +543,10 @@
│ ├── zh-HK.js <span class="c"># 繁中文案</span>
│ └── en.js
├── <span class="d">global/</span><span class="c"> # ── 其他國家(纯英文,无在线订阅)──</span>
│ ├── pages/ <span class="c"># 不含 plans-plus / pay-return</span>
│ └── i18n/en.js
└── <span class="c">(新增地区照抄一个目录即可,零特判)</span></pre>
</div>
<ul>
@ -593,6 +620,7 @@
<tbody>
<tr><td><span class="pill jp">日本 JP</span></td><td class="mono">JPY</td><td>PayPay · JCB/信用卡 · Konbini 便利店</td></tr>
<tr><td><span class="pill hk">香港 HK</span></td><td class="mono">HKD</td><td>FPS 转数快 · AlipayHK · 信用卡Stripe</td></tr>
<tr><td><span class="pill gl">其他國家</span></td><td class="mono"></td><td>无在线支付 —— 只走线下渠道(试用申请 / 联络我们)</td></tr>
<tr><td>其它</td><td class="mono"></td><td>按落地地区补充</td></tr>
</tbody>
</table>
@ -634,12 +662,16 @@
<span class="k">availableLangs</span>: [<span class="s">"zh-HK"</span>, <span class="s">"en"</span>], <span class="c">// 语言切换器用</span>
<span class="k">currency</span>: <span class="s">"HKD"</span>, <span class="c">// 该地区币种§07</span>
<span class="k">paymentMethods</span>: [<span class="s">"fps"</span>, <span class="s">"alipay_hk"</span>, <span class="s">"card"</span>], <span class="c">// 该地区可用支付方式§07</span>
<span class="k">subscription</span>: <span class="s">true</span>, <span class="c">// 该地区是否有在线订阅(决定 Plans 入口给哪张页)</span>
<span class="k">regions</span>: [{ code, name, defaultLang, availableLangs, subscription }, …], <span class="c">// 全部站点国家选项用§02</span>
<span class="k">markets</span>: { HKG: {…}, JPN: {…} }, <span class="c">// 市场码 → 站点,跨站续费/加购用</span>
<span class="k">geo</span>: { country, lang, … } <span class="c">// 现有 geoip 结果(裸域落地判断)</span>
}</pre>
</div>
<ul>
<li><code>/config.js</code> 保持 <code>no-store</code>,按请求路径注入正确的 <code>region/lang</code></li>
<li>语言切换器读 <code>availableLangs</code> 渲染,切换即跳到 <code>/{region}/{targetLang}/{samePage}</code></li>
<li>国家选项读 <code>regions</code> 渲染,切换即跳到 <code>/{targetRegion}/{lang}/{samePage}</code>(见 §02</li>
</ul>
</section>
@ -655,7 +687,9 @@
<li><strong>克隆日本地区</strong><code>public/jp/</code>,落地日文排版与 <code>ja.js</code>/<code>en.js</code></li>
<li><strong>BFF 引入地区上下文</strong><code>X-Region</code> 中间件 + 让 <code>countries/industries/plans</code> 等按地区返回。</li>
<li><strong>收尾</strong>:旧扁平 URL<code>/plans</code>301 到默认地区,保留一段时间兼容外链。</li>
<li><strong>补建 global 站 + 国家选项</strong>2026-07-27Q1/Q3 修订):<code>regions.js</code><code>global</code> 一行 → 重跑 <code>build-regions.js</code>(生成 <code>public/global/pages</code>+ <code>split-i18n.js</code>(生成 <code>global/i18n/en.js</code>);裸域兜底改 <code>global</code><code>main.js</code> 注入页头/页尾国家选项。</li>
</ol>
<div class="note warn"><b>改动这一层时的顺序固定为:</b><code>node scripts/build-regions.js</code><code>node scripts/split-i18n.js</code>(后者要读前者产出的地区页来收 <code>data-i18n</code> 键)。两者都幂等。</div>
</section>
<!-- 10 -->
@ -690,11 +724,11 @@
<!-- 12 -->
<section id="s12">
<div class="sec-head"><span class="sec-num">§12</span><h2>决策记录Q1Q7</h2></div>
<p>Q1Q5 已拍板并回填到上文相关章节Q6Q7 待后续确认。</p>
<p>Q1Q5 已拍板并回填到上文相关章节Q6Q7 待后续确认。<b>Q1 与 Q3 已于 2026-07-27 因「页头/页尾国家选项」需求修订</b>——原决策保留在下方以便追溯。</p>
<div class="card">
<div class="q done"><span class="qn">Q1</span><div class="qb"><strong>裸域 / 的兜底地区</strong>geoip 认不出来(本地/内网/未覆盖国家)时落到哪?<span class="ans"><b>HK</b>;语言按 geoip 语种,认不出 → <code>en</code>,即最终落 <code>/hk/en</code></span></div></div>
<div class="q done"><span class="qn">Q1</span><div class="qb"><strong>裸域 / 的兜底地区</strong>geoip 认不出来(本地/内网/未覆盖国家)时落到哪?<span class="ans"><b>2026-07-27 修订:落 <code>/global/en</code></b>(其他國家站)—— 与页头国家选项显示的站点一致Plans 自然是英文营销版。<br><s>原:落 HK语言按 geoip认不出 → <code>/hk/en</code></s></span></div></div>
<div class="q done"><span class="qn">Q2</span><div class="qb"><strong>语言码规范</strong>是否规范化为 BCP47<span class="ans"><b>采用 BCP47</b><code>ja</code> / <code>zh-HK</code> / <code>en</code>;旧 <code>jp</code>/<code>tc</code> 迁移期做别名兼容</span></div></div>
<div class="q done"><span class="qn">Q3</span><div class="qb"><strong>首批上线地区</strong><span class="ans"><b>仅 jp + hk</b>;不单建 global 基线地区</span></div></div>
<div class="q done"><span class="qn">Q3</span><div class="qb"><strong>首批上线地区</strong><span class="ans"><b>2026-07-27 修订jp + hk + global</b> —— 有了国家选项就必须有「其他國家」这个可选项,故补建 global 基线站(纯英文、无在线订阅,页面同样以 HK 为基线克隆)。<br><s>原:仅 jp + hk不单建 global 基线地区</s></span></div></div>
<div class="q done"><span class="qn">Q4</span><div class="qb"><strong>地区差异化起点</strong><span class="ans"><b>以现有 HK 页面为基线复制</b>,再逐地区改排版</span></div></div>
<div class="q done"><span class="qn">Q5</span><div class="qb"><strong>是否用独立域名</strong><span class="ans"><b>不用独立域名</b>纯路径前缀region 中间件无需预留「域名→地区」解析口</span></div></div>
<div class="q pend"><span class="qn">Q6</span><div class="qb"><strong>BFF 地区差异范围</strong>哪些接口/数据真的按地区不同(币种/Plans/合规文案/上游租户/支付方式)?据此确定 <code>X-Region</code> 影响哪些 service。<span class="ans pend">⏳ 待定 —— 实现时按接口逐一确认</span></div></div>
@ -709,15 +743,16 @@
<table>
<thead><tr><th>组件</th><th>位置(建议)</th><th>职责</th></tr></thead>
<tbody>
<tr><td>地区注册表</td><td class="mono">server/regions.js</td><td>region → { defaultLang, availableLangs, currency, paymentMethods, … }</td></tr>
<tr><td>地区注册表</td><td class="mono">server/regions.js</td><td>region → { defaultLang, availableLangs, currency, market, paymentMethods, subscription };键序即国家选项的展示顺序</td></tr>
<tr><td>region 解析中间件</td><td class="mono">server/index.js</td><td>解析 /{region}/{lang}、裸地区/裸域重定向、按地区托管静态</td></tr>
<tr><td>geoip 落地</td><td class="mono">server/geoip.js复用</td><td>裸域 / 决定落地 region/lang认不出 → hk/en</td></tr>
<tr><td>geoip 落地</td><td class="mono">server/geoip.js复用</td><td>裸域 / 决定落地 region/lang认不出 → global/en</td></tr>
<tr><td>国家选项(切换器)</td><td class="mono">public/_shared/js/main.js</td><td>页头 + 页尾运行时注入,读 <code>__APP_CONFIG__.regions</code>,换站做语言/页面回落</td></tr>
<tr><td>BFF 地区上下文中间件</td><td class="mono">server/index.js</td><td>读 X-Region → req.region</td></tr>
<tr><td>支付门面路由</td><td class="mono">server/routes/payments.js</td><td>统一 /api/payments/*,按 region + method 选适配器</td></tr>
<tr><td>支付适配器</td><td class="mono">server/services/payments/*.js</td><td>一网关一适配器createPayment · verifyWebhook · getStatus</td></tr>
<tr><td>i18n 引擎</td><td class="mono">public/_shared/js/i18n.js</td><td>加载共享 + 地区字典,渲染 data-i18n</td></tr>
<tr><td>BFF 客户端</td><td class="mono">public/_shared/js/api.js</td><td>/api/* 请求自动带 X-Region / X-Lang</td></tr>
<tr><td>配置注入</td><td class="mono">/config.jsserver/index.js</td><td>注入 region / lang / availableLangs / currency / paymentMethods</td></tr>
<tr><td>配置注入</td><td class="mono">/config.jsserver/index.js</td><td>注入 region / lang / availableLangs / currency / paymentMethods / subscription / regions / markets</td></tr>
</tbody>
</table>
</div>