Next.js 13 App Router嵌套布局下i18n字典传递至子组件方案
Next.js 13 App Router 国际化字典传递方案
问题背景
基于Next.js 13新/app路由的项目,根布局src/app/[lang]/layout.tsx可通过路由参数lang获取i18n字典,但无法将该字典传递给子布局(src/app/[lang]/(main)/layout.tsx)及Header等组件,需解决这一传递问题。
解决方案
方案1:React Context 全局传递(推荐)
利用React Context实现字典的全局共享,适合组件层级较多的场景。
步骤1:创建I18n Context组件
新建src/components/I18nContext.tsx:
'use client'; import { createContext, useContext } from 'react'; import type { I18nDictionary } from '@/i18n'; const I18nContext = createContext<I18nDictionary | null>(null); export const I18nProvider = ({ children, dictionary }: { children: React.ReactNode; dictionary: I18nDictionary }) => { return <I18nContext.Provider value={dictionary}>{children}</I18nContext.Provider>; }; export const useI18n = () => { const context = useContext(I18nContext); if (!context) { throw new Error('useI18n必须在I18nProvider内部使用'); } return context; };
步骤2:修改根布局,包裹Provider
更新src/app/[lang]/layout.tsx:
import React from 'react'; import { getI18nDictionary } from '@/i18n'; import { I18nProvider } from '@/components/I18nContext'; type Props = { children: React.ReactNode; params: { lang: string; }; }; const AppLayout = async ({ children, params }: Props) => { const i18nDictionary = await getI18nDictionary(params.lang); return ( <html lang={params.lang}> <body> <I18nProvider dictionary={i18nDictionary}> {children} </I18nProvider> </body> </html> ); }; export default AppLayout;
步骤3:子组件中使用字典
修改Header组件,通过useI18n获取字典:
'use client'; // 保留原有导入 import { useI18n } from '@/components/I18nContext'; export const Header = () => { const i18n = useI18n(); const { isOpen: isMobileNavOpen, onToggle } = useDisclosure(); const { colorMode, toggleColorMode } = useColorMode(); return ( <Box as="header"> {/* 原有代码 */} <Heading as={'h1'} fontSize={'xl'} display={{ base: 'none', md: 'block' }}> <TextUnderline>{i18n.header.logoText}</TextUnderline> </Heading> {/* 原有代码 */} </Box> ); };
方案2:Props 逐层传递
适合组件层级较少、结构简单的项目,逻辑直观。
步骤1:修改子布局接收字典Props
更新src/app/[lang]/(main)/layout.tsx:
import React from 'react'; import type { Metadata } from 'next/types'; import type { I18nDictionary } from '@/i18n'; import { InitializeChakra } from '@/components/InitializeChakra'; import { Header } from '@/components/Header'; import { Footer } from '@/components/Footer'; export const metadata: Metadata = { title: 'Test', description: 'Test', keywords: 'test', }; type Props = { children: React.ReactNode; i18nDictionary: I18nDictionary; }; const AppLayout = ({ children, i18nDictionary }: Props) => { return ( <InitializeChakra> <Header i18nDictionary={i18nDictionary} /> {children} <Footer i18nDictionary={i18nDictionary} /> </InitializeChakra> ); }; export default AppLayout;
步骤2:根布局传递字典给子布局
更新src/app/[lang]/layout.tsx:
import React from 'react'; import { getI18nDictionary } from '@/i18n'; type Props = { children: React.ReactNode; params: { lang: string; }; }; const AppLayout = async ({ children, params }: Props) => { const i18nDictionary = await getI18nDictionary(params.lang); // 将字典作为Props传递给子布局 const childWithProps = React.cloneElement(children as React.ReactElement, { i18nDictionary, }); return ( <html lang={params.lang}> <body> {childWithProps} </body> </html> ); }; export default AppLayout;
步骤3:Header组件接收字典Props
修改Header组件:
'use client'; // 保留原有导入 import type { I18nDictionary } from '@/i18n'; type Props = { i18nDictionary: I18nDictionary; }; export const Header = ({ i18nDictionary }: Props) => { const { isOpen: isMobileNavOpen, onToggle } = useDisclosure(); const { colorMode, toggleColorMode } = useColorMode(); return ( <Box as="header"> {/* 原有代码 */} <Heading as={'h1'} fontSize={'xl'} display={{ base: 'none', md: 'block' }}> <TextUnderline>{i18nDictionary.header.logoText}</TextUnderline> </Heading> {/* 原有代码 */} </Box> ); };
方案3:组件内自行获取(搭配缓存)
适合无需全局共享字典的场景,每个组件独立获取并缓存,避免重复请求。
步骤1:安装SWR用于缓存
npm install swr
步骤2:Header组件内获取字典
修改Header组件:
'use client'; // 保留原有导入 import { useParams } from 'next/navigation'; import useSWR from 'swr'; import { getI18nDictionary } from '@/i18n'; export const Header = () => { const params = useParams(); const lang = params.lang as string; // 用SWR缓存字典,避免重复请求 const { data: i18nDictionary } = useSWR( ['i18n', lang], () => getI18nDictionary(lang), { fallbackData: null } ); // 加载状态处理 if (!i18nDictionary) return <div>加载中...</div>; const { isOpen: isMobileNavOpen, onToggle } = useDisclosure(); const { colorMode, toggleColorMode } = useColorMode(); return ( <Box as="header"> {/* 原有代码 */} <Heading as={'h1'} fontSize={'xl'} display={{ base: 'none', md: 'block' }}> <TextUnderline>{i18nDictionary.header.logoText}</TextUnderline> </Heading> {/* 原有代码 */} </Box> ); };
方案选择建议
- Context方案:优先选择,适合大多数项目,全局共享无需逐层传递Props。
- Props传递:适合小型项目或组件层级简单的场景,逻辑清晰。
- 组件内自行获取:适合独立组件或无需全局共享的场景,搭配缓存性能更优。
内容的提问来源于stack exchange,提问作者nop
相关产品推荐
相关产品推荐

