feat(site): 多语言架构 + 繁体(zh-Hant)站点

把站点从写死 zh/en 重构为语言列表 LANGS 驱动,新增语言只需加一项
+ 写一个 T.<code> 对象即可 drop-in。首个新增语言:繁体中文。

- LANGS 配置化:首页生成、skill 详情页、语言切换器、hreflang、
  sitemap 全部按 LANGS 循环
- 语言切换器从二选一(中文↔EN)改为多语言链接
- render 分支由 `=== 'zh'` 改为 `=== 'en'`,非英文语言默认走中文内容
  (繁体 skill 卡片/正文用中文,仅外壳翻译,与 en 站策略一致)
- T.zht:手写繁体(台港自然用词:軟體/程式碼/專案/預設/除錯/計畫/
  全域/使用者/檔案 等;处理 發/後/裡/雲/麵 等一简对多繁),不引入
  OpenCC 依赖
- 收尾日志按语言数动态输出

日文暂缓:公开营销文案需母语审校,避免机翻上线砸品牌。

测试:构建 63 页(3 语言 × (首页+20 skill));en/zh 站零回归;
繁体 htmlLang=zh-Hant、skill 卡片用中文标题、语言切换/hreflang/
sitemap 三语言齐全。
This commit is contained in:
AI不止语
2026-07-13 22:23:07 +08:00
parent 01319b5b02
commit d7dd3d2459

View File

