Relocate the internationalization guide from project root to .dev_docs folder for better documentation organization
335 lines
14 KiB
Markdown
335 lines
14 KiB
Markdown
# Astro + React 项目国际化 (i18n) 实施步骤
|
||
|
||
本文档将严格按照 Astro 官方文档的推荐实践,指导您一步步为您的 Astro + React 项目添加国际化功能。请在完成每个主要步骤后,与指导者确认,然后再进行下一步。
|
||
|
||
## 基本配置与约定
|
||
|
||
- **默认语言**: `en` (英语)
|
||
- **支持语言**: `en` (英语), `zh` (简体中文)
|
||
- **URL 结构**: 不同语言的内容将通过 URL 子路径区分,例如 `example.com/en/` 和 `example.com/zh/`。
|
||
- **翻译文件格式**: JSON
|
||
|
||
## 基本术语
|
||
|
||
- **i18n**: Internationalization 的缩写,意为国际化。
|
||
- **L10n**: Localization 的缩写,意为本地化。
|
||
- **UI Strings**: 用户界面中需要翻译的文本,例如按钮标签、导航链接文本等。
|
||
|
||
---
|
||
|
||
## 步骤 1:配置 Astro i18n 并组织页面
|
||
|
||
此步骤的目标是配置 Astro 以支持多种语言,并相应地组织您的页面文件结构。
|
||
|
||
### 1.1 配置 `astro.config.mjs`
|
||
|
||
您需要在 `astro.config.mjs` 文件中定义国际化相关的配置。虽然 Astro 的核心 i18n 功能不直接在 `defineConfig` 中指定 `locales` 或 `defaultLocale` (这些通常是集成特定 i18n 库或中间件时的配置),但我们将遵循一种通用的模式,即通过目录结构来管理多语言内容。
|
||
|
||
目前,我们不需要修改 `astro.config.mjs` 来显式声明语言,Astro 会根据 `src/pages/` 下的目录结构自动处理多语言路由。
|
||
|
||
### 1.2 组织多语言页面目录
|
||
|
||
1. 在 `src/pages/` 目录下,为项目支持的每种语言创建一个子目录。根据我们的约定,您需要创建以下目录:
|
||
* `src/pages/en/`
|
||
* `src/pages/zh/`
|
||
|
||
2. 将您现有的页面文件(例如 `index.astro`, `about.astro` 等)复制到每个语言目录下。例如:
|
||
|
||
```tree
|
||
src/
|
||
└── pages/
|
||
├── en/
|
||
│ ├── index.astro
|
||
│ └── about.astro (如果存在)
|
||
├── zh/
|
||
│ ├── index.astro
|
||
│ └── about.astro (如果存在)
|
||
└── index.astro (根目录的索引页,将用于重定向)
|
||
```
|
||
|
||
### 1.3 设置默认语言重定向
|
||
|
||
为了让访问网站根路径 (`/`) 的用户自动跳转到默认语言(我们约定为英语 `en`)的首页,请修改位于 `src/pages/index.astro` 的文件,内容如下 <mcreference index="0" link="https://docs.astro.build/zh-cn/recipes/i18n/">0</mcreference>:
|
||
|
||
```astro
|
||
---
|
||
// src/pages/index.astro
|
||
// This page will redirect to the default language's home page.
|
||
---
|
||
<meta http-equiv="refresh" content="0;url=/en/" />
|
||
```
|
||
|
||
**关键点** <mcreference index="0" link="https://docs.astro.build/zh-cn/recipes/i18n/">0</mcreference>:
|
||
|
||
* 这种 `meta refresh` 重定向方法适用于各种部署环境。
|
||
* 确保您的默认语言首页(例如 `src/pages/en/index.astro`)已创建并包含实际内容。
|
||
|
||
**完成后,请与指导者确认,然后再进行下一步。**
|
||
|
||
---
|
||
|
||
## 步骤 2:创建翻译文件 (UI Strings)
|
||
|
||
此步骤的目标是为网站用户界面 (UI) 元素创建翻译字典。这些字典将存储不同语言的文本字符串。
|
||
|
||
### 2.1 创建 `ui.ts` 文件
|
||
|
||
根据 Astro 官方文档的建议,我们将在 `src/i18n/` 目录下创建一个 `ui.ts` 文件来存储翻译字符串 <mcreference index="0" link="https://docs.astro.build/zh-cn/recipes/i18n/">0</mcreference>。
|
||
|
||
1. 在 `src/` 目录下创建一个名为 `i18n` 的新文件夹。
|
||
2. 在 `src/i18n/` 文件夹中创建一个名为 `ui.ts` 的文件。
|
||
|
||
```tree
|
||
src/
|
||
├── i18n/
|
||
│ └── ui.ts
|
||
└── pages/
|
||
...
|
||
```
|
||
|
||
3. 将以下内容添加到 `src/i18n/ui.ts` 文件中 <mcreference index="0" link="https://docs.astro.build/zh-cn/recipes/i18n/">0</mcreference>:
|
||
|
||
```typescript
|
||
// src/i18n/ui.ts
|
||
export const languages = {
|
||
en: 'English',
|
||
zh: '简体中文',
|
||
} as const;
|
||
|
||
export const defaultLang = 'en';
|
||
|
||
export const ui = {
|
||
en: {
|
||
'nav.home': 'Home',
|
||
'nav.projects': 'Projects',
|
||
'nav.experience': 'Experience',
|
||
'nav.skills': 'Skills',
|
||
'nav.awards': 'Awards',
|
||
'nav.education': 'Education',
|
||
'footer.rights': 'All rights reserved.',
|
||
// 根据您的项目实际情况添加更多翻译键值对
|
||
},
|
||
zh: {
|
||
'nav.home': '首页',
|
||
'nav.projects': '项目经历',
|
||
'nav.experience': '工作经历',
|
||
'nav.skills': '专业技能',
|
||
'nav.awards': '奖项荣誉',
|
||
'nav.education': '教育背景',
|
||
'footer.rights': '版权所有。',
|
||
// 根据您的项目实际情况添加更多翻译键值对
|
||
},
|
||
} as const;
|
||
```
|
||
|
||
**关键点** <mcreference index="0" link="https://docs.astro.build/zh-cn/recipes/i18n/">0</mcreference>:
|
||
|
||
* `languages`: 一个对象,键是语言代码,值是该语言的人类可读名称。这对于构建语言切换器非常有用。
|
||
* `defaultLang`: 指定项目的默认语言代码。
|
||
* `ui`: 一个对象,其键是语言代码。每个语言代码下又是一个对象,包含该语言的翻译键和对应的翻译文本。
|
||
* 我们使用点号 (`.`) 来组织翻译键的层级,例如 `nav.home`。
|
||
* `as const` 用于确保 TypeScript 将这些对象视为常量,提供更好的类型推断和自动完成。
|
||
* **请根据您项目中的实际 UI 文本,在 `ui.en` 和 `ui.zh` 对象中添加或修改相应的翻译键值对。** 例如,导航栏链接、页脚文本、按钮文本等。
|
||
|
||
**完成后,请与指导者确认,然后再进行下一步。**
|
||
|
||
---
|
||
|
||
## 步骤 3:创建翻译辅助函数
|
||
|
||
此步骤的目标是创建一些辅助函数,以便在您的 Astro 页面和组件中更轻松地获取和使用翻译字符串。
|
||
|
||
### 3.1 创建 `utils.ts` 文件
|
||
|
||
我们将在 `src/i18n/` 目录下创建一个 `utils.ts` 文件来存放这些辅助函数 <mcreference index="0" link="https://docs.astro.build/zh-cn/recipes/i18n/">0</mcreference>。
|
||
|
||
1. 在 `src/i18n/` 文件夹中创建一个名为 `utils.ts` 的新文件。
|
||
|
||
```tree
|
||
src/
|
||
├── i18n/
|
||
│ ├── ui.ts
|
||
│ └── utils.ts
|
||
└── pages/
|
||
...
|
||
```
|
||
|
||
2. 将以下内容添加到 `src/i18n/utils.ts` 文件中 <mcreference index="0" link="https://docs.astro.build/zh-cn/recipes/i18n/">0</mcreference>:
|
||
|
||
```typescript
|
||
// src/i18n/utils.ts
|
||
import { ui, defaultLang, languages } from './ui';
|
||
|
||
export type Lang = keyof typeof ui;
|
||
export type UiKeys = keyof typeof ui[typeof defaultLang];
|
||
|
||
export function getLangFromUrl(url: URL): Lang {
|
||
const [, lang] = url.pathname.split('/');
|
||
if (lang in ui) return lang as Lang;
|
||
return defaultLang;
|
||
}
|
||
|
||
export function useTranslations(lang: Lang | undefined) {
|
||
const currentLang = lang || defaultLang;
|
||
return function t(key: UiKeys, params?: Record<string, string | number>): string {
|
||
let translation = ui[currentLang][key] || ui[defaultLang][key];
|
||
if (params) {
|
||
Object.keys(params).forEach(paramKey => {
|
||
const regex = new RegExp(`\\{\\s*${paramKey}\\s*\\}`, 'g');
|
||
translation = translation.replace(regex, String(params[paramKey]));
|
||
});
|
||
}
|
||
return translation;
|
||
}
|
||
}
|
||
|
||
export function getLocalizedPath(path: string, lang: Lang | undefined, base: string | URL = import.meta.env.BASE_URL): string {
|
||
const currentLang = lang || defaultLang;
|
||
const baseWithSlash = typeof base === 'string' && !base.endsWith('/') ? `${base}/` : String(base);
|
||
const langPath = currentLang === defaultLang ? '' : `${currentLang}/`;
|
||
const normalizedPath = path.startsWith('/') ? path.substring(1) : path;
|
||
return `${baseWithSlash}${langPath}${normalizedPath}`.replace(/\/\/+$/, '/'); // Ensure no double slashes at the end
|
||
}
|
||
|
||
export { languages, defaultLang };
|
||
```
|
||
|
||
**关键点** <mcreference index="0" link="https://docs.astro.build/zh-cn/recipes/i18n/">0</mcreference>:
|
||
|
||
* `Lang` 和 `UiKeys`: TypeScript 类型定义,用于增强代码的类型安全。
|
||
* `Lang`: 表示支持的语言代码的类型 (例如 `'en' | 'zh'`)。
|
||
* `UiKeys`: 表示翻译字典中所有键的类型 (例如 `'nav.home' | 'nav.projects'`)。
|
||
* `getLangFromUrl(url: URL)`: 一个函数,用于从当前页面的 URL 中提取语言代码。如果 URL 中没有指定语言或者指定的语言无效,则返回默认语言。
|
||
* `useTranslations(lang: Lang | undefined)`: 一个高阶函数,它接收一个语言代码作为参数,并返回另一个函数 `t`。
|
||
* 返回的 `t(key: UiKeys, params?: Record<string, string | number>)` 函数用于获取特定键的翻译文本。
|
||
* 它会首先尝试获取当前语言的翻译,如果找不到,则回退到默认语言的翻译。
|
||
* 支持可选的 `params` 参数,用于在翻译字符串中插入动态值。例如,如果翻译字符串是 `Hello {name}`,你可以通过 `t('greeting', { name: 'World' })` 来得到 `Hello World`。
|
||
* `getLocalizedPath(path: string, lang: Lang | undefined, base: string | URL = import.meta.env.BASE_URL)`: 一个函数,用于生成本地化的 URL 路径。它会根据传入的语言和基础路径,自动添加语言子路径(如果不是默认语言)。
|
||
* `export { languages, defaultLang };`: 重新导出了 `ui.ts` 中的 `languages` 和 `defaultLang`,方便在其他地方统一从 `utils.ts` 导入。
|
||
|
||
**完成后,请与指导者确认,然后再进行下一步。**
|
||
|
||
---
|
||
|
||
## 步骤 4:在 Astro 页面和组件中使用翻译
|
||
|
||
此步骤将演示如何在您的 Astro 页面 (`.astro` 文件) 和 React 组件 (`.tsx` 文件) 中使用我们创建的翻译辅助函数来显示本地化文本。
|
||
|
||
### 4.1 在 Astro 页面中使用翻译
|
||
|
||
在您的 Astro 页面中,您可以导入并使用 `getLangFromUrl` 和 `useTranslations` 函数来获取当前语言并翻译文本。
|
||
|
||
**示例:修改 `src/pages/en/index.astro` 和 `src/pages/zh/index.astro`**
|
||
|
||
假设您的首页需要显示一个欢迎标题。您可以像这样修改您的语言特定首页文件:
|
||
|
||
**`src/pages/en/index.astro`**
|
||
```astro
|
||
---
|
||
import Layout from '../../layouts/Layout.astro';
|
||
import { getLangFromUrl, useTranslations } from '../../i18n/utils';
|
||
|
||
const lang = getLangFromUrl(Astro.url);
|
||
const t = useTranslations(lang);
|
||
---
|
||
<Layout title={t('nav.home')} lang={lang}>
|
||
<h1>{t('nav.home')}</h1>
|
||
<p>Welcome to the English version of the site!</p>
|
||
<p>{t('footer.rights')}</p>
|
||
</Layout>
|
||
```
|
||
|
||
**`src/pages/zh/index.astro`**
|
||
```astro
|
||
---
|
||
import Layout from '../../layouts/Layout.astro';
|
||
import { getLangFromUrl, useTranslations } from '../../i18n/utils';
|
||
|
||
const lang = getLangFromUrl(Astro.url);
|
||
const t = useTranslations(lang);
|
||
---
|
||
<Layout title={t('nav.home')} lang={lang}>
|
||
<h1>{t('nav.home')}</h1>
|
||
<p>欢迎来到本站的中文版本!</p>
|
||
<p>{t('footer.rights')}</p>
|
||
</Layout>
|
||
```
|
||
|
||
**关键点** <mcreference index="0" link="https://docs.astro.build/zh-cn/recipes/i18n/">0</mcreference>:
|
||
|
||
* 我们从 `../../i18n/utils` 导入了 `getLangFromUrl` 和 `useTranslations`。
|
||
* `getLangFromUrl(Astro.url)` 用于从当前页面的 URL 获取语言代码。
|
||
* `useTranslations(lang)` 返回了翻译函数 `t`。
|
||
* 我们使用 `t('key_name')` 来获取翻译后的字符串,例如 `t('nav.home')` 和 `t('footer.rights')`。
|
||
* 确保您的 `Layout.astro` 组件能够接收并使用 `lang` prop 来设置 HTML 的 `lang` 属性,例如 `<html lang={Astro.props.lang}>`。
|
||
|
||
### 4.2 在 React 组件中使用翻译
|
||
|
||
要在 React 组件中使用翻译,您需要将翻译函数 `t` 和当前语言 `lang` 作为 props 传递给组件。
|
||
|
||
**示例:创建一个简单的 React 页脚组件 `src/components/Footer.tsx`**
|
||
|
||
1. 创建文件 `src/components/Footer.tsx`:
|
||
|
||
```tree
|
||
src/
|
||
├── components/
|
||
│ └── Footer.tsx
|
||
├── i18n/
|
||
│ ...
|
||
└── pages/
|
||
...
|
||
```
|
||
|
||
2. 将以下内容添加到 `src/components/Footer.tsx`:
|
||
|
||
```tsx
|
||
// src/components/Footer.tsx
|
||
import type { Lang, UiKeys } from '../i18n/utils'; // 导入类型
|
||
|
||
interface FooterProps {
|
||
lang: Lang;
|
||
t: (key: UiKeys, params?: Record<string, string | number>) => string;
|
||
}
|
||
|
||
export default function Footer({ lang, t }: FooterProps) {
|
||
return (
|
||
<footer>
|
||
<p>{t('footer.rights')}</p>
|
||
<p>Current language: {lang}</p>
|
||
</footer>
|
||
);
|
||
}
|
||
```
|
||
|
||
3. 在您的 Astro 页面中使用此组件,例如在 `src/pages/en/index.astro` 中:
|
||
|
||
```astro
|
||
---
|
||
import Layout from '../../layouts/Layout.astro';
|
||
import { getLangFromUrl, useTranslations } from '../../i18n/utils';
|
||
import Footer from '../../components/Footer.tsx'; // 导入 React 组件
|
||
|
||
const lang = getLangFromUrl(Astro.url);
|
||
const t = useTranslations(lang);
|
||
---
|
||
<Layout title={t('nav.home')} lang={lang}>
|
||
<h1>{t('nav.home')}</h1>
|
||
<p>Welcome to the English version of the site!</p>
|
||
|
||
<Footer lang={lang} t={t} client:visible />
|
||
</Layout>
|
||
```
|
||
|
||
**关键点**:
|
||
|
||
* 我们为 `FooterProps` 定义了接口,明确了 `lang` 和 `t` 的类型。
|
||
* `lang` 和 `t` 函数从 Astro 页面作为 props 传递给 `Footer` 组件。
|
||
* 在 React 组件内部,我们直接使用传递进来的 `t` 函数进行翻译。
|
||
* `client:visible` (或其他 Astro 客户端指令) 是必需的,以使 React 组件在客户端进行交互式渲染。
|
||
|
||
**请根据您的项目结构和需求,在相应的 Astro 页面和 React 组件中应用这些翻译方法。**
|
||
|
||
**完成后,请与指导者确认,然后再进行下一步。**
|
||
|
||
--- |