添加 i18n 功能
在本指南中,你将学习如何使用内容集合和动态路由来构建你自己的国际化 (i18n) 解决方案,并以不同语言提供你的内容。
在 v4.0 中,Astro 添加了对 i18n 路由的内置支持,允许你配置默认语言和支持的语言,并包含有价值的辅助函数来帮助你为国际用户服务。如果你想改用此功能,请参阅我们的国际化指南以了解这些功能。
本示例在各自的子路径下提供每种语言,例如英语为 example.com/en/blog
,法语为 example.com/fr/blog
。
如果你希望默认语言不像其他语言一样在 URL 中可见,下面有隐藏默认语言的说明。
为每种语言设置页面
标题为“为每种语言设置页面”的部分-
为你想要支持的每种语言创建一个目录。例如,如果你支持英语和法语,则创建
en/
和fr/
。目录src/
目录pages/
目录en/
- about.astro
- index.astro
目录fr/
- about.astro
- index.astro
- index.astro
-
设置
src/pages/index.astro
以重定向到你的默认语言。src/pages/index.astro <meta http-equiv="refresh" content="0;url=/en/" />这种方法使用 meta refresh,无论你如何部署网站都能工作。一些静态主机也允许你使用自定义配置文件来配置服务器重定向。更多详情请参阅你的部署平台的文档。
如果你正在使用 SSR 适配器,你可以使用
Astro.redirect
在服务器上重定向到默认语言。src/pages/index.astro ---return Astro.redirect('/en/');---
使用集合管理翻译内容
标题为“使用集合管理翻译内容”的部分-
在
src/content/
中为每种要包含的内容类型创建一个文件夹,并为每种支持的语言添加子目录。例如,要支持英语和法语的博客文章:目录src/
目录content/
目录blog/
目录en/ 英文博客文章
- post-1.md
- post-2.md
目录fr/ 法文博客文章
- post-1.md
- post-2.md
-
创建一个
src/content.config.ts
文件,并为每种内容类型导出一个集合。src/content.config.ts import { defineCollection, z } from 'astro:content';const blogCollection = defineCollection({schema: z.object({title: z.string(),author: z.string(),date: z.date()})});export const collections = {'blog': blogCollection};阅读更多关于内容集合的信息。 -
使用动态路由根据
lang
和slug
参数获取和渲染内容。在静态渲染模式下,使用
getStaticPaths
将每个内容条目映射到一个页面:src/pages/[lang]/blog/[...slug].astro ---import { getCollection, render } from 'astro:content';export async function getStaticPaths() {const pages = await getCollection('blog');const paths = pages.map(page => {const [lang, ...slug] = page.id.split('/');return { params: { lang, slug: slug.join('/') || undefined }, props: page };});return paths;}const { lang, slug } = Astro.params;const page = Astro.props;const formattedDate = page.data.date.toLocaleString(lang);const { Content } = await render(page);---<h1>{page.data.title}</h1><p>by {page.data.author} • {formattedDate}</p><Content/>在SSR 模式下,直接获取请求的条目:
src/pages/[lang]/blog/[...slug].astro ---import { getEntry, render } from 'astro:content';const { lang, slug } = Astro.params;const page = await getEntry('blog', `${lang}/${slug}`);if (!page) {return Astro.redirect('/404');}const formattedDate = page.data.date.toLocaleString(lang);const { Content, headings } = await render(page);---<h1>{page.data.title}</h1><p>by {page.data.author} • {formattedDate}</p><Content/>阅读更多关于动态路由的信息。上面的例子使用了内置的
toLocaleString()
日期格式化方法,从 frontmatter 日期创建一个人类可读的字符串。这确保了日期和时间的格式与用户的语言相匹配。
翻译 UI 字符串
标题为“翻译 UI 字符串”的部分创建术语词典来翻译你网站上 UI 元素的标签。这能让你的访客以他们的语言完整地体验你的网站。
-
创建一个
src/i18n/ui.ts
文件来存储你的翻译字符串:src/i18n/ui.ts export const languages = {en: 'English',fr: 'Français',};export const defaultLang = 'en';export const ui = {en: {'nav.home': 'Home','nav.about': 'About','nav.twitter': 'Twitter',},fr: {'nav.home': 'Accueil','nav.about': 'À propos',},} as const; -
创建两个辅助函数:一个用于根据当前 URL 检测页面语言,另一个用于在
src/i18n/utils.ts
中获取 UI 不同部分的翻译字符串:src/i18n/utils.ts import { ui, defaultLang } from './ui';export function getLangFromUrl(url: URL) {const [, lang] = url.pathname.split('/');if (lang in ui) return lang as keyof typeof ui;return defaultLang;}export function useTranslations(lang: keyof typeof ui) {return function t(key: keyof typeof ui[typeof defaultLang]) {return ui[lang][key] || ui[defaultLang][key];}}在第 1 步中,
nav.twitter
字符串没有被翻译成法语。你可能不希望翻译每个术语,比如专有名词或常见的行业术语。useTranslations
辅助函数在键未被翻译时将返回默认语言的值。在本例中,法语用户也将在导航栏中看到“Twitter”。 -
在需要的地方导入辅助函数,并使用它们来选择与当前语言对应的 UI 字符串。例如,一个导航组件可能看起来像这样:
src/components/Nav.astro ---import { getLangFromUrl, useTranslations } from '../i18n/utils';const lang = getLangFromUrl(Astro.url);const t = useTranslations(lang);---<ul><li><a href={`/${lang}/home/`}>{t('nav.home')}</a></li><li><a href={`/${lang}/about/`}>{t('nav.about')}</a></li><li><a href="https://twitter.com/astrodotbuild">{t('nav.twitter')}</a></li></ul> -
每个页面必须在
<html>
元素上有一个与页面语言匹配的lang
属性。在这个例子中,一个可重用的布局从当前路由中提取语言:src/layouts/Base.astro ---import { getLangFromUrl } from '../i18n/utils';const lang = getLangFromUrl(Astro.url);---<html lang={lang}><head><meta charset="utf-8" /><link rel="icon" type="image/svg+xml" href="/favicon.svg" /><meta name="viewport" content="width=device-width" /><title>Astro</title></head><body><slot /></body></html>然后,你可以使用这个基础布局来确保页面自动使用正确的
lang
属性。src/pages/en/about.astro ---import Base from '../../layouts/Base.astro';---<Base><h1>About me</h1>...</Base>
让用户切换语言
标题为“让用户切换语言”的部分创建指向你支持的不同语言的链接,以便用户可以选择他们想要阅读你网站的语言。
-
创建一个组件来为每种语言显示一个链接:
src/components/LanguagePicker.astro ---import { languages } from '../i18n/ui';---<ul>{Object.entries(languages).map(([lang, label]) => (<li><a href={`/${lang}/`}>{label}</a></li>))}</ul> -
将
<LanguagePicker />
添加到你的网站,使其在每个页面上都显示。下面的例子将其添加到一个基础布局的网站页脚中:src/layouts/Base.astro ---import LanguagePicker from '../components/LanguagePicker.astro';import { getLangFromUrl } from '../i18n/utils';const lang = getLangFromUrl(Astro.url);---<html lang={lang}><head><meta charset="utf-8" /><link rel="icon" type="image/svg+xml" href="/favicon.svg" /><meta name="viewport" content="width=device-width" /><title>Astro</title></head><body><slot /><footer><LanguagePicker /></footer></body></html>
在 URL 中隐藏默认语言
标题为“在 URL 中隐藏默认语言”的部分-
为除默认语言外的每种语言创建一个目录。例如,将你的默认语言页面直接存储在
pages/
中,并将翻译后的页面存储在fr/
中:目录src/
目录pages/
- about.astro
- index.astro
目录fr/
- about.astro
- index.astro
-
向
src/i18n/ui.ts
文件添加另一行以切换该功能:src/i18n/ui.ts export const showDefaultLang = false; -
向
src/i18n/utils.ts
添加一个辅助函数,以根据当前语言翻译路径:src/i18n/utils.ts import { ui, defaultLang, showDefaultLang } from './ui';export function useTranslatedPath(lang: keyof typeof ui) {return function translatePath(path: string, l: string = lang) {return !showDefaultLang && l === defaultLang ? path : `/${l}${path}`}} -
在需要的地方导入该辅助函数。例如,一个
nav
组件可能看起来像这样:src/components/Nav.astro ---import { getLangFromUrl, useTranslations, useTranslatedPath } from '../i18n/utils';const lang = getLangFromUrl(Astro.url);const t = useTranslations(lang);const translatePath = useTranslatedPath(lang);---<ul><li><a href={translatePath('/home/')}>{t('nav.home')}</a></li><li><a href={translatePath('/about/')}>{t('nav.about')}</a></li><li><a href="https://twitter.com/astrodotbuild">{t('nav.twitter')}</a></li></ul> -
该辅助函数也可以用于为特定语言翻译路径。例如,当用户在不同语言之间切换时:
src/components/LanguagePicker.astro ---import { languages } from '../i18n/ui';import { getLangFromUrl, useTranslatedPath } from '../i18n/utils';const lang = getLangFromUrl(Astro.url);const translatePath = useTranslatedPath(lang);---<ul>{Object.entries(languages).map(([lang, label]) => (<li><a href={translatePath('/', lang)}>{label}</a></li>))}</ul>
翻译路由
标题为“翻译路由”的部分为每种语言翻译你页面的路由。
-
将路由映射添加到
src/i18n/ui.ts
:src/i18n/ui.ts export const routes = {de: {'services': 'leistungen',},fr: {'services': 'prestations-de-service',},} -
更新
src/i18n/utils.ts
中的useTranslatedPath
辅助函数以添加路由翻译逻辑。src/i18n/utils.ts import { ui, defaultLang, showDefaultLang, routes } from './ui';export function useTranslatedPath(lang: keyof typeof ui) {return function translatePath(path: string, l: string = lang) {const pathName = path.replaceAll('/', '')const hasTranslation = defaultLang !== l && routes[l] !== undefined && routes[l][pathName] !== undefinedconst translatedPath = hasTranslation ? '/' + routes[l][pathName] : pathreturn !showDefaultLang && l === defaultLang ? translatedPath : `/${l}${translatedPath}`}} -
在
src/i18n/utils.ts
中创建一个辅助函数,以根据当前 URL 获取路由(如果存在):src/i18n/utils.ts import { ui, defaultLang, showDefaultLang, routes } from './ui';export function getRouteFromUrl(url: URL): string | undefined {const pathname = new URL(url).pathname;const parts = pathname?.split('/');const path = parts.pop() || parts.pop();if (path === undefined) {return undefined;}const currentLang = getLangFromUrl(url);if (defaultLang === currentLang) {const route = Object.values(routes)[0];return route[path] !== undefined ? route[path] : undefined;}const getKeyByValue = (obj: Record<string, string>, value: string): string | undefined => {return Object.keys(obj).find((key) => obj[key] === value);}const reversedKey = getKeyByValue(routes[currentLang], path);if (reversedKey !== undefined) {return reversedKey;}return undefined;} -
该辅助函数可用于获取翻译后的路由。例如,当没有定义翻译路由时,用户将被重定向到主页:
src/components/LanguagePicker.astro ---import { languages } from '../i18n/ui';import { getRouteFromUrl, useTranslatedPath } from '../i18n/utils';const route = getRouteFromUrl(Astro.url);---<ul>{Object.entries(languages).map(([lang, label]) => {const translatePath = useTranslatedPath(lang);return (<li><a href={translatePath(`/${route ? route : ''}`)}>{label}</a></li>)})}</ul>
相关资源
标题为“资源”的部分社区库
标题为“社区库”的部分- astro-i18next — 一个用于 i18next 的 Astro 集成,包含一些实用组件。
- astro-i18n — 一个为 Astro 打造的 TypeScript 优先的国际化库。
- astro-i18n-aut — 一个支持在不生成页面的情况下使用
defaultLocale
的 Astro i18n 集成。该集成与适配器和 UI 框架无关。 - astro-react-i18next — 一个 Astro 集成,可在 Astro 网站的 React 组件中无缝使用 i18next 和 react-i18next。
- paraglide — 一个完全类型安全的 i18n 库,专为像 Astro islands 这样的局部水合模式设计。
- astro-loader-i18n — 一个用于 i18n 文件和文件夹结构的 Astro glob 内容加载器,支持路由翻译。