@@ -274,6 +274,90 @@ const T = {
{ q: 'How do I update or uninstall?', a: 'Update: re-run npx superpowers-zh to overwrite. Uninstall: npx superpowers-zh --uninstall for the current project; npx superpowers-zh --global --uninstall for a global install.' },
],
},
zht: {
htmlLang: 'zh-Hant',
title: 'superpowers-zh · AI 編程超能力中文增強版',
desc: 'superpowers250k+ ⭐)完整漢化 + 4 個中國原創 skills一條 npx 命令為 20 款 AI 編程工具裝上系統化工作方法論。',
nav: { why: '特性', install: '安裝', skills: 'Skills', tools: '支援工具', faq: 'FAQ', learn: '學習 ↗', github: 'GitHub ↗' },
heroBadge: 'superpowers 250k+ ⭐ · 完整漢化 + 中國原創',
heroH1: '給你的 AI 編程工具<br>裝上<span class="grad">真正會幹活</span>的超能力',
heroLead: '{n} 個經過實戰驗證的工作方法論 skill —— 從頭腦風暴到 TDD從系統化除錯到程式碼審查。<br>一條命令,自動識別專案裡的工具並安裝。',
heroBtn1: '查看安裝命令', heroBtn2: 'GitHub 原始碼',
stats: ['Skills', '中國原創', '支援工具', '目前版本'],
whyTitle: '為什麼選擇 superpowers-zh',
whySub: '不是又一套提示詞範本 —— 是讓 AI 真正按工程方法幹活的系統化能力。',
plTitle: '一條龍工作流,每一步都有 skill 把關',
plSub: 'skill 之間彼此銜接AI 會在合適的節點自動觸發對應方法論。',
cmpTitle: '裝上之後AI 不再「上來就寫」',
cmpBad: '❌ 沒裝', cmpGood: '✅ 裝了 superpowers-zh',
cmpBadPre: '你:給使用者模組加個批次匯出功能\nAI好的我來實作……直接開寫\n export async function exportUsers() { … }\n你等等格式不對沒分頁\n 大量資料會 OOM……',
cmpGoodPre: '你:給使用者模組加個批次匯出功能\nAI先理清需求 —— 匯出格式?資料量級?\n 要分頁/串流嗎?要權限校驗嗎?\n (觸發 brainstorming\n → 寫計畫 → TDD → 驗證 → 審查',
instTitle: '選你的工具,拿到安裝命令',
instSub: '大多數工具 <code>npx superpowers-zh</code> 會自動識別專案目錄並安裝;識別不出的用 <code>--tool</code> 指定。',
instLabel: '我用的是',
instNoteAuto: '在你的專案根目錄執行,<b>自動識別 {name}</b> 並安裝。安裝後重啟工具即可生效。',
instNoteManual: '{name} 無法自動識別,需用 <code>--tool</code> 顯式指定。在專案根目錄執行,安裝後重啟工具即可生效。',
skTitle: '{n} 個 Skill覆蓋開發全流程',
skSub: '點擊任意卡片查看完整操作文件。',
skSearch: '搜尋 skill…如 除錯 / review / TDD',
skEmpty: '沒有符合的 skill。',
skDetail: '查看文件 →',
tagCn: '中國原創',
ucTitle: '典型使用場景', ucSub: '每個場景背後都是一組協同工作的 skill。',
toolsTitle: '一套 skill20 款工具通用', toolsSub: '換工具不用換習慣,方法論跟著你走。',
faqTitle: '常見問題',
bookTitle: '裝好之後,配上方法論效率翻倍',
bookDesc: '《AI 編程實戰 · 方法論三卷書》—— 10 個 AI 編程工具完整教程 + 真實踩坑。線上書 + PDF永久免費。',
bookBtn: '免費閱讀 ↗',
aiolaolaBtn: '免費學 AI 編程 · aiOlaOla ↗',
ctaTitle: '準備好讓 AI 真正會幹活了嗎?',
ctaDesc: '一條命令,{n} 個實戰方法論裝進你的工具。免費、開源、零依賴。',
ctaBtn1: '查看安裝命令', ctaBtn2: '⭐ Star on GitHub',
footCols: [
{ h: '產品', links: [['特性', '#why'], ['Skills', '#skills'], ['支援工具', '#tools'], ['FAQ', '#faq']] },
{ h: '資源', links: [['GitHub', 'https://github.com/jnMetaCode/superpowers-zh'], ['npm', 'https://www.npmjs.com/package/superpowers-zh'], ['方法論三卷書', 'https://book.aibuzhiyu.com/']] },
{ h: '生態', links: [['aiOlaOla · 從零學會 AI 編程', 'https://aiolaola.com/?utm_source=sp1'], ['X / Twitter', 'https://x.com/jnMetaCode'], ['公眾號 AI不止語', 'https://aiolaola.com/'], ['姊妹專案', 'https://github.com/jnMetaCode']] },
{ h: '社群', links: [['提交 Issue', 'https://github.com/jnMetaCode/superpowers-zh/issues'], ['貢獻指南', 'https://github.com/jnMetaCode/superpowers-zh/blob/main/CLAUDE.md'], ['聯絡信箱', 'mailto:jnMetaCode@qq.com']] },
],
footTag: 'AI 編程超能力 · 中文增強版 · MIT License',
copyright: '© 2026 superpowers-zh · MIT License',
followUs: '掃碼關注', qrWechat: '公眾號 · AI不止語', qrDouyin: '抖音 · @AI不止語AIBZY',
copy: '複製', copied: '已複製 ✓',
backToSkills: '← 返回全部 Skill',
detailInstall: '安裝此 skill',
detailSource: '在 GitHub 查看原始檔 ↗',
features: [
{ icon: '🧠', t: '20 個實戰方法論', d: '不是 prompt 範本,是經過跨會話對抗式壓力測試調優的工作方法論 —— 從頭腦風暴到 TDD、除錯、程式碼審查。' },
{ icon: '🔌', t: '20 款工具通用', d: '一套 skillClaude Code / Cursor / Codex / Gemini CLI / Windsurf… 全適配,換工具不用換習慣。' },
{ icon: '⚡', t: '一條命令安裝', d: 'npx superpowers-zh 自動識別專案裡用的是哪款工具並安裝,零設定,裝完重啟即生效。' },
{ icon: '🇨🇳', t: '中國原創 Skills', d: '中文程式碼審查話術、中文提交規範、中文文件排版、國內 Git 平台Gitee/Coding/極狐)設定 —— 上游沒有。' },
{ icon: '📖', t: '完整漢化上游', d: '同步 obra/superpowers250k+ ⭐),核心 skill 全部中文母語化,不是機翻,是逐條校準。' },
{ icon: '🔓', t: '零依賴 · MIT 開源', d: '純 Markdown skill不引入任何外部依賴、不連網、不上傳程式碼按需觸發零執行時開銷。' },
],
pipeline: [
{ n: '頭腦風暴', d: '動手前先理清意圖與需求' },
{ n: '寫計畫', d: '把需求拆成可執行步驟' },
{ n: 'TDD', d: '先寫測試再寫實作' },
{ n: '系統化除錯', d: '先重現定位再改' },
{ n: '程式碼審查', d: '合併前嚴謹驗收' },
{ n: '完成前驗證', d: '用證據證明真的好了' },
],
usecases: [
{ tag: '開發新功能', skills: 'brainstorming → writing-plans → TDD', desc: 'AI 先反問需求、寫出實作計畫,再用測試驅動落地,而不是上來就糊一坨程式碼。' },
{ tag: '修 Bug', skills: 'systematic-debugging', desc: '強制先重現、定位根因,再提修復方案 —— 杜絕「猜一個改一下」的瞎試迴圈。' },
{ tag: '提交 / 合併前', skills: 'verification-before-completion → code-review', desc: '必須跑驗證命令、拿證據說話,再走一輪程式碼審查,才允許聲稱「完成」。' },
{ tag: '國內團隊協作', skills: 'chinese-commit-conventions → chinese-code-review', desc: '中文 commit 規範 + 分級 review 話術,配 Gitee / Coding / 極狐 GitLab 工作流。' },
],
faq: [
{ q: 'superpowers-zh 是免費的嗎?', a: '完全免費。MIT 協議開源,永久免費,不含任何付費牆或訂閱。' },
{ q: '支援哪些 AI 編程工具?', a: '共 20 款Claude Code、Cursor、Windsurf、Codex CLI、Gemini CLI、Kiro、Trae、Qoder、CodeBuddy騰訊、CodeArts華為雲碼道、Aider、OpenCode、Qwen Code、Antigravity、DeerFlow、VS Code(Copilot)、Copilot CLI、Hermes Agent、Claw Code、OpenClaw。' },
{ q: 'superpowers-zh 有哪些獨特價值?', a: '一套完整中文化的系統工作方法論從頭腦風暴、規劃、TDD 到除錯、程式碼審查,每個 skill 都是實戰驗證的工作流;並疊加 4 個面向中國開發者的原創 skill中文程式碼審查 / Git 工作流 / 文件規範 / 提交規範),適配 20 款 AI 編程工具。MIT 協議開源,永久免費。' },
{ q: '安裝後怎麼生效?', a: 'npx 會把 skill 檔案裝到你專案對應工具的目錄(如 .claude/skills/),重啟 AI 工具後,它會在恰當時機自動觸發相應 skill —— 無需你每次手動呼叫。' },
{ q: '能一次裝好、所有專案都用嗎?(全域安裝)', a: '能。npx superpowers-zh --global 裝到工具的使用者級目錄(如 ~/.claude/skills所有專案自動共享更新時只需重裝一次。專案級優先、全域兜底二者可共存。支援全域的工具均為各工具文件確認的載入路徑Claude Code / Codex CLI / Qoder / Windsurf / Qwen Code / OpenClaw / OpenCode其餘工具含 Gemini / Antigravity有各自專屬全域方式請在專案內安裝或參考對應文件。' },
{ q: '會拖慢我的 AI 嗎?會上傳程式碼嗎?', a: '不會。skill 是按需觸發的純 Markdown零執行時、不連網、不上傳任何程式碼或資料全程在本機。' },
{ q: '怎麼更新或解除安裝?', a: '更新:重新執行 npx superpowers-zh 覆蓋即可。解除安裝npx superpowers-zh --uninstall 清理目前專案;全域安裝用 npx superpowers-zh --global --uninstall 清理。' },
],
},
};
// ---- 读取 skills含正文 ----
@@ -305,11 +389,30 @@ function loadSkills() {
return skills;
}
// ---- 多语言配置:新增语言只需往这里加一项 + 写一个 T.<code> 翻译对象 ----
// dir: 该语言站点子目录(根语言为 ''hreflang: SEO 语言标记label: 语言切换器显示名
const LANGS = [
{ code: 'zh', dir: '', label: '中文', hreflang: 'zh-CN' },
{ code: 'en', dir: 'en/', label: 'EN', hreflang: 'en' },
{ code: 'zht', dir: 'zh-Hant/', label: '繁體', hreflang: 'zh-Hant' },
];
const DEFAULT_DIR = LANGS[0].dir; // 根语言zh用于 x-default
// ---- 公共布局 ----
// base: 资源相对前缀('' / '../' / '../../'langHref: 切换语言的目标 URL
function layout({ lang, base, title, desc, body, langHref, canonical = '/', altZh = '/', altEn = '/en/', extraHead = '' }) {
// pageClean: 语言无关的 canonical 后缀(首页 ''skill 页 'skills/<name>'
// pageFile: 语言无关的文件后缀,用于语言切换链接(首页 ''skill 页 'skills/<name>.html'
// base: 资源相对前缀('' / '../' / '../../'),仅用于品牌链接等相对引用
function layout({ lang, base, title, desc, body, pageClean = '', pageFile = '', extraHead = '' }) {
const t = T[lang];
const other = lang === 'zh' ? 'EN' : '中文';
const curDir = LANGS.find(l => l.code === lang).dir;
const canonical = `/${curDir}${pageClean}`;
const altLinks = LANGS
.map(l => `<link rel="alternate" hreflang="${l.hreflang}" href="${SITE_URL}/${l.dir}${pageClean}">`)
.join('\n') + `\n<link rel="alternate" hreflang="x-default" href="${SITE_URL}/${DEFAULT_DIR}${pageClean}">`;
const langSwitch = LANGS
.filter(l => l.code !== lang)
.map(l => `<a class="lang-switch" href="/${l.dir}${pageFile}">${l.label}</a>`)
.join('');
return `<!DOCTYPE html>
<html lang="${t.htmlLang}">
<head>
@@ -322,9 +425,7 @@ function layout({ lang, base, title, desc, body, langHref, canonical = '/', altZ
<title>${esc(title)}</title>
<meta name="description" content="${esc(desc)}">
<link rel="canonical" href="${SITE_URL}${canonical}">
<link rel="alternate" hreflang="zh-CN" href="${SITE_URL}${altZh}">
<link rel="alternate" hreflang="en" href="${SITE_URL}${altEn}">
<link rel="alternate" hreflang="x-default" href="${SITE_URL}${altZh}">
${altLinks}
<meta property="og:title" content="${esc(title)}">
<meta property="og:description" content="${esc(desc)}">
<meta property="og:type" content="website">
@@ -351,7 +452,7 @@ ${extraHead}</head>
<a href="${base}index.html#faq">${t.nav.faq}</a>
<a href="https://aiolaola.com/?utm_source=sp1" target="_blank" rel="noopener">${t.nav.learn}</a>
<a href="https://github.com/jnMetaCode/superpowers-zh" target="_blank" rel="noopener">${t.nav.github}</a>
<a class="lang-switch" href="${langHref}">${other}</a>
${langSwitch}
<button class="theme-btn" id="themeBtn" aria-label="theme" title="切换主题">◐</button>
</nav>
</header>
@@ -387,8 +488,8 @@ function renderLanding(skills, lang) {
const fill = (s, map) => s.replace(/\{(\w+)\}/g, (_, k) => map[k]);
const cards = skills.map(s => {
const title = lang === 'zh' ? s.title : s.titleEn;
const d = lang === 'zh' ? s.desc : (s.descEn || s.desc);
const title = lang === 'en' ? s.titleEn : s.title;
const d = lang === 'en' ? (s.descEn || s.desc) : s.desc;
return `
<a class="card" href="skills/${esc(s.name)}.html" data-group="${s.group}" data-name="${esc(s.name)}" data-title="${esc(title)}">
<div class="card-head"><h3>${esc(title)}</h3>${s.china ? `<span class="tag tag-cn">${t.tagCn}</span>` : ''}</div>
@@ -506,7 +607,7 @@ function renderLanding(skills, lang) {
// ---- skill 详情(操作文档)页正文 ----
function renderDetail(skill, lang) {
const t = T[lang];
const title = lang === 'zh' ? skill.title : skill.titleEn;
const title = lang === 'en' ? skill.titleEn : skill.title;
const bodyHtml = renderMarkdown(skill.raw);
const cnNotice = lang === 'en'
? '<div class="doc-notice">📖 This skill\'s content is written in Chinese — superpowers-zh is a Chinese-localized toolkit.</div>'
@@ -550,31 +651,28 @@ function build() {
copyFileSync(join(TEMPLATE, 'assets', 'qr-wechat.jpg'), join(DIST, 'assets', 'qr-wechat.jpg'));
copyFileSync(join(TEMPLATE, 'assets', 'qr-douyin.jpg'), join(DIST, 'assets', 'qr-douyin.jpg'));
// 中文站(根)
writeFileSync(join(DIST, 'index.html'), layout({
lang: 'zh', base: '', title: T.zh.title, desc: T.zh.desc,
body: renderLanding(skills, 'zh'), langHref: 'en/index.html',
canonical: '/', altZh: '/', altEn: '/en/',
}));
// 英文站(/en/
writeFileSync(join(DIST, 'en', 'index.html'), layout({
lang: 'en', base: '../', title: T.en.title, desc: T.en.desc,
body: renderLanding(skills, 'en'), langHref: '../index.html',
canonical: '/en/', altZh: '/', altEn: '/en/',
}));
// 详情(操作文档)页 ×2 语言
for (const s of skills) {
writeFileSync(join(DIST, 'skills', `${s.name}.html`), layout({
lang: 'zh', base: '../', title: `${s.title} · superpowers-zh`, desc: s.desc,
body: renderDetail(s, 'zh'), langHref: `../en/skills/${s.name}.html`,
canonical: `/skills/${s.name}`, altZh: `/skills/${s.name}`, altEn: `/en/skills/${s.name}`,
}));
writeFileSync(join(DIST, 'en', 'skills', `${s.name}.html`), layout({
lang: 'en', base: '../../', title: `${s.titleEn} · superpowers-zh`, desc: s.descEn || s.desc,
body: renderDetail(s, 'en'), langHref: `../../skills/${s.name}.html`,
canonical: `/en/skills/${s.name}`, altZh: `/skills/${s.name}`, altEn: `/en/skills/${s.name}`,
// ---- 每种语言生成首页 + 全部 skill 详情页 ----
for (const L of LANGS) {
const t = T[L.code];
const dirParts = L.dir ? [L.dir.replace(/\/$/, '')] : [];
const homeBase = L.dir ? '../' : '';
mkdirSync(join(DIST, ...dirParts, 'skills'), { recursive: true });
// 首页
writeFileSync(join(DIST, ...dirParts, 'index.html'), layout({
lang: L.code, base: homeBase, title: t.title, desc: t.desc,
body: renderLanding(skills, L.code), pageClean: '', pageFile: '',
}));
// skill 详情页
for (const s of skills) {
const title = L.code === 'en' ? s.titleEn : s.title;
const desc = L.code === 'en' ? (s.descEn || s.desc) : s.desc;
writeFileSync(join(DIST, ...dirParts, 'skills', `${s.name}.html`), layout({
lang: L.code, base: L.dir ? '../../' : '../',
title: `${title} · superpowers-zh`, desc,
body: renderDetail(s, L.code),
pageClean: `skills/${s.name}`, pageFile: `skills/${s.name}.html`,
}));
}
}
// ---- SEO: robots.txt + sitemap.xml ----
@@ -582,11 +680,14 @@ function build() {
'User-agent: *\nAllow: /\n\nSitemap: ' + SITE_URL + '/sitemap.xml\n');
const today = new Date().toISOString().slice(0, 10);
const urls = ['/', '/en/'];
for (const s of skills) { urls.push(`/skills/${s.name}`, `/en/skills/${s.name}`); }
const urls = [];
for (const L of LANGS) {
urls.push(`/${L.dir}`);
for (const s of skills) urls.push(`/${L.dir}skills/${s.name}`);
}
const sitemap = '<?xml version="1.0" encoding="UTF-8"?>\n' +
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' +
urls.map(u => ` <url><loc>${SITE_URL}${u}</loc><lastmod>${today}</lastmod><changefreq>weekly</changefreq><priority>${u === '/' ? '1.0' : '0.7'}</priority></url>`).join('\n') +
urls.map(u => ` <url><loc>${SITE_URL}${u}</loc><lastmod>${today}</lastmod><changefreq>weekly</changefreq><priority>${u === '/' ? '1.0' : (u.endsWith('/') ? '0.8' : '0.7')}</priority></url>`).join('\n') +
'\n</urlset>\n';
writeFileSync(join(DIST, 'sitemap.xml'), sitemap);
@@ -643,8 +744,8 @@ function build() {
'/styles.css\n Cache-Control: public, max-age=31536000, immutable\n' +
'/app.js\n Cache-Control: public, max-age=31536000, immutable\n');
const pages = 2 + skills.length * 2;
console.log(`✅ 生成 ${pages} 个页面:中/英首页 + ${skills.length} 个 skill × 2 语言详情页 → ${DIST}`);
const pages = LANGS.length * (1 + skills.length);
console.log(`✅ 生成 ${pages} 个页面:${LANGS.length} 语言(${LANGS.map(l => l.code).join('/')}× (首页 + ${skills.length} 个 skill 详情页)${DIST}`);
}
build();