如何利用Astro.currentLocale避免样板代码及组件传参问题?
Astro国际化:避免传递lang参数并解决预渲染/重水化问题
问题场景
当前使用Astro.currentLocale实现国际化,示例代码如下:
--- import { getRelativeLocaleUrl } from "astro:i18n" // Components // imports ... // i18n import { getTranslations, type Lang } from "@/i18n/utils" const lang = Astro.currentLocale as Lang const t = getTranslations(lang) --- <div> <div> <h2>{t("any-title")}</h2> <div> <a href={getRelativeLocaleUrl(lang, "any/route")}> {t("view-details")} </a> </div> </div> <div> <InventorySummaryLineChart lang={lang} client:load /> <InventorySummaryBarChart lang={lang} client:load /> <InventorySummaryPieChart lang={lang} client:load /> </div> </div>
希望让图表组件预渲染,使标题和描述在到达客户端时已完成翻译,但目前被迫传递lang参数——客户端无法通过window直接获取带语言信息的URL,且document.documentElement.lang可能因布局渲染顺序问题无法及时读取。
解决方案
1. 直接在框架组件的服务端逻辑中访问Astro.currentLocale
Astro在服务端渲染阶段会暴露Astro.currentLocale,对于支持预渲染的框架组件(React、Vue等),可以在服务端执行的代码块中直接读取该值,无需通过props传递:
以React组件为例:
// InventorySummaryLineChart.tsx import type { Lang } from "@/i18n/utils"; import { getTranslations } from "@/i18n/utils"; // 仅在服务端运行时读取locale,完成预渲染翻译 const lang = import.meta.env.SSR ? (Astro.currentLocale as Lang) : null; const t = lang ? getTranslations(lang) : () => ""; export default function InventorySummaryLineChart() { // 预渲染完成的标题,客户端无需二次处理 const chartTitle = t("line-chart-title"); return ( <div> <h3>{chartTitle}</h3> {/* 图表渲染逻辑 */} </div> ); }
通过import.meta.env.SSR区分环境,避免客户端运行时因找不到Astro对象报错。这样组件在预渲染阶段已完成翻译,客户端无需接收lang参数。
2. 通过根布局注入全局lang变量,兼顾服务端预渲染与客户端读取
如果需要客户端也能访问lang信息,可以在根布局中提前将lang注入全局,确保组件渲染时能读取到:
<!-- src/layouts/BaseLayout.astro --> --- import type { Lang } from "@/i18n/utils"; const lang = Astro.currentLocale as Lang; --- <html lang={lang}> <head> <!-- 页面头部内容 --> </head> <body> <slot /> <!-- 内联脚本挂载全局lang变量 --> <script is:inline> window.__APP_LANG__ = "{lang}"; </script> </body> </html>
之后在框架组件中同时处理服务端预渲染和客户端读取:
// InventorySummaryLineChart.tsx import type { Lang } from "@/i18n/utils"; import { getTranslations } from "@/i18n/utils"; import { useEffect, useState } from "react"; export default function InventorySummaryLineChart() { const [t, setT] = useState(() => () => ""); // 服务端预渲染时直接获取locale并生成翻译函数 if (import.meta.env.SSR) { const serverLang = Astro.currentLocale as Lang; setT(getTranslations(serverLang)); } // 客户端读取全局变量,确保状态一致 useEffect(() => { if (!import.meta.env.SSR) { const clientLang = window.__APP_LANG__ as Lang; setT(getTranslations(clientLang)); } }, []); return ( <div> <h3>{t("line-chart-title")}</h3> {/* 图表内容 */} </div> ); }
这种方式既保证了服务端预渲染的翻译结果,又让客户端能正确获取lang信息,无需传递props。
3. 使用状态管理器时避免重水化不匹配问题
若使用状态管理器(如Zustand、Redux),核心是确保服务端与客户端的初始状态一致:
- 服务端初始化状态时,直接用
Astro.currentLocale设置lang和翻译函数; - 客户端初始化时,读取服务端注入的初始状态,而非重新计算;
以Zustand为例:
// src/store/i18nStore.ts import { create } from "zustand"; import { getTranslations, type Lang } from "@/i18n/utils"; interface I18nState { lang: Lang; t: (key: string) => string; } // 服务端创建初始状态的工厂函数 export const createI18nStore = (initialLang: Lang) => { return create<I18nState>()(() => ({ lang: initialLang, t: getTranslations(initialLang), })); }; // 客户端复用服务端传递的初始状态 let store: ReturnType<typeof createI18nStore>; export const useI18nStore = () => { if (!store) { // 读取根布局注入的全局lang变量 const initialLang = window.__APP_LANG__ as Lang; store = createI18nStore(initialLang); } return store; };
在Astro根布局中注入全局变量后,框架组件即可直接使用状态管理器:
// InventorySummaryLineChart.tsx import { useI18nStore } from "@/store/i18nStore"; export default function InventorySummaryLineChart() { const { t } = useI18nStore(); const chartTitle = t("line-chart-title"); return ( <div> <h3>{chartTitle}</h3> {/* 图表内容 */} </div> ); }
此方案确保服务端预渲染的状态与客户端初始化状态完全一致,避免重水化时出现内容不匹配的问题。
内容的提问来源于stack exchange,提问作者JuanDa237
相关产品推荐
相关产品推荐

