1515 * - 简单的 ZH / EN 双字母展示,当前语言高亮;button 尺寸与 ThemeToggle 对齐
1616 */
1717
18- import { useEffect , useState } from "react" ;
18+ import { useSyncExternalStore } from "react" ;
1919import { useRouter } from "next/navigation" ;
2020import { Button } from "@/components/ui/button" ;
2121
2222type Locale = "zh" | "en" ;
2323
24+ // 自定义事件名:toggle 点击后 dispatch,通知 useSyncExternalStore 重新读取 cookie
25+ const LOCALE_EVENT = "locale-toggle-change" ;
26+
2427function readLocaleCookie ( ) : Locale {
2528 if ( typeof document === "undefined" ) return "zh" ;
2629 const m = document . cookie . match ( / (?: ^ | ; \s * ) l o c a l e = ( [ ^ ; ] + ) / ) ;
@@ -33,21 +36,35 @@ function writeLocaleCookie(next: Locale) {
3336 document . cookie = `locale=${ next } ;path=/;max-age=${ 60 * 60 * 24 * 365 } ;samesite=lax` ;
3437}
3538
39+ // 订阅 LOCALE_EVENT,toggle 时主动 dispatch 让 useSyncExternalStore 重新读 cookie
40+ function subscribeLocale ( callback : ( ) => void ) {
41+ if ( typeof window === "undefined" ) return ( ) => { } ;
42+ window . addEventListener ( LOCALE_EVENT , callback ) ;
43+ return ( ) => window . removeEventListener ( LOCALE_EVENT , callback ) ;
44+ }
45+
46+ const emptySubscribe = ( ) => ( ) => { } ;
47+
3648export function LocaleToggle ( ) {
3749 const router = useRouter ( ) ;
38- // 初始 render 先给默认值避免 hydration 不一致,真实值由 useEffect 读 cookie 后覆盖
39- const [ locale , setLocale ] = useState < Locale > ( "zh" ) ;
40- const [ ready , setReady ] = useState ( false ) ;
41-
42- useEffect ( ( ) => {
43- setLocale ( readLocaleCookie ( ) ) ;
44- setReady ( true ) ;
45- } , [ ] ) ;
50+ // 用 useSyncExternalStore 替代 useEffect+setState:SSR 返回 "zh",客户端读 cookie
51+ const locale = useSyncExternalStore < Locale > (
52+ subscribeLocale ,
53+ ( ) => readLocaleCookie ( ) ,
54+ ( ) => "zh" ,
55+ ) ;
56+ // ready 表示已 hydrate 到客户端,可以按真实 locale 高亮
57+ const ready = useSyncExternalStore (
58+ emptySubscribe ,
59+ ( ) => true ,
60+ ( ) => false ,
61+ ) ;
4662
4763 const toggle = ( ) => {
4864 const next : Locale = locale === "zh" ? "en" : "zh" ;
4965 writeLocaleCookie ( next ) ;
50- setLocale ( next ) ;
66+ // 通知所有订阅者(当前组件)重新从 cookie 读取
67+ window . dispatchEvent ( new Event ( LOCALE_EVENT ) ) ;
5168 // 刷新 server component 树,重新按 cookie 渲染各页面
5269 router . refresh ( ) ;
5370 } ;
0 commit comments