背景

博客上线初期只支持中英文。随着内容积累,我们于 2026-04-07 正式接入俄语。从决定到上线只用了很短时间,这得益于一套在重构阶段就打好的参数化 i18n 架构。

本文不重述架构细节(可参考前文《Astro 国际化重构:从平行目录树到参数化路由》),而是把新增语言的具体操作步骤固定成可复用的工作流。

前置要求

博客已具备以下基础设施:

  • src/i18n/config.ts — 语言配置中心
  • src/i18n/utils.tst() 翻译函数
  • src/i18n/{zh,en}.json — 现有翻译文件
  • src/layouts/Layout.astro — 国旗下拉菜单

标准流程(5 步)

Step 1 — 注册语言代码

修改文件: src/i18n/config.ts

在三个配置项中各添加一行:

export const locales = ['zh', 'en', 'ru'] as const;
//                                         ↑ 新语言代码

export const localeNames: Record<Locale, string> = {
  zh: '中文',
  en: 'EN',
  ru: 'RU',   // ← 新增
};

export const localePaths: Record<Locale, string> = {
  zh: '',
  en: 'en',
  ru: 'ru',   // ← 新增
};

localePaths 中的值必须与 locales 数组中的字面量一致,用于 URL 前缀生成。

Step 2 — 创建翻译文件

新增文件: src/i18n/{code}.json

zh.json 复制全套 key,将中文值替换为新语言。命名规范:

  • 所有 key 使用 snake_case(如 hero_title 而非 heroTitle
  • 翻译函数 t()utils.ts 中按 dict[lang][key] 取值,严格区分大小写

必须包含的顶级 key:

顶级 key说明
nav导航链接
home首页内容(标题、副标题)
archive归档页(分页、筛选)
tags标签页
about关于页(使命卡片、技术栈、链接)
changelog变更日志
post文章页(翻页、标签、目录)
search搜索
404404 迷航页
footer页脚

Step 3 — 注册翻译字典

修改文件: src/i18n/utils.ts

dicts 对象中注册新语言:

const dicts: Record<Locale, Record<string, any>> = {
  zh,
  en,
  ru,   // ← 新增
};

Step 4 — 添加国旗下拉选项

修改文件: src/layouts/Layout.astro

在语言下拉菜单的 HTML 中添加新选项,使用 flagcdn.com 国旗图片:

<!-- 当前语言按钮显示 -->
{lang === 'ru' ? <span class="flag-label">
  <img src="https://flagcdn.com/w20/ru.png" width="20" height="14" alt="Русский" class="flag-img"/>
  Русский
</span> : ...}

<!-- 下拉菜单选项 -->
<a href="/ru/" class="lang-option" data-lang="ru">
  <img src="https://flagcdn.com/w20/ru.png" width="20" height="14" alt="Русский" class="flag-img"/>
  Русский
</a>

国别代码使用 ISO 3166-1 alpha-2(俄罗斯 → ru,英国 → gb,中国 → cn)。

Step 5 — 构建部署

npm run build
docker build -t astro-blog:latest .
docker stop astro-blog && docker rm astro-blog
docker run -d --name astro-blog --network astro-net -p 8083:80 --restart unless-stopped astro-blog:latest
git add -A && git commit -m "i18n: add Russian language" && git push

避坑指南

key 命名不一致

症状: 页面显示 key 名(如 about.intro_p1)而非翻译文本。

原因: zh.jsonsnake_case,翻译者写成了 camelCase

解法: 始终从 zh.json 复制 key 名,不要自己发明。

about.astro 残留硬编码

症状: about 页部分内容不翻译(其他页面正常)。

原因: about.astro 中曾使用 lang === 'en' ? '英文文本' : '中文文本' 内联判断,未走 t() 函数。

解法: 将组件中所有内联文案抽取为 t() 调用,新增语言后自然生效。

flagcdn 无法访问

症状: 国旗图片在服务器上 404。

原因: 本机网络无法访问 flagcdn.com

解法: 将国旗图片下载到本地 public/flags/ 目录,改用相对路径引用。

总结

这套流程的核心优势在于:新增语言是数据操作,而非工程操作。不再需要复制页面文件、不再需要修改每个组件的路由逻辑、不再需要维护散落在各处的语言判断。

架构设计决定了维护成本的上限。i18n 重构的投资在今天得到了回报——俄语上线几乎没有摩擦。