mirror of
https://github.com/jnMetaCode/superpowers-zh.git
synced 2026-09-02 22:54:06 +08:00
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:
179
site/build.mjs
179
site/build.mjs
@@ -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: 'superpowers(250k+ ⭐)完整漢化 + 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: '一套 skill,20 款工具通用', 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: '一套 skill,Claude Code / Cursor / Codex / Gemini CLI / Windsurf… 全適配,換工具不用換習慣。' },
|
||||
{ icon: '⚡', t: '一條命令安裝', d: 'npx superpowers-zh 自動識別專案裡用的是哪款工具並安裝,零設定,裝完重啟即生效。' },
|
||||
{ icon: '🇨🇳', t: '中國原創 Skills', d: '中文程式碼審查話術、中文提交規範、中文文件排版、國內 Git 平台(Gitee/Coding/極狐)設定 —— 上游沒有。' },
|
||||
{ icon: '📖', t: '完整漢化上游', d: '同步 obra/superpowers(250k+ ⭐),核心 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();
|
||||
|
||||
Reference in New Issue
Block a user