diff --git a/i18n_guide.md b/i18n_guide.md index c72670a..4c59a07 100644 --- a/i18n_guide.md +++ b/i18n_guide.md @@ -1,455 +1,335 @@ -# Astro + React 项目国际化 (i18n) 指南 +# Astro + React 项目国际化 (i18n) 实施步骤 -本文档旨在指导如何在基于 Astro 和 React 的项目中实现国际化功能,特别是针对 Astro 4.0+ 版本内置的 i18n 支持。 +本文档将严格按照 Astro 官方文档的推荐实践,指导您一步步为您的 Astro + React 项目添加国际化功能。请在完成每个主要步骤后,与指导者确认,然后再进行下一步。 -## 1. Astro i18n 配置 +## 基本配置与约定 -首先,需要在 `astro.config.mjs` 文件中配置国际化选项。 +- **默认语言**: `en` (英语) +- **支持语言**: `en` (英语), `zh` (简体中文) +- **URL 结构**: 不同语言的内容将通过 URL 子路径区分,例如 `example.com/en/` 和 `example.com/zh/`。 +- **翻译文件格式**: JSON -```javascript -// astro.config.mjs -import { defineConfig } from 'astro/config'; -import react from "@astrojs/react"; -import tailwind from "@tailwindcss/vite"; +## 基本术语 -export default defineConfig({ - integrations: [react(), tailwind()], - i18n: { - defaultLocale: "en", // 默认语言 - locales: ["en", "zh"], // 支持的语言列表 - routing: { - prefixDefaultLocale: true, // 是否为默认语言添加前缀 (例如 /en/blog) - // 其他路由配置,例如 fallback +- **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` 的文件,内容如下 0: + +```astro +--- +// src/pages/index.astro +// This page will redirect to the default language's home page. +--- + +``` + +**关键点** 0: + +* 这种 `meta refresh` 重定向方法适用于各种部署环境。 +* 确保您的默认语言首页(例如 `src/pages/en/index.astro`)已创建并包含实际内容。 + +**完成后,请与指导者确认,然后再进行下一步。** + +--- + +## 步骤 2:创建翻译文件 (UI Strings) + +此步骤的目标是为网站用户界面 (UI) 元素创建翻译字典。这些字典将存储不同语言的文本字符串。 + +### 2.1 创建 `ui.ts` 文件 + +根据 Astro 官方文档的建议,我们将在 `src/i18n/` 目录下创建一个 `ui.ts` 文件来存储翻译字符串 0。 + +1. 在 `src/` 目录下创建一个名为 `i18n` 的新文件夹。 +2. 在 `src/i18n/` 文件夹中创建一个名为 `ui.ts` 的文件。 + + ```tree + src/ + ├── i18n/ + │ └── ui.ts + └── pages/ + ... + ``` + +3. 将以下内容添加到 `src/i18n/ui.ts` 文件中 0: + + ```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; + ``` + +**关键点** 0: + +* `languages`: 一个对象,键是语言代码,值是该语言的人类可读名称。这对于构建语言切换器非常有用。 +* `defaultLang`: 指定项目的默认语言代码。 +* `ui`: 一个对象,其键是语言代码。每个语言代码下又是一个对象,包含该语言的翻译键和对应的翻译文本。 + * 我们使用点号 (`.`) 来组织翻译键的层级,例如 `nav.home`。 + * `as const` 用于确保 TypeScript 将这些对象视为常量,提供更好的类型推断和自动完成。 +* **请根据您项目中的实际 UI 文本,在 `ui.en` 和 `ui.zh` 对象中添加或修改相应的翻译键值对。** 例如,导航栏链接、页脚文本、按钮文本等。 + +**完成后,请与指导者确认,然后再进行下一步。** + +--- + +## 步骤 3:创建翻译辅助函数 + +此步骤的目标是创建一些辅助函数,以便在您的 Astro 页面和组件中更轻松地获取和使用翻译字符串。 + +### 3.1 创建 `utils.ts` 文件 + +我们将在 `src/i18n/` 目录下创建一个 `utils.ts` 文件来存放这些辅助函数 0。 + +1. 在 `src/i18n/` 文件夹中创建一个名为 `utils.ts` 的新文件。 + + ```tree + src/ + ├── i18n/ + │ ├── ui.ts + │ └── utils.ts + └── pages/ + ... + ``` + +2. 将以下内容添加到 `src/i18n/utils.ts` 文件中 0: + + ```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; } - } -}); -``` -**关键点**: -- `defaultLocale`: 设置网站的默认语言。 -- `locales`: 定义所有支持的语言。 -- `routing.prefixDefaultLocale`: 决定默认语言的 URL 是否也带有语言前缀。设置为 `true` (例如 `/en/about`) 通常能提供更一致的 URL 结构,但如果希望默认语言的 URL 更简洁 (例如 `/about`),可以设置为 `false`。 - -## 2. 组织多语言内容 (页面) - -Astro 的 i18n 路由策略是基于文件系统的。你需要根据语言在 `src/pages/` 目录下组织你的页面文件。 - -创建对应语言的子目录,例如: - -``` -src/ -└── pages/ - ├── en/ - │ ├── index.astro - │ └── about.astro - ├── zh/ - │ ├── index.astro - │ └── about.astro - └── index.astro // (可选) 如果 defaultLocale 不加前缀,这个作为根目录的默认语言页面 -``` - -- 如果 `prefixDefaultLocale` 为 `true`,则所有语言(包括默认语言)的页面都应放在各自的语言子目录中 (例如 `src/pages/en/index.astro`, `src/pages/zh/index.astro`)。 -- 如果 `prefixDefaultLocale` 为 `false`,默认语言的页面可以直接放在 `src/pages/` 根目录下 (例如 `src/pages/index.astro` 对应默认语言),其他语言的页面放在各自的子目录中 (例如 `src/pages/zh/index.astro`)。 - -## 3. 存储 UI 翻译文本 - -对于 UI 界面中需要翻译的文本(例如按钮文字、标签、提示信息等),建议使用 JSON 文件来管理。 - -在 `public/locales/` 目录下为每种语言创建一个 JSON 文件: - -``` -public/ -└── locales/ - ├── en/ - │ └── ui.json - └── zh/ - └── ui.json -``` - -**示例 `public/locales/en/ui.json`**: -```json -{ - "site.title": "My Portfolio", - "nav.home": "Home", - "nav.about": "About", - "nav.projects": "Projects", - "button.viewDetails": "View Details" -} -``` - -**示例 `public/locales/zh/ui.json`**: -```json -{ - "site.title": "我的作品集", - "nav.home": "首页", - "nav.about": "关于", - "nav.projects": "项目", - "button.viewDetails": "查看详情" -} -``` - -## 4. 创建翻译辅助函数 (可选但推荐) - -为了方便在 Astro 组件和 React 组件中获取翻译文本,可以创建一个辅助函数。 - -在 `src/i18n/utils.ts` (如果目录不存在则创建) 中: - -```typescript -// src/i18n/utils.ts -import { ui } from './ui'; // 假设 ui.ts 用于加载 JSON - -// 这是一个简化的示例,实际项目中可能需要更完善的加载和错误处理机制 -// 或者直接在组件中 fetch JSON 文件 - -export function useTranslations(lang: keyof typeof ui) { - return function t(key: keyof typeof ui[typeof lang]) { - return ui[lang][key] || key; // 如果翻译不存在,返回 key 本身 - } -} -``` - -你还需要一个 `src/i18n/ui.ts` 来实际加载这些 JSON 文件。Astro 官方文档提供了一种方式,但对于客户端 React 组件,可能需要调整。 - -一个更通用的方法是在组件中根据当前语言动态 `fetch` 对应的 `ui.json` 文件。 - -**或者,更简单的方式是直接在 Astro 页面/布局中获取当前语言,然后将翻译对象传递给 React 组件。** - -## 5. 更新 LanguageSwitcher 组件 - -`LanguageSwitcher.tsx` 组件需要能够检测当前语言、列出可选语言,并在用户选择新语言时导航到对应的本地化 URL。 - -```typescript -// src/components/LanguageSwitcher.tsx -import { useState, useEffect } from 'react'; - -// 假设我们从 Astro.locals.lang 获取当前语言,并通过 props 传入 -// 或者通过 window.location.pathname 解析 - -interface LanguageSwitcherProps { - currentLang: string; - availableLangs: Array<{ code: string; name: string }>; // 例如 [{ code: 'en', name: 'English' }, { code: 'zh', name: '中文' }] -} - -const LanguageSwitcher = ({ currentLang, availableLangs }: LanguageSwitcherProps) => { - const [selectedLang, setSelectedLang] = useState(currentLang); - - useEffect(() => { - setSelectedLang(currentLang); - }, [currentLang]); - - const handleChangeLanguage = (langCode: string) => { - if (langCode !== selectedLang) { - // 获取当前路径,但不包含语言前缀 - const currentPathname = window.location.pathname; - let basePath = ''; - - // 移除已有的语言前缀 (如果存在) - const langRegex = new RegExp(`^/(${availableLangs.map(l => l.code).join('|')})`); - basePath = currentPathname.replace(langRegex, ''); - if (!basePath.startsWith('/')) { - basePath = '/' + basePath; + export function useTranslations(lang: Lang | undefined) { + const currentLang = lang || defaultLang; + return function t(key: UiKeys, params?: Record): 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; } - - // Astro 的 i18n 路由会自动处理 - // 如果 prefixDefaultLocale 为 true,则所有语言都有前缀 - // 如果为 false,默认语言没有前缀 - // 这里我们假设 Astro 的配置会处理好目标 URL - // 对于 Astro 4.0+,可以直接使用 astro:i18n 的 getRelativeLocaleUrl - // 但在 React 组件中,我们通常通过 window.location.href 来跳转 - - // 简单的跳转逻辑,实际项目中可能需要更精确的 URL 构建 - // 考虑 Astro.config.mjs 中的 prefixDefaultLocale 设置 - const astroConfig = { - defaultLocale: 'en', // 需要与 astro.config.mjs 一致 - locales: ['en', 'zh'], // 需要与 astro.config.mjs 一致 - prefixDefaultLocale: true // 需要与 astro.config.mjs 一致 - }; - - let newPath; - if (langCode === astroConfig.defaultLocale && !astroConfig.prefixDefaultLocale) { - newPath = basePath; - } else { - newPath = `/${langCode}${basePath}`; - } - - // 确保路径是干净的,例如移除双斜杠 - newPath = newPath.replace(/\/\/+/g, '/'); - if (newPath === '') newPath = '/'; - - window.location.href = newPath; } - }; - return ( -
- -
- ); -}; + 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 }; + ``` -export default LanguageSwitcher; +**关键点** 0: -``` +* `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)` 函数用于获取特定键的翻译文本。 + * 它会首先尝试获取当前语言的翻译,如果找不到,则回退到默认语言的翻译。 + * 支持可选的 `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` 导入。 -**在 Astro 布局或页面中使用 `LanguageSwitcher`**: +**完成后,请与指导者确认,然后再进行下一步。** -```astro ---- -// src/layouts/Layout.astro -import LanguageSwitcher from '../components/LanguageSwitcher.tsx'; -import { getRelativeLocaleUrl } from 'astro:i18n'; // Astro 4.0+ i18n helper - -const { currentLocale } = Astro; -const availableLangs = [ - { code: 'en', name: 'English', url: getRelativeLocaleUrl('en') }, - { code: 'zh', name: '中文', url: getRelativeLocaleUrl('zh') }, - // ... 其他语言 -]; - -// 传递给 React 组件的 props -const langSwitcherProps = { - currentLang: currentLocale || 'en', // Astro.currentLocale 可能为 undefined - availableLangs: [ - { code: 'en', name: 'English' }, - { code: 'zh', name: '中文' }, - ] -}; --- - - - - - -
- -
- - - -``` -**注意**: `getRelativeLocaleUrl` 是在 Astro 文件中使用的。在 React 组件内部进行路由切换时,通常是直接修改 `window.location.href`。确保生成的 URL 与 Astro 的路由配置(特别是 `prefixDefaultLocale`)相匹配。 +## 步骤 4:在 Astro 页面和组件中使用翻译 -## 6. 国际化静态数据 (例如 `src/lib/data.ts`) +此步骤将演示如何在您的 Astro 页面 (`.astro` 文件) 和 React 组件 (`.tsx` 文件) 中使用我们创建的翻译辅助函数来显示本地化文本。 -对于 `src/lib/data.ts` 中的静态数据,有两种主要的处理方式: +### 4.1 在 Astro 页面中使用翻译 -**方式一:为每种语言创建单独的数据文件** +在您的 Astro 页面中,您可以导入并使用 `getLangFromUrl` 和 `useTranslations` 函数来获取当前语言并翻译文本。 -``` -src/ -└── lib/ - ├── data.en.ts - ├── data.zh.ts - └── data.ts // (可选) 作为默认或共享数据 -``` +**示例:修改 `src/pages/en/index.astro` 和 `src/pages/zh/index.astro`** -然后在你的 Astro 页面或组件中,根据当前语言动态导入对应的数据文件: - -```astro ---- -// src/pages/en/index.astro (或任何需要数据的页面) -import { personalData, experiences, projects } from '../../lib/data.en'; -// ... ---- -``` - -```astro ---- -// src/pages/zh/index.astro -import { personalData, experiences, projects } from '../../lib/data.zh'; -// ... ---- -``` - -**方式二:在同一个数据文件中使用嵌套结构** - -```typescript -// src/lib/data.ts -export const siteTitle = { - en: "My Awesome Portfolio", - zh: "我的炫酷作品集" -}; - -export const personalData = { - en: { - name: "Your Name", - tagline: "Full Stack Developer | Tech Enthusiast", - bio: "A short bio about yourself..." - }, - zh: { - name: "你的名字", - tagline: "全栈开发者 | 技术爱好者", - bio: "一段关于你自己的简短介绍..." - } -}; - -export const experiences = { - en: [ - { title: "Software Engineer at Tech Corp", duration: "2020 - Present", description: "Developed amazing things..." }, - ], - zh: [ - { title: "软件工程师 @ 科技公司", duration: "2020 -至今", description: "开发了很棒的东西..." }, - ] -}; -// ... 其他数据类似结构 -``` - -然后在组件中根据当前语言选择对应的数据: - -```typescript -// 在 React 组件中 (假设 lang 作为 prop 传入) -// const currentData = experiences[lang] || experiences.en; - -// 在 Astro 页面中 -// --- -// import { experiences } from '../lib/data'; -// const lang = Astro.currentLocale || 'en'; -// const currentExperiences = experiences[lang] || experiences.en; -// --- -``` - -选择哪种方式取决于你的偏好和数据复杂度。分离文件更清晰,但可能需要更多导入语句。嵌套结构更集中,但文件可能变得较大。 - -## 7. 在 Astro 页面和 React 组件中使用翻译 - -**Astro 页面/布局 (.astro)** - -你可以直接在 Astro 的 frontmatter 中获取当前语言,并加载对应的翻译文本。 +假设您的首页需要显示一个欢迎标题。您可以像这样修改您的语言特定首页文件: +**`src/pages/en/index.astro`** ```astro --- -// src/pages/en/index.astro import Layout from '../../layouts/Layout.astro'; -import HeroSection from '../../components/HeroSection.tsx'; -// 假设我们有一个方法来加载翻译 -import { getTranslations } from '../../i18n/server'; // 这是一个假设的服务器端翻译加载函数 +import { getLangFromUrl, useTranslations } from '../../i18n/utils'; -const lang = Astro.currentLocale || 'en'; -const t = await getTranslations(lang, 'ui'); // 加载 public/locales/en/ui.json - -const heroSectionProps = { - title: t['hero.title'], // 假设 ui.json 中有 hero.title - subtitle: t['hero.subtitle'] - // ... 其他需要翻译的 props -}; +const lang = getLangFromUrl(Astro.url); +const t = useTranslations(lang); --- - - - {/* ...其他内容... */} + +

{t('nav.home')}

+

Welcome to the English version of the site!

+

{t('footer.rights')}

``` -**React 组件 (.tsx)** +**`src/pages/zh/index.astro`** +```astro +--- +import Layout from '../../layouts/Layout.astro'; +import { getLangFromUrl, useTranslations } from '../../i18n/utils'; -将翻译函数 `t` 或翻译好的文本作为 props 传递给 React 组件。 - -```typescript -// src/components/HeroSection.tsx -interface HeroSectionProps { - title: string; - subtitle: string; - // ...其他 props -} - -const HeroSection = ({ title, subtitle }: HeroSectionProps) => { - return ( -
-

{title}

-

{subtitle}

-
- ); -}; - -export default HeroSection; +const lang = getLangFromUrl(Astro.url); +const t = useTranslations(lang); +--- + +

{t('nav.home')}

+

欢迎来到本站的中文版本!

+

{t('footer.rights')}

+
``` -或者,如果 React 组件需要在客户端自行获取翻译(例如,不通过 Astro 预渲染的部分),可以 `fetch` 对应的 `ui.json` 文件。 +**关键点** 0: -```typescript -// src/components/SomeClientComponent.tsx -import { useState, useEffect } from 'react'; +* 我们从 `../../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` 属性,例如 ``。 -interface Translations { - [key: string]: string; -} +### 4.2 在 React 组件中使用翻译 -const SomeClientComponent = () => { - const [t, setT] = useState< (key: string) => string >(() => (key: string) => key); - const [lang, setLang] = useState('en'); // 需要确定当前语言 +要在 React 组件中使用翻译,您需要将翻译函数 `t` 和当前语言 `lang` 作为 props 传递给组件。 - useEffect(() => { - // 确定当前语言,例如从 URL 或 localStorage - const currentPathLang = window.location.pathname.split('/')[1]; - // 假设 'en', 'zh' 是支持的语言 - if (['en', 'zh'].includes(currentPathLang)) { - setLang(currentPathLang); +**示例:创建一个简单的 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; } - }, []); - useEffect(() => { - async function fetchTranslations() { - try { - const response = await fetch(`/locales/${lang}/ui.json`); - if (!response.ok) throw new Error('Failed to load translations'); - const translations: Translations = await response.json(); - setT(() => (key: string) => translations[key] || key); - } catch (error) { - console.error("Error fetching translations:", error); - // 保留默认的 t 函数 (返回 key) - } + export default function Footer({ lang, t }: FooterProps) { + return ( +
+

{t('footer.rights')}

+

Current language: {lang}

+
+ ); } - fetchTranslations(); - }, [lang]); + ``` - return ( -
-

{t('some.dynamic.text')}

-
- ); -}; +3. 在您的 Astro 页面中使用此组件,例如在 `src/pages/en/index.astro` 中: -export default SomeClientComponent; -``` + ```astro + --- + import Layout from '../../layouts/Layout.astro'; + import { getLangFromUrl, useTranslations } from '../../i18n/utils'; + import Footer from '../../components/Footer.tsx'; // 导入 React 组件 -## 8. 重要注意事项和目标 + const lang = getLangFromUrl(Astro.url); + const t = useTranslations(lang); + --- + +

{t('nav.home')}

+

Welcome to the English version of the site!

+ +