From ab01907f64adba4abf2cd407b42b0750fcd69df4 Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Mon, 18 May 2026 21:41:00 +0800 Subject: [PATCH] docs: add i18n zh-cn implementation plan --- .../plans/2026-05-18-i18n-zh-cn.md | 1349 +++++++++++++++++ 1 file changed, 1349 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-18-i18n-zh-cn.md diff --git a/docs/superpowers/plans/2026-05-18-i18n-zh-cn.md b/docs/superpowers/plans/2026-05-18-i18n-zh-cn.md new file mode 100644 index 0000000..92e70b6 --- /dev/null +++ b/docs/superpowers/plans/2026-05-18-i18n-zh-cn.md @@ -0,0 +1,1349 @@ +# i18n (zh-CN) 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:** Add Simplified Chinese (zh-CN) as a second locale to the Ignis Network website, with URL-based routing (`/zh-cn/` prefix), a nav language switcher, translated UI strings, and localized blog posts. + +**Architecture:** Astro's built-in i18n handles URL routing (`prefixDefaultLocale: false` keeps English at `/`). TypeScript dictionaries in `src/i18n/` hold all translated strings; each Astro page calls `getTranslations(Astro.currentLocale)` at build time and passes slices as props. Blog posts move into `src/content/blog/en/` and `src/content/blog/zh-cn/` sub-directories; pages filter by locale prefix and strip it for clean URLs. + +**Tech Stack:** Astro 5 (built-in i18n), React 19 (HomePage.tsx only), TypeScript, pnpm + +**Verification command (no test suite):** `pnpm build` — must exit 0 with no type errors. Then `pnpm dev` + browser check for each new route. + +--- + +## File Map + +**New files:** +- `src/i18n/en.ts` — English translation dictionary (source of truth) +- `src/i18n/zh-cn.ts` — Chinese translation dictionary (same shape as `en.ts`) +- `src/i18n/utils.ts` — `getTranslations(locale)` helper +- `src/pages/zh-cn/index.astro` — Chinese home page +- `src/pages/zh-cn/blog/index.astro` — Chinese blog index +- `src/pages/zh-cn/blog/[slug].astro` — Chinese blog post page +- `src/content/blog/en/hello-world.md` — English post (moved from `blog/`) +- `src/content/blog/en/nix-kubernetes-setup.md` — English post (moved from `blog/`) +- `src/content/blog/zh-cn/hello-world.md` — Chinese translation +- `src/content/blog/zh-cn/nix-kubernetes-setup.md` — Chinese translation + +**Modified files:** +- `astro.config.mjs` — add `i18n` config block +- `src/layouts/Layout.astro` — derive `locale`/`currentPath`, pass to Nav+Footer, update `` +- `src/components/Nav.astro` — add props interface, render `EN | 中文` switcher +- `src/components/Footer.astro` — add `t` prop, replace hardcoded strings +- `src/components/HomePage.tsx` — add `t` and `locale` props, replace hardcoded strings +- `src/pages/index.astro` — pass `t.home`/`locale` to HomePage, filter slugs by `en/` +- `src/pages/blog/index.astro` — filter posts by `en/`, strip prefix, use `t.blog` +- `src/pages/blog/[slug].astro` — filter paths by `en/`, strip prefix, use `t.blog` + +--- + +## Task 1: Configure Astro i18n routing + +**Files:** +- Modify: `astro.config.mjs` + +- [ ] **Open `astro.config.mjs` and replace its contents with:** + +```js +import { defineConfig } from "astro/config"; +import react from "@astrojs/react"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + integrations: [react()], + vite: { plugins: [tailwindcss()] }, + i18n: { + defaultLocale: "en", + locales: ["en", "zh-cn"], + routing: { prefixDefaultLocale: false }, + }, + server: { + host: "0.0.0.0", + port: 5000, + allowedHosts: true, + }, + markdown: { + shikiConfig: { + theme: "github-light", + }, + }, +}); +``` + +- [ ] **Run build to confirm the config parses cleanly:** + +```bash +pnpm build +``` + +Expected: build succeeds (the site is unchanged so far — routing config takes effect when new pages are added). + +- [ ] **Commit:** + +```bash +git add astro.config.mjs +git commit -m "feat(i18n): configure Astro built-in i18n routing" +``` + +--- + +## Task 2: Create translation dictionaries + +**Files:** +- Create: `src/i18n/en.ts` +- Create: `src/i18n/zh-cn.ts` +- Create: `src/i18n/utils.ts` + +- [ ] **Create `src/i18n/en.ts`:** + +```ts +export const en = { + nav: { + blog: "Blog", + contact: "Contact Us", + langSwitch: { label: "中文", locale: "zh-cn" }, + }, + footer: { + tagline: "Smart infrastructure built for stability, longevity, and trust.", + eventsHeading: "Events", + nixcnConference: "NixCN Conference", + contactHeading: "Contact", + contactLink: "Contact Us", + ctaTitle: "Get in touch", + ctaDesc: "Reach out whenever there's something worth building together.", + ctaButton: "Contact Us", + copyright: "© 2026 Ignis Network, Co. All rights reserved.", + builtWith: "Built with ♥ for the digital age", + }, + home: { + heroDesc1: + "We're a startup company that builds resilient digital infrastructure designed to endure, with AI applied where it meaningfully adds strength and insight.", + heroDesc2: + "We focus on stability, clarity, and long-term reliability — creating systems that smartly carry what matters, support growth over time, and let everything else move forward with confidence.", + latestPostsTitle: "Latest from the Blog", + viewAll: "View all →", + minRead: "min read", + dateLocale: "en-US", + }, + blog: { + title: "Blog", + description: + "Thoughts on infrastructure, AI, and building for the long term.", + back: "← Blog", + minRead: "min read", + dateLocale: "en-US", + }, +} as const; + +export type Translations = typeof en; +``` + +- [ ] **Create `src/i18n/zh-cn.ts`:** + +```ts +import type { Translations } from "./en"; + +export const zhCn: Translations = { + nav: { + blog: "博客", + contact: "联系我们", + langSwitch: { label: "EN", locale: "en" }, + }, + footer: { + tagline: "为稳定性、持久性和可信度而生的智能基础设施。", + eventsHeading: "活动", + nixcnConference: "NixCN 大会", + contactHeading: "联系", + contactLink: "联系我们", + ctaTitle: "联系我们", + ctaDesc: "随时联系,共同构建有价值的事物。", + ctaButton: "联系我们", + copyright: "© 2026 Ignis Network, Co. 保留所有权利。", + builtWith: "为数字时代倾心打造", + }, + home: { + heroDesc1: + "我们是一家初创公司,构建经久耐用的弹性数字基础设施,并在有意义的场景中应用 AI 来增强系统的强度与洞察力。", + heroDesc2: + "我们专注于稳定性、清晰度和长期可靠性——打造能够承载重要业务的智能系统,支持持续增长,让一切都更有信心地向前推进。", + latestPostsTitle: "博客最新动态", + viewAll: "查看全部 →", + minRead: "分钟阅读", + dateLocale: "zh-CN", + }, + blog: { + title: "博客", + description: "关于基础设施、AI 以及长远构建的思考。", + back: "← 博客", + minRead: "分钟阅读", + dateLocale: "zh-CN", + }, +}; +``` + +- [ ] **Create `src/i18n/utils.ts`:** + +```ts +import { en } from "./en"; +import { zhCn } from "./zh-cn"; + +export function getTranslations(locale: string | undefined) { + return locale === "zh-cn" ? zhCn : en; +} +``` + +- [ ] **Run build to verify no type errors in the new files:** + +```bash +pnpm build +``` + +Expected: exits 0. (Nothing consumes these files yet, but TypeScript still checks them.) + +- [ ] **Commit:** + +```bash +git add src/i18n/ +git commit -m "feat(i18n): add en/zh-cn translation dictionaries" +``` + +--- + +## Task 3: Restructure blog content into locale sub-directories + +**Files:** +- Create: `src/content/blog/en/hello-world.md` (moved) +- Create: `src/content/blog/en/nix-kubernetes-setup.md` (moved) +- Create: `src/content/blog/zh-cn/hello-world.md` +- Create: `src/content/blog/zh-cn/nix-kubernetes-setup.md` +- Delete: `src/content/blog/hello-world.md` +- Delete: `src/content/blog/nix-kubernetes-setup.md` + +After this task the build will break until Task 8 (English blog pages) updates their `getCollection` calls to filter by the new `en/` slug prefix. That's expected — commit anyway and continue. + +- [ ] **Move the English posts into `en/` sub-directory:** + +```bash +mkdir -p src/content/blog/en src/content/blog/zh-cn +mv src/content/blog/hello-world.md src/content/blog/en/hello-world.md +mv src/content/blog/nix-kubernetes-setup.md src/content/blog/en/nix-kubernetes-setup.md +``` + +- [ ] **Create `src/content/blog/zh-cn/hello-world.md`:** + +```markdown +--- +title: "你好,世界:Ignis Network 正式登场" +description: "我们很高兴正式介绍 Ignis Network —— 我们的使命、正在构建的内容以及未来的计划。" +pubDate: 2026-05-18 +category: company +emoji: "🔥" +tags: ["公告"] +--- + +经过数月的默默耕耘,今天我们正式向世界介绍自己。 + +## Ignis Network 是什么? + +Ignis Network 构建经久耐用的弹性数字基础设施。我们专注于稳定性、清晰度和长期可靠性——打造能够承载重要业务的系统,支持持续增长,让一切都更有信心地向前推进。 + +我们在 AI 真正能增强基础设施的智能和洞察力的地方应用 AI。不是无处不在,不是为了 AI 而 AI,而是在 AI 真正让基础设施更智能的地方。 + +## 我们在构建什么 + +我们的技术栈经过深思熟虑,以求长久:NixOS 用于声明式、可复现的系统配置;Kubernetes 用于容器编排;Terraform 用于基础设施即代码;OpenTelemetry 用于不会过时的可观测性。 + +我们正在这套基础之上构建工具和服务——从 Ignis AI 开始,这是我们的第一款产品。 + +## 接下来是什么 + +我们将在这里持续分享:技术深度解析、产品公告,以及对"构建可信赖基础设施意味着什么"的偶尔思考。 + +如果你想关注我们的进展,最好的方式是[联系我们](mailto:contact@ignisnet.co)。 + +欢迎来到 Ignis Network。 +``` + +- [ ] **Create `src/content/blog/zh-cn/nix-kubernetes-setup.md`:** + +```markdown +--- +title: "在 NixOS 上运行 Kubernetes:我们的实践" +description: "我们如何使用 NixOS 管理可复现的 Kubernetes 集群,以及为何认为这是构建严肃基础设施的正确基础。" +pubDate: 2026-05-05 +category: technical +emoji: "⚙️" +tags: ["kubernetes", "nixos", "基础设施"] +--- + +我们已经在 NixOS 上运行 Kubernetes 数月了。以下是我们的实践和做出这一选择的原因。 + +## 为什么选择 NixOS? + +核心原因是可复现性。集群中的每个节点都从一份相同的、以声明式描述的配置启动。当我们添加节点时,不需要运行 Ansible playbook 并祈祷它与其他节点一致——我们从同一个 Nix 表达式进行配置,结果是确定的。 + +```nix +services.kubernetes = { + roles = [ "master" "node" ]; + masterAddress = "control.internal"; +}; +` `` ` + +这意味着我们的集群配置存储在 Git 中。变更经过审查,回滚只需一条 `nixos-rebuild switch --rollback`。 + +## 集群架构 + +我们运行一个三节点控制平面,按需添加工作节点。TalosOS 曾在我们的候选列表中,但我们最终选择了 NixOS,因为它具有在 Kubernetes 旁边运行任意服务的灵活性——在我们早期阶段"集群工作负载"与"主机服务"边界仍在演化时,这一点非常有用。 + +## 从第一天起的可观测性 + +我们在每个节点上以 DaemonSet 形式部署 OpenTelemetry 采集器。Traces、指标和日志流向中心聚合点,再分发到各存储后端。 + +```yaml +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: otel-collector +spec: + selector: + matchLabels: + app: otel-collector + template: + metadata: + labels: + app: otel-collector + spec: + containers: + - name: otel-collector + image: otel/opentelemetry-collector-contrib:latest +` `` ` + +## 我们会做什么不同 + +NixOS Kubernetes 模块在证书管理方面有些粗糙。请为此预留时间——可以解决,但并不快。 + +我们也会从第一天起就将 Cilium 作为 CNI,而不是使用默认选项。它提供的可观测性原语值得额外的配置成本。 +``` + +- [ ] **Commit:** + +```bash +git add src/content/blog/ +git commit -m "feat(i18n): restructure blog content into en/ and zh-cn/ sub-dirs" +``` + +--- + +## Task 4: Update Layout.astro — thread locale, currentPath, and t to Nav and Footer + +**Files:** +- Modify: `src/layouts/Layout.astro` + +`Layout.astro` is the single place that knows the current locale. It derives `locale` and `currentPath`, fetches translations, and passes slices to `Nav` and `Footer`. Page components get their own translations directly in their own page files (because `` cannot inject props into slot content). + +- [ ] **Replace the frontmatter block (lines 1–21) of `src/layouts/Layout.astro` with:** + +```astro +--- +import Nav from '../components/Nav.astro'; +import Footer from '../components/Footer.astro'; +import { getTranslations } from '../i18n/utils'; + +interface Props { + title?: string; + description?: string; + canonicalPath?: string; + noindex?: boolean; +} + +const { + title = 'Ignis Network', + description = 'Redefining the digital frontier with next-generation internet services and advanced AI.', + canonicalPath = '', + noindex = false, +} = Astro.props; + +const locale = Astro.currentLocale ?? 'en'; +const rawPath = Astro.url.pathname; +const currentPath = + locale === 'zh-cn' ? rawPath.replace(/^\/zh-cn/, '') || '/' : rawPath; + +const t = getTranslations(locale); +const siteUrl = Astro.site?.href || ''; +const canonicalUrl = `${siteUrl.replace(/\/$/, '')}${canonicalPath}`; +--- +``` + +- [ ] **Update the `` opening tag (was ``):** + +```astro + +``` + +- [ ] **Update the `