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 ` ` call to pass props:**
+
+```astro
+
+```
+
+- [ ] **Update the `` call to pass props:**
+
+```astro
+
+```
+
+- [ ] **Run build — it will fail because Nav and Footer don't accept those props yet. That's expected. Commit the partial change:**
+
+```bash
+git add src/layouts/Layout.astro
+git commit -m "feat(i18n): Layout derives locale/currentPath, threads t to Nav+Footer"
+```
+
+---
+
+## Task 5: Update Nav.astro — add language switcher
+
+**Files:**
+- Modify: `src/components/Nav.astro`
+
+- [ ] **Replace the entire contents of `src/components/Nav.astro` with:**
+
+```astro
+---
+interface Props {
+ locale: string;
+ currentPath: string;
+ t: {
+ blog: string;
+ contact: string;
+ langSwitch: { label: string; locale: string };
+ };
+}
+
+const { locale, currentPath, t } = Astro.props;
+
+const altPath =
+ t.langSwitch.locale === 'zh-cn'
+ ? '/zh-cn' + currentPath
+ : currentPath;
+---
+
+
+
+
+
+
+```
+
+- [ ] **Run build:**
+
+```bash
+pnpm build
+```
+
+Expected: build now succeeds for Nav (Footer still fails until Task 6).
+
+- [ ] **Commit:**
+
+```bash
+git add src/components/Nav.astro
+git commit -m "feat(i18n): Nav accepts t/locale/currentPath, adds EN|ZH switcher"
+```
+
+---
+
+## Task 6: Update Footer.astro — accept t prop
+
+**Files:**
+- Modify: `src/components/Footer.astro`
+
+- [ ] **Replace the entire contents of `src/components/Footer.astro` with:**
+
+```astro
+---
+import type { Translations } from '../i18n/en';
+
+interface Props {
+ t: Translations['footer'];
+}
+
+const { t } = Astro.props;
+---
+
+
+
+
+
+
+
+
+
+
+
{t.eventsHeading}
+
+
+ {t.nixcnConference}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{t.copyright}
+
{t.builtWith}
+
+
+
+```
+
+- [ ] **Run build:**
+
+```bash
+pnpm build
+```
+
+Expected: build succeeds. The blog pages will error because they still reference the old flat `blog/` collection paths (no `en/` prefix) — that's fixed in Tasks 8–9.
+
+- [ ] **Commit:**
+
+```bash
+git add src/components/Footer.astro
+git commit -m "feat(i18n): Footer accepts t prop, replaces hardcoded strings"
+```
+
+---
+
+## Task 7: Update HomePage.tsx — add t and locale props
+
+**Files:**
+- Modify: `src/components/HomePage.tsx`
+
+Only two sections need changes: the `Hero` description paragraphs and the `LatestPosts` section strings/date format. The canvas-nest animation, tech badges, and Framer Motion setup are untouched.
+
+- [ ] **Add the `HomeTranslations` type and update the `LatestPosts` component signature. Replace lines 20–107 (`export type PostPreview = ...` through the closing `};` of `LatestPosts`) with:**
+
+```tsx
+export type PostPreview = {
+ slug: string;
+ title: string;
+ description: string;
+ pubDate: string;
+ category: 'technical' | 'company';
+ emoji?: string;
+ readTime: number;
+};
+
+export type HomeTranslations = {
+ heroDesc1: string;
+ heroDesc2: string;
+ latestPostsTitle: string;
+ viewAll: string;
+ minRead: string;
+ dateLocale: string;
+};
+
+const CATEGORY_STYLE = {
+ company: {
+ thumb: 'from-primary/20 to-primary/40',
+ tag: 'text-primary bg-primary/10',
+ defaultEmoji: '🔥',
+ },
+ technical: {
+ thumb: 'from-secondary/50 to-secondary/70',
+ tag: 'text-secondary-foreground bg-secondary/40',
+ defaultEmoji: '⚙️',
+ },
+} as const;
+
+const LatestPosts = ({
+ posts,
+ t,
+ locale,
+}: {
+ posts: PostPreview[];
+ t: HomeTranslations;
+ locale: string;
+}) => {
+ const blogBase = locale === 'zh-cn' ? '/zh-cn/blog' : '/blog';
+ if (posts.length === 0) return null;
+ return (
+
+
+
+
+ {posts.map((post, idx) => {
+ const style = CATEGORY_STYLE[post.category];
+ const emoji = post.emoji ?? style.defaultEmoji;
+ const date = new Date(post.pubDate).toLocaleDateString(t.dateLocale, {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ });
+ return (
+
+
+ {emoji}
+
+
+
+ {post.category}
+
+
+ {post.title}
+
+
+ {post.description}
+
+
+ {date} · {post.readTime} {t.minRead}
+
+
+
+ );
+ })}
+
+
+
+ );
+};
+```
+
+- [ ] **Update the `Hero` component to accept and render translated description paragraphs. Find the `Hero` component (starts at `const Hero = () => {`) and replace the two `
` description tags inside `` with:**
+
+```tsx
+
+ {t.heroDesc1}
+
+
+ {t.heroDesc2}
+
+```
+
+And update the `Hero` signature to accept `t`:
+
+```tsx
+const Hero = ({ t }: { t: HomeTranslations }) => {
+```
+
+- [ ] **Update the default `Home` export to accept `t` and thread it through:**
+
+Replace the current `Home` export (was):
+```tsx
+export default function Home({
+ latestPosts = [],
+}: {
+ latestPosts?: PostPreview[];
+}) {
+ return (
+
+
+
+
+ );
+}
+```
+
+With:
+```tsx
+export default function Home({
+ latestPosts = [],
+ t,
+ locale = 'en',
+}: {
+ latestPosts?: PostPreview[];
+ t: HomeTranslations;
+ locale?: string;
+}) {
+ return (
+
+
+
+
+ );
+}
+```
+
+- [ ] **Run build:**
+
+```bash
+pnpm build
+```
+
+Expected: TypeScript errors on `src/pages/index.astro` (it still passes the old props). That's fixed in Task 8.
+
+- [ ] **Commit:**
+
+```bash
+git add src/components/HomePage.tsx
+git commit -m "feat(i18n): HomePage accepts t/locale props, replaces hardcoded strings"
+```
+
+---
+
+## Task 8: Update English page files — pass translations and fix blog slug filtering
+
+**Files:**
+- Modify: `src/pages/index.astro`
+- Modify: `src/pages/blog/index.astro`
+- Modify: `src/pages/blog/[slug].astro`
+
+After this task the English site is fully functional with translations. The zh-cn routes don't exist yet.
+
+- [ ] **Replace `src/pages/index.astro` with:**
+
+```astro
+---
+import Layout from '../layouts/Layout.astro';
+import HomePage from '../components/HomePage';
+import { getCollection } from 'astro:content';
+import { getTranslations } from '../i18n/utils';
+
+function readTime(body: string): number {
+ return Math.max(1, Math.ceil(body.split(/\s+/).filter(Boolean).length / 200));
+}
+
+const t = getTranslations(Astro.currentLocale);
+
+const allPosts = await getCollection(
+ 'blog',
+ ({ data, slug }) => !data.draft && slug.startsWith('en/'),
+);
+const latestPosts = allPosts
+ .sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
+ .slice(0, 3)
+ .map((post) => ({
+ slug: post.slug.replace(/^en\//, ''),
+ title: post.data.title,
+ description: post.data.description,
+ pubDate: post.data.pubDate.toISOString(),
+ category: post.data.category,
+ emoji: post.data.emoji,
+ readTime: readTime(post.body),
+ }));
+---
+
+
+
+
+```
+
+- [ ] **Replace `src/pages/blog/index.astro` with:**
+
+```astro
+---
+import Layout from '../../layouts/Layout.astro';
+import { getCollection } from 'astro:content';
+import { getTranslations } from '../../i18n/utils';
+
+function readTime(body: string): number {
+ return Math.max(1, Math.ceil(body.split(/\s+/).filter(Boolean).length / 200));
+}
+
+const t = getTranslations(Astro.currentLocale);
+
+const CATEGORY_STYLE = {
+ company: {
+ thumb: 'from-primary/20 to-primary/40',
+ tag: 'text-primary bg-primary/10',
+ defaultEmoji: '🔥',
+ },
+ technical: {
+ thumb: 'from-secondary/50 to-secondary/70',
+ tag: 'text-secondary-foreground bg-secondary/40',
+ defaultEmoji: '⚙️',
+ },
+} as const;
+
+const allPosts = await getCollection(
+ 'blog',
+ ({ data, slug }) => !data.draft && slug.startsWith('en/'),
+);
+const posts = allPosts.sort(
+ (a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
+);
+---
+
+
+
+
+
+
+```
+
+- [ ] **Replace `src/pages/blog/[slug].astro` with:**
+
+```astro
+---
+import Layout from '../../layouts/Layout.astro';
+import { getCollection } from 'astro:content';
+import { getTranslations } from '../../i18n/utils';
+
+export async function getStaticPaths() {
+ const posts = await getCollection(
+ 'blog',
+ ({ data, slug }) => !data.draft && slug.startsWith('en/'),
+ );
+ return posts.map((post) => ({
+ params: { slug: post.slug.replace(/^en\//, '') },
+ props: { post },
+ }));
+}
+
+const { post } = Astro.props;
+const { slug } = Astro.params;
+const { Content } = await post.render();
+
+const t = getTranslations(Astro.currentLocale);
+
+function readTime(body: string): number {
+ return Math.max(1, Math.ceil(body.split(/\s+/).filter(Boolean).length / 200));
+}
+
+const CATEGORY_STYLE = {
+ company: { tag: 'text-primary bg-primary/10' },
+ technical: { tag: 'text-secondary-foreground bg-secondary/40' },
+} as const;
+
+const style = CATEGORY_STYLE[post.data.category];
+const date = post.data.pubDate.toLocaleDateString(t.blog.dateLocale, {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+});
+const rt = readTime(post.body);
+---
+
+
+
+
+
+
+```
+
+- [ ] **Run build and confirm it succeeds:**
+
+```bash
+pnpm build
+```
+
+Expected: exits 0. English site is fully functional with translations threaded through.
+
+- [ ] **Start dev server and visually verify English routes:**
+
+```bash
+pnpm dev
+```
+
+Check:
+- `/` — home page renders, hero text visible, latest posts show
+- `/blog` — blog index lists both posts with correct dates
+- `/blog/hello-world` — post renders, "← Blog" back link works
+- Nav shows "Blog" and "Contact Us" and "中文" switcher link
+
+- [ ] **Commit:**
+
+```bash
+git add src/pages/
+git commit -m "feat(i18n): update English pages to use t prop and en/ slug prefix"
+```
+
+---
+
+## Task 9: Create zh-cn page routes
+
+**Files:**
+- Create: `src/pages/zh-cn/index.astro`
+- Create: `src/pages/zh-cn/blog/index.astro`
+- Create: `src/pages/zh-cn/blog/[slug].astro`
+
+- [ ] **Create `src/pages/zh-cn/index.astro`:**
+
+```astro
+---
+import Layout from '../../layouts/Layout.astro';
+import HomePage from '../../components/HomePage';
+import { getCollection } from 'astro:content';
+import { getTranslations } from '../../i18n/utils';
+
+function readTime(body: string): number {
+ return Math.max(1, Math.ceil(body.split(/\s+/).filter(Boolean).length / 200));
+}
+
+const t = getTranslations(Astro.currentLocale);
+
+const allPosts = await getCollection('blog', ({ data }) => !data.draft);
+const enPosts = allPosts.filter((p) => p.slug.startsWith('en/'));
+const zhMap = new Map(
+ allPosts
+ .filter((p) => p.slug.startsWith('zh-cn/'))
+ .map((p) => [p.slug.replace(/^zh-cn\//, ''), p]),
+);
+
+const latestPosts = enPosts
+ .sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
+ .slice(0, 3)
+ .map((enPost) => {
+ const cleanSlug = enPost.slug.replace(/^en\//, '');
+ const post = zhMap.get(cleanSlug) ?? enPost;
+ return {
+ slug: cleanSlug,
+ title: post.data.title,
+ description: post.data.description,
+ pubDate: post.data.pubDate.toISOString(),
+ category: post.data.category,
+ emoji: post.data.emoji,
+ readTime: readTime(post.body),
+ };
+ });
+---
+
+
+
+
+```
+
+- [ ] **Create `src/pages/zh-cn/blog/index.astro`:**
+
+```astro
+---
+import Layout from '../../../layouts/Layout.astro';
+import { getCollection } from 'astro:content';
+import { getTranslations } from '../../../i18n/utils';
+
+function readTime(body: string): number {
+ return Math.max(1, Math.ceil(body.split(/\s+/).filter(Boolean).length / 200));
+}
+
+const t = getTranslations(Astro.currentLocale);
+
+const CATEGORY_STYLE = {
+ company: {
+ thumb: 'from-primary/20 to-primary/40',
+ tag: 'text-primary bg-primary/10',
+ defaultEmoji: '🔥',
+ },
+ technical: {
+ thumb: 'from-secondary/50 to-secondary/70',
+ tag: 'text-secondary-foreground bg-secondary/40',
+ defaultEmoji: '⚙️',
+ },
+} as const;
+
+const allPosts = await getCollection('blog', ({ data }) => !data.draft);
+const enPosts = allPosts.filter((p) => p.slug.startsWith('en/'));
+const zhMap = new Map(
+ allPosts
+ .filter((p) => p.slug.startsWith('zh-cn/'))
+ .map((p) => [p.slug.replace(/^zh-cn\//, ''), p]),
+);
+
+const posts = enPosts
+ .sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime())
+ .map((enPost) => {
+ const cleanSlug = enPost.slug.replace(/^en\//, '');
+ return { post: zhMap.get(cleanSlug) ?? enPost, cleanSlug };
+ });
+---
+
+
+
+
+
+
+```
+
+- [ ] **Create `src/pages/zh-cn/blog/[slug].astro`:**
+
+```astro
+---
+import Layout from '../../../layouts/Layout.astro';
+import { getCollection } from 'astro:content';
+import { getTranslations } from '../../../i18n/utils';
+
+export async function getStaticPaths() {
+ const allPosts = await getCollection('blog', ({ data }) => !data.draft);
+ const enPosts = allPosts.filter((p) => p.slug.startsWith('en/'));
+ const zhMap = new Map(
+ allPosts
+ .filter((p) => p.slug.startsWith('zh-cn/'))
+ .map((p) => [p.slug.replace(/^zh-cn\//, ''), p]),
+ );
+
+ return enPosts.map((enPost) => {
+ const cleanSlug = enPost.slug.replace(/^en\//, '');
+ const post = zhMap.get(cleanSlug) ?? enPost;
+ return {
+ params: { slug: cleanSlug },
+ props: { post },
+ };
+ });
+}
+
+const { post } = Astro.props;
+const { slug } = Astro.params;
+const { Content } = await post.render();
+
+const t = getTranslations(Astro.currentLocale);
+
+function readTime(body: string): number {
+ return Math.max(1, Math.ceil(body.split(/\s+/).filter(Boolean).length / 200));
+}
+
+const CATEGORY_STYLE = {
+ company: { tag: 'text-primary bg-primary/10' },
+ technical: { tag: 'text-secondary-foreground bg-secondary/40' },
+} as const;
+
+const style = CATEGORY_STYLE[post.data.category];
+const date = post.data.pubDate.toLocaleDateString(t.blog.dateLocale, {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+});
+const rt = readTime(post.body);
+---
+
+
+
+
+
+
+```
+
+- [ ] **Run final build:**
+
+```bash
+pnpm build
+```
+
+Expected: exits 0, all 8 routes generated (`/`, `/blog`, `/blog/hello-world`, `/blog/nix-kubernetes-setup`, `/zh-cn/`, `/zh-cn/blog`, `/zh-cn/blog/hello-world`, `/zh-cn/blog/nix-kubernetes-setup`).
+
+- [ ] **Start dev server and verify zh-cn routes:**
+
+```bash
+pnpm dev
+```
+
+Check:
+- `/zh-cn/` — Chinese home, hero text in Chinese, "博客最新动态" section title
+- `/zh-cn/blog` — "博客" heading, Chinese post titles
+- `/zh-cn/blog/hello-world` — Chinese post content, "← 博客" back link, Chinese date format
+- Nav shows "博客", "联系我们", and "EN" switcher linking back to `/`
+- Nav "中文" link on English pages routes to `/zh-cn/`
+- Footer renders Chinese tagline on zh-cn pages
+
+- [ ] **Commit:**
+
+```bash
+git add src/pages/zh-cn/
+git commit -m "feat(i18n): add zh-cn page routes for home, blog index, blog posts"
+```