我想要我的應用程式有以下的路由邏輯:
- URL 前綴表示語言 (
fr、en或zh等),例如/fr/about、/en/about等 - 基於內容合集的動態 URL,例如
/fr/resources/my-resource、/zh/articles/my-article等 - 如果 URL 中沒有指定語言,則自動重新導向到用戶的語言(依照瀏覽器組態的語言)
組態
Astro 應用程式中的多語言整合非常簡單。
主組態檔案中可以寫:
// astro.config.mjs
export default defineConfig({
i18n: {
locales: [
{ path: "fr", codes: ["fr", "fr-FR", "fr-CA"] },
{ path: "en", codes: ["en", "en-US", "en-GB"] },
{ path: "zh", codes: ["zh", "zh-CN", "zh-TW"] },
],
defaultLocale: "fr",
routing: {
prefixDefaultLocale: true,
redirectToDefaultLocale: false,
},
},
//...
});
-
每個語言(
path)的codes用於定義支援的語言,這些語言必須跟瀏覽器標頭 Accept-Language 的語法相容的 -
prefixDefaultLocale定義 URL 中一直涵蓋語言的前綴,例如https://domain.com/fr/page。 -
redirectToDefaultLocale: false令我們手動處理用戶到達首頁時正確的語言轉址。
國際化檔案
按照 實用指南 中的建議,我們將創造以下檔案:
src/i18n/ui.ts: 定義我們的應用程式支援的語言並介面中顯示翻譯的字串(功能表、頁尾等)
export const languages = {
fr: "Français",
en: "English",
zh: "繁體中文",
};
export const ui = {
fr: {
"nav.home": "Accueil",
"nav.about": "À propos",
},
en: {
"nav.home": "Home",
"nav.about": "About",
},
zh: {
//...
},
} as const;
接下來,按照實用指南,我們還將建立一些函式,使我們能夠檢索國際化字串(src/i18n/utils.ts 中的 useTranslation)。
瀏覽器語言檢測
當用戶到達網站的首頁時,如果 URL 中沒有定義語言,我們會轉址到用戶偏好的語言。負責處理這個邏輯的網頁就是網站的首頁(/:根網頁)。
如果我們的應用程式不支援瀏覽器組態的語言,我們就會顯示預設語言(組態中定義的 defaultLocale)。用戶可以選擇他們偏好的語言,使用介面中的語言選擇器。
首頁(沒有定義語言代碼)的範例:
---
// src/pages/index.astro
import { i18n } from "astro:config/server";
import { getPathByLocale } from "astro:i18n";
export const prerender = false;
const targetLocale = Astro.preferredLocale
? getPathByLocale(Astro.preferredLocale)
: i18n!.defaultLocale;
return Astro.redirect(`/${targetLocale}/`);
---
Redirecting to {`/${targetLocale}/`}...
所有 frontmatter 的部份(--- 與 --- 之間)都是 在伺服器上 執行的。使用 Astro 時,所有頁面是應用程式編譯期間靜態算繪的,除非像上面一樣明確寫下 export const prerender = false;:一旦在這個 URL 上收到要求,它就會由伺服器處理。
Astro.preferredLocale
此屬性僅在 伺服器端算繪 可用(prerender = false)。它將包含瀏覽器偏好的語言,對應應用程式中的組態語言(由 Astro 處理)。例如,如果瀏覽器將其第一個語言設置為 fr-FR,則 Astro 將組態的代碼中會找到 fr-FR 而把 Astro.preferredLocale 填寫相同的值。
如果我們的應用程式不支援瀏覽器組態的語言(例如我們不支援西班牙語 es-ES),則 Astro.preferredLocale 將包含 undefined:可以回退到 defaultLocale。
getPathByLocale
getPathByLocale 函式來自 astro:i18n 模組:從瀏覽器收到的語言代碼(例如 en-US 或 zh-TW),檢索要轉址的 URL 前綴(en 或 zh)。都是組態中 path 與 codes 屬性定義的:
// astro.config.mjs
export default defineConfig({
i18n: {
locales: [
// 瀏覽器收到的語言代碼與對應應用程式中的路徑
{ path: "fr", codes: ["fr", "fr-FR", "fr-CA"] },
{ path: "en", codes: ["en", "en-US", "en-GB"] },
{ path: "zh", codes: ["zh", "zh-CN", "zh-TW"] },
],
//...
},
//...
});
因此,使用者 fr-FR 將被轉址到 /fr/,使用者 en-GB 將被轉址到 /en/,等等…
資料夾結構
應用程式的主頁(src/pages/index.astro)負責為使用者轉址到正確的語言。但對於其餘頁面,我們將創造一個 src/pages/[locale]/ 資料夾,將允許以多種語言管理每個頁面。
因此,對於靜態頁面(如首頁或關於頁面),可以建立單一檔案,並整合國際化的字串。
對於更豐富的內容(如文章),會建立一個範本檔案,然後將內容在內容合集中組織。
內容合集
作為內容的入口,將創造個新 src/content 的資料夾。
裡面可以為每種語言創造新資料夾。因此,將有:
src/content/frsrc/content/ensrc/content/zh
接下來,在每種語言的下方,可以創造新的資料夾,對應到內容合集(例如文章):
src/content/fr/articlessrc/content/en/articlessrc/content/zh/articles
要載入內容,會定義內容合集,其 pattern 包含所有支援的語言:
const articlesCollection = defineCollection({
loader: glob({
pattern: localizedPattern("articles/**/*.{md,mdx}"),
base: "src/content/",
}),
schema: z.object({
title: z.string(),
}),
});
localizedPattern 在 pattern 前面增加語言的前綴 :
export function localizedPattern(pattern: string): string {
const langCodes = Object.keys(languages);
return `(${langCodes.join("|")})/${pattern}`;
}
因此,就像上面的例子一樣,articles/**/*.{md,mdx} 就會變成 (fr|en|zh)/articles/**/*.{md,mdx}(base 是 src/content/ 資料夾)。
getStaticPaths
如果應用程式支援多種語言,可以將 [locale] 作為根資料夾,這意味著所有頁面至少有一個 URL 參數。對於靜態頁面,這可能意味著需要重複邏輯來檢索這個參數的所有值(/articles、/about、/contact、…)並生成所有 URL。
我們會創造一個 localizedPaths 的實用函式,它將負責返回包含我們 URL 參數 locale 的對象,每個語言一個。
如果沒有其他 URL 參數要填寫,我們可以直接將這個函式重新導出為 getStaticPaths:Astro 將為每個語言生成 URL。
type LocalizedPath = {
params: {
locale: string;
};
};
/**
* For a given page, grab all languages and create the necessary paths with the locale param
*
* @returns An array of all localized paths
*/
export async function localizedPaths(): Promise<LocalizedPath[]> {
const langs = Object.keys(languages);
return langs.map((locale) => {
return { params: { locale } };
});
}
例如,[locale]/about.astro 中:
export { localizedPaths as getStaticPaths } from "@/lib/utils";