Files
OpenTerminal/website/README.md
T
Bill b89f9c6c95 website: v1.0.25 — 四语言站点 + 更新下载与日志
- 单页 + 语言层:assets/i18n.js(147 键 × zh-CN/zh-TW/en/ja)+
  i18n-apply.js(data-i18n / data-i18n-html / data-i18n-attr 三种挂键方式,
  首帧前定语言,?lang= → localStorage → navigator → zh-CN)
- 导航栏新增语言切换;html[lang]、title、meta description / og:* 同步
- 版本号/下载直链 → v1.0.25;近期更新补 1.0.25 / 1.0.24,裁到 5 条
- style.css 仅追加(语言选择器样式 + 首帧防闪),既有规则零改动
- README 补语言层说明与发版清单
2026-10-10 15:18:50 +08:00

188 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OpenTerminal 官网
OpenTerminal 的产品介绍站点,部署在 GitHub Pages。
纯静态 HTML + CSS + 原生 JS,**无构建步骤、无第三方依赖**,改完即可发布。
支持四语言:简体中文(源码基准)、繁体中文、English、日本語。
## 线上地址
- GitHub Pages: `https://billowliu2.github.io/OpenTerminal/`
## 目录结构
```
website/
├── index.html # 单页站点:功能介绍 / 下载 / 更新日志(文案挂 data-i18n 键)
├── .nojekyll # 让 GitHub Pages 原样托管(跳过 Jekyll 处理)
└── assets/
├── style.css # 全部样式(深色默认 / 浅色可切换,主题令牌在 :root 与 html[data-theme='light'])
├── i18n.js # 四语言文案字典 window.OPENTERMINAL_I18N
├── i18n-apply.js # 语言层:选定语言 → 写 DOM / <html lang> / title / meta,并绑定语言切换控件
├── fonts/ # 自托管 woff2:Space Grotesk 400/500/600/700、JetBrains Mono 400/500/600
├── shot-terminal.jpg # 主界面截图(三路分屏并行 AI 编程 Agent,含背景图个性化)
├── shot-agents.png # 多路 AI Agent 分屏截图(无背景图版)
├── shot-sftp.png # SFTP 文件面板:列头、排序、用户/组、字体缩放
├── shot-sftp-menu.png # 文件列表空白处右键菜单
├── shot-themes.jpg # 主题设置页截图(背景图/不透明度/压暗层)
├── shot-lock.png # 锁屏界面截图
└── icon.png # 应用图标(来自仓库 build/icon.png)
```
## 语言层(i18n)
**单文件 + 语言层**,不是复制四份 HTML:HTML 只有一份(文案是简体中文原文,同时作为缺键时的兜底),
所有可翻译文本通过 data 属性挂键,`assets/i18n.js` 提供四语言字典,`assets/i18n-apply.js` 在运行时写入。
### 三种挂键方式
| 属性 | 行为 | 例子 |
| --- | --- | --- |
| `data-i18n="k"` | 写 `textContent`(纯文本) | `<h2 data-i18n="dl.title">下载</h2>` |
| `data-i18n-html="k"` | 写 `innerHTML`(需要内联标签时) | `<h1 data-i18n-html="hero.title">…<br /><span class="accent">同一个</span>…</h1>` |
| `data-i18n-attr="attr:k"` | 写属性,多组用 `;` 分隔 | `<img data-i18n-attr="alt:hero.shot.alt" />` |
`<title>` 和三个 meta 也走同一套机制(`<title data-i18n="meta.title">`、`<meta … data-i18n-attr="content:meta.description">`),
所以「HTML 里出现的键」和「字典里的键」可以一次性机器比对,不存在漏网的特殊分支。
### 键命名
扁平命名,与 HTML 结构对应:
- `meta.*`、`a11y.skip`、`brand.*`
- `nav.*`(含 `nav.themeToLight` / `nav.themeToDark`,由页尾脚本在主题切换时取用)
- `hero.*`、`spec.*`
- `features.*`、`cap.NN.title` / `cap.NN.li.N`(8 张能力卡)、`cap.hint`
- 四个 feature-row:`fw.*`(Workbench / AI Agent)、`fs.*`(SFTP)、`fc.*`(Customize)、`fl.*`(Security)
- `dl.*`(下载表格与自动更新说明)、`cl.*`(更新日志小节)、`changelog.<版本>.li.N`(每条更新一条键)
- `footer.*`
产品名 `OpenTerminal`、版本号、命令、URL、快捷键(`Ctrl+L` / `Ctrl+F`)、技术名词(SSH / SFTP / WebGL / xterm.js / ZMODEM / MIT…)一律不译。
各节拉丁 eyebrow(`Capabilities` / `Workbench` / `SFTP` / `Customize` / `Security` / `Download` / `Changelog`)是刻意的排版元素,四语言保持一致。
### 加一条文案
1. 在 `index.html` 的对应元素上加 `data-i18n="新键"`,元素里保留简体中文原文(作为兜底)
2. 在 `assets/i18n.js` 的**四个语言块里都加上同名键**——键集合必须完全一致
3. 跑下面的「改完必跑」三条命令
漏加某个语言的键不会显示空白:`i18n-apply.js` 会回落到简体中文。但键集合不一致会让「键一致性」检查失败,属于必须修的。
### 语言选择与首帧防闪
选定语言优先级(`index.html` 头部内联脚本与 `i18n-apply.js` 用的是同一套判定):
1. URL 参数 `?lang=xx`(一次性,不写 localStorage,便于分享指定语言的链接)
2. `localStorage['openterminal.lang']`
3. `navigator.languages` 前缀匹配(`zh-TW`/`zh-HK`/`zh-MO` → 繁中,其余 `zh*` → 简中,`ja*` → 日文,`en*` → 英文)
4. 默认 `zh-CN`
**首帧不闪中文**:HTML 源码是简体中文,若等脚本跑完再翻译,非中文用户会先看到一帧中文。
所以头部内联脚本在设置 `document.documentElement.lang` 的同时,非 `zh-CN` 时给 `<html>` 挂上 `data-i18n-pending`,
`style.css` 末尾的 `html[data-i18n-pending] body { visibility: hidden }` 把 body 先藏住;
页尾的 `i18n-apply.js` 应用完文案后立刻摘掉该属性(`finally` 里摘,异常也不会把页面藏死)。
另有一条 2.5s 兜底定时器(头部脚本里),保证脚本加载失败时页面最终一定可见。
导航栏 `.nav-right` 里的 `<select class="lang-select" id="lang-select">`(在主题按钮左侧)负责切换:
换语言 → 写 `localStorage` → 重写全部文案与 `<html lang>` → 派发 `openterminal:langchange` 事件,
页尾脚本收到事件后重刷主题按钮的 `aria-label` / `title`。全程不刷新页面。
## 发新版本时需要更新的地方
版本号与链接都是**硬编码在 `index.html`**(版本号本身不翻译,不进字典),需要改的地方:
1. `index.html` 中搜索旧版本号,替换为 `v<新版本>` / `<新版本>`:
- hero 主按钮 `id="dl-exe"` 的直链(1 处)
- hero-note 里的 `<span class="mono">`(1 处)
- `spec-strip` 里的 `<strong class="mono">`(1 处)
- 下载表格的 exe 文件名单元格与「下载」链接(2 处)
2. 下载表格里的文件大小(`.mono` 单元格,如 `109 MB`)
3. 「近期更新」小节:新增一条 `<article>`(最新的在最前面,结构保持
`<h3><span class="mono">版本</span><span class="rel-date">日期</span></h3>` + `<ul><li>`),
并删掉最旧的一条以控制长度(当前保留最近 5 条:1.0.25 / 1.0.24 / 1.0.23 / 1.0.22 / 1.0.21)
4. `assets/i18n.js`:给新版本加 `changelog.<新版本>.li.N` 键(**四个语言块都要加**),
并删掉被移除那条的 `changelog.<旧版本>.li.*` 键(同样是四个语言块)
自 v1.0.22 起不再提供 MSI,只有 NSIS exe 一条直链:
```
https://github.com/billowliu2/OpenTerminal/releases/download/v<版本>/OpenTerminal-<版本>-setup.exe
```
注意:发布新版本时需同时把安装包资产上传到 GitHub release
(`HTTPS_PROXY=http://127.0.0.1:7897 node scripts/release.cjs <版本> --skip-gitea`),
否则直链 404。
### 改完必跑
```bash
# 1) 语法(在仓库根执行)
node --check website/assets/i18n.js
node --check website/assets/i18n-apply.js
# 2) 四语言键集合一致性(缺失 / 多余 / 同一语言内重复键)
node -e '
const fs=require("fs"); global.window={};
require("./website/assets/i18n.js");
const D=window.OPENTERMINAL_I18N, langs=Object.keys(D), base=new Set(Object.keys(D[langs[0]]));
let bad=false;
for (const l of langs) console.log(l, Object.keys(D[l]).length);
for (const l of langs.slice(1)) {
const s=new Set(Object.keys(D[l]));
const miss=[...base].filter(k=>!s.has(k)), extra=[...s].filter(k=>!base.has(k));
if (miss.length||extra.length) { bad=true; console.log(l, "missing:", miss, "extra:", extra); }
}
console.log("键集合一致:", !bad);'
# 3) index.html 引用的键在字典里都存在(防漏键显示空白)
node -e '
const fs=require("fs"); global.window={};
require("./website/assets/i18n.js");
const D=window.OPENTERMINAL_I18N, langs=Object.keys(D);
const html=fs.readFileSync("website/index.html","utf8");
const keys=new Set();
for (const m of html.matchAll(/data-i18n(?:-html)?="([^"]+)"/g)) keys.add(m[1]);
for (const m of html.matchAll(/data-i18n-attr="([^"]+)"/g))
m[1].split(";").forEach(p=>{const i=p.indexOf(":"); if(i>0) keys.add(p.slice(i+1).trim());});
["nav.themeToLight","nav.themeToDark"].forEach(k=>keys.add(k));
const dict=new Set(); langs.forEach(l=>Object.keys(D[l]).forEach(k=>dict.add(k)));
const missing=[...keys].filter(k=>!dict.has(k));
const unused=[...dict].filter(k=>!keys.has(k));
console.log("HTML 引用键:", keys.size, "字典键:", dict.size);
console.log("缺失:", missing.length?missing:"无", "| 未引用:", unused.length?unused:"无");'
```
## 部署方式
GitHub Actions 工作流 `.github/workflows/deploy-website.yml`:
push 到 `main` 且 `website/` 下有改动时,自动把 `website/` 目录发布到 GitHub Pages。
Gitea(git.codingplan.site)只同步代码,Pages 由 GitHub 侧托管。
首次部署需要在 GitHub 仓库设置里确认:Settings → Pages → Source 选择
"GitHub Actions"(工作流已声明 `pages: write` 与 `id-token: write` 权限)。
## 本地预览
无构建步骤,任选其一:
```bash
# Python
python -m http.server 8080 --directory website
# Node
npx serve website
```
然后访问 `http://localhost:8080`。直接双击 `index.html` 也可以(无跨域资源);
想看指定语言加参数即可,例如 `index.html?lang=ja`。
## 设计约定
- **深色为主**:默认深色(`#0b0e12` 底 + 终端绿强调色),右上角按钮可切浅色;两套主题共用同一份令牌,新增样式一律走 CSS 变量,不要写死颜色
- 主题选择存 `localStorage['openterminal.theme']`,语言选择存 `localStorage['openterminal.lang']`,两者都在首帧前由 `index.html` 头部的内联脚本定好,避免闪白/闪黑与闪中文
- 新增样式**只追加在 `style.css` 末尾**,不重排已有规则;语言切换控件(`.lang-select`)复用既有令牌,不改 `.theme-toggle` 的规则
- 全站只用**一个强调色**:深底 `#3fb950`、浅底 `#1a7f37`(对比度 ≥ 4.5:1)
- 字体自托管(`assets/fonts/`),不依赖 Google Fonts:展示字体 Space Grotesk、等宽 JetBrains Mono;中文回落到系统栈
- 「核心能力」是横向滚动卡片轨道(箭头翻页 + 拖拽 + 键盘方向键),不再用三栏等高卡片
- 截图来自真实运行界面,与应用内设置一致;涉及 IP / 主机名等敏感信息的截图必须打码后再放入 `assets/`
- 文案保持具体、平实,避免营销套话;四语言都按各语言习惯写,不做逐字直译