TL;DR
Astro 6.x 内置 i18n 支持,本文教你实现:URL 路由(/blog/post vs /en/blog/post)、内容双语组织、导航语言切换器、默认语言重定向。完成后博客支持中文和英文两套内容。
前提:
- Astro 6.x 项目
- 有两套内容(中文 + 英文)
- 了解 Astro Content Collections
Step 1:理解两种路由策略
Astro i18n 支持两种 URL 结构:
策略 A:子路径模式(推荐)
/blog/my-post → 中文
/en/blog/my-post → 英文
策略 B:文件名模式
/src/pages/blog/my-post.md → 中文
/src/pages/en/blog/my-post.md → 英文
策略 A 更清晰,URL 结构统一,推荐使用。
Step 2:配置 astro.config.mjs
// astro.config.mjs
export default defineConfig({
i18n: {
defaultLocale: 'zh',
locales: ['zh', 'en'],
routing: {
prefixDefaultLocale: false, // 默认语言不加前缀(/blog 而非 /zh/blog)
},
},
});
prefixDefaultLocale: false 让中文 URL 不加 /zh 前缀,英文加 /en。
Step 3:组织内容结构
src/content/blog/
my-post/
index.md # 中文(默认)
index.en.md # 英文版本
Astro 会自动识别 index.en.md 为英文版本,index.md 为中文版本。
注意:这种方式要求中文和英文在同一目录下(同一 slug)。如果内容差异大(不是翻译关系),可以用不同 content collection。
Step 4:获取翻译版本
// 在文章页面中获取其他语言版本
const { entry, render } = Astro.props;
const lang = Astro.currentLocale; // 'zh' 或 'en'
// 获取其他语言版本
const allEntries = await getCollection('blog');
const translations = allEntries.filter(e =>
e.slug.replace(`/${lang}`, '') === entry.slug.replace(`/${lang}`, '') &&
e.slug !== entry.slug
);
或者用 Astro 内置的 getRelativeLocaleUrl:
---
// 获取当前页面的英文版本 URL
const enUrl = getRelativeLocaleUrl('en', currentPath);
---
<a href={enUrl}>English</a>
Step 5:语言切换器组件
---
// src/components/LanguageSwitcher.astro
const currentLang = Astro.currentLocale || 'zh';
const targetLang = currentLang === 'zh' ? 'en' : 'zh';
const currentPath = Astro.url.pathname;
// 去掉当前语言前缀,得到路径部分
let pathWithoutLang = currentPath;
if (currentPath.startsWith('/en')) {
pathWithoutLang = currentPath.replace('/en', '') || '/';
} else if (currentPath.startsWith('/zh')) {
pathWithoutLang = currentPath.replace('/zh', '') || '/';
}
// 如果默认语言不加前缀,切换到英文需要加 /en
const targetPath = targetLang === 'en'
? `/en${pathWithoutLang}`
: pathWithoutLang;
---
<div class="lang-switcher">
<a href={targetPath} class={targetLang === 'en' ? 'active' : ''}>
EN
</a>
<span class="sep">|</span>
<a href={pathWithoutLang === '/' ? '/' : pathWithoutLang} class={targetLang === 'zh' ? 'active' : ''}>
中文
</a>
</div>
<style is:inline>
.lang-switcher { display: flex; gap: 4px; align-items: center; }
.lang-switcher a { font-size: 14px; color: var(--text-secondary); text-decoration: none; }
.lang-switcher a.active { color: var(--color-primary); font-weight: 600; }
.lang-switcher .sep { color: var(--border); }
</style>
Step 6:默认语言重定向
如果用户访问 /blog/my-post,应该自动重定向到中文版本(默认语言)。这不需要额外配置,Astro i18n 默认行为就是这样。
但如果用户访问 /en/blog/my-post 时英文内容不存在,需要重定向到中文版本:
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware(async (context, next) => {
const lang = context.currentLocale;
const path = context.url.pathname;
// 检查当前语言版本是否存在
const allEntries = await getCollection('blog');
const pathWithoutLang = path.replace(/^\/(zh|en)/, '') || '/';
const hasThisLang = allEntries.some(e => e.slug.endsWith(path));
if (!hasThisLang && lang === 'en') {
// 英文版本不存在,重定向到中文
return context.redirect(pathWithoutLang);
}
return next();
});
Step 7:导航中语言链接
在导航栏或 Footer 中加入语言切换:
---
// src/components/Header.astro
import LanguageSwitcher from './LanguageSwitcher.astro';
---
<nav>
<a href="/">首页</a>
<a href="/blog">博客</a>
<a href="/about">关于</a>
<LanguageSwitcher />
</nav>
Step 8:SEO 配置
每篇文章需要在 <head> 中添加 hreflang 声明:
---
// 在 BlogPost.astro 的 <head> 中
const enEntry = allEntries.find(e => e.slug === entry.slug.replace(/^\/zh/, '/en'));
---
{entry.slug.includes('/en') && (
<link rel="alternate" hreflang="zh" href={entry.slug.replace('/en', '/zh')} />
)}
{enEntry && (
<link rel="alternate" hreflang="en" href={`/en${entry.slug}`} />
)}
同时在 src/pages/sitemap.xml.ts 中生成多语言 sitemap:
// src/pages/sitemap.xml.ts
import { getCollection } from 'astro:content';
export async function GET(context) {
const blog = await getCollection('blog');
const baseUrl = context.site;
const zhUrls = blog.map(entry => ({
url: `${baseUrl}${entry.slug}`,
changefreq: 'weekly',
priority: entry.data.important ? 0.8 : 0.6,
}));
const enEntries = blog.filter(e => e.id.endsWith('.en.md'));
const enUrls = enEntries.map(entry => ({
url: `${baseUrl}/en${entry.slug}`,
changefreq: 'weekly',
priority: 0.6,
}));
return new Response([...zhUrls, ...enUrls], { status: 200 });
}
Trade-offs
| 优点 | 代价 |
|---|---|
| URL 结构清晰 | 内容需要维护两份 |
| SEO 友好 | 写作工作量翻倍 |
| 用户体验好 | 翻译质量难保证一致性 |
| Astro 内置支持 | 初次配置有复杂度 |
写在
双语支持不是技术问题,是内容策略问题。在开始之前问自己:你的读者真需要英文版本吗?
如果是为了 SEO,英文内容需要和中文内容一样丰富才有意义。半吊子翻译(中文内容 100 篇,英文内容 5 篇)对 SEO 没有帮助。
延伸阅读
COMMENTS
种下你的想法
在花园里留下一条评论,和这篇文章一起生长。