本章节做一个 Nextjs 的补充知识:多语言与主题的切换
多语言
安装依赖:
创建目录结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 - app - [locale] - layout.tsx - page.tsx - i18n.ts - next.config.mjs - middleware.ts - messages - en.json - zh.json - components - LanguageSwitch.tsx
添加对应的语言内容
1 2 3 4 5 6 7 8 9 10 11 12 13 { "Index" : { "title" : "Hello world!" } } { "Index" : { "title" : "你好 世界!" } }
在next.config.mjs中使用该插件
1 2 3 4 5 6 7 8 import createNextIntlPlugin from 'next-intl/plugin' ; const withNextIntl = createNextIntlPlugin();const nextConfig = {};export default withNextIntl(nextConfig);
在i18n.ts中使用语言文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import {notFound} from 'next/navigation' ;import {getRequestConfig} from 'next-intl/server' ; const locales = ['en' , 'zh' ]; export default getRequestConfig(async ({locale}) => { if (!locales.includes(locale as any )) notFound(); return { messages : (await import (`./messages/${locale} .json` )).default }; });
middleware.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import createMiddleware from 'next-intl/middleware' ; export default createMiddleware({ locales : ['en' , 'zh' ], defaultLocale : 'en' }); export const config = { matcher : ['/' , '/(zh|en)/:path*' ] };
在app/[locale]/layout.tsx提供语言信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import {NextIntlClientProvider} from 'next-intl' ;import {getMessages} from 'next-intl/server' ; export default async function LocaleLayout ({ children, params: {locale} }: { children: React.ReactNode; params: {locale: string }; } ) { const messages = await getMessages(); return ( <html lang ={locale} > <body > <NextIntlClientProvider messages ={messages} > {children} </NextIntlClientProvider > </body > </html > ); }
在其他子路由中使用语言信息,比如app/[locale]/page.tsx
1 2 3 4 5 6 import {useTranslations} from 'next-intl' ; export default function Index ( ) { const t = useTranslations('Index' ); return <h1 > {t('title')}</h1 > ; }
创建 LanguageSwitch.tsx组件用于切换语言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 'use client' import { useLocale } from "next-intl" import { usePathname, useRouter } from "next/navigation" export default function LanguageSwitch ( ) { const localActive = useLocale() const router = useRouter() const pathname = usePathname() const onChangeLanguage = () => { const nextLocale = localActive === 'en' ? 'zh' : 'en' const newPath = pathname.replace(/^\/(en|zh)/ , `/${nextLocale} /` ) router.replace(newPath, { scroll : false , }) } return ( <button className ="w-[2.5rem] h-[2.5rem] bg-opacity-80 flex items-center justify-center gap-1 transition-all" onClick ={onChangeLanguage} > <span className ="text-sm hover:scale-[1.15] active:scale-105 transition-all" > {localActive === 'en' ? 'EN' : 'ZH'} </span > </button > ) }
自行决定将组件在什么位置引入,可以在app/[locale]/layout.tsx中引入使用
主题 主题通过 tailwindCSS 实现即可。
在 tailwind.config.ts 中添加:
创建 context 提供主题数据并提供 hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 'use client' import { createContext, useContext, useEffect, useState } from "react" type ThemeContextType = { theme : string toggleTheme : () => void } type ThemeContextProviderProp = { children : React.ReactNode } const ThemeContext = createContext<ThemeContextType | null >(null )export const ThemeContextProvider = ({ children }: ThemeContextProviderProp ) => { const [theme, setTheme] = useState('light' ) const toggleTheme = () => { if (theme === 'light' ) { setTheme('dark' ) window .localStorage.setItem('theme' , 'dark' ) document .documentElement.classList.add('dark' ) } else { setTheme('light' ) window .localStorage.setItem('theme' , 'light' ) document .documentElement.classList.remove('dark' ) } } useEffect(() => { const localTheme = window .localStorage.getItem("theme" ) if (localTheme) { setTheme(localTheme) if (localTheme === "dark" ) { document .documentElement.classList.add("dark" ) } else { document .documentElement.classList.remove("dark" ) } } else if (window .matchMedia("(prefers-color-scheme: dark)" ).matches) { setTheme("dark" ) document .documentElement.classList.add("dark" ) } }, []) return ( <ThemeContext.Provider value ={{ theme , toggleTheme }}> {children} </ThemeContext.Provider > ) } export function useTheme ( ) { const context = useContext(ThemeContext) if (!context) { throw new Error ('useTheme must be used within a ThemeContextProvider' ) } return context }
自行决定在什么位置使用,推荐在 app/[locale]/layout.tsx 中包裹 children,对所有的子路由都提供主题信息
创建 ThemeSwitch.tsx 组件用来切换主题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 'use client' import { useTheme } from "@/context/theme-context"; import { BsSun, BsMoon } from 'react-icons/bs' export default function ThemeSwitch() { const {theme, toggleTheme} = useTheme() return ( <button className="w-[2.5rem] h-[2.5rem] bg-opacity-80 flex items-center justify-center hover:scale-[1.15] active:scale-105 transition-all" onClick={toggleTheme} > {theme === 'light' ? <BsSun /> : <BsMoon />} </button> ) }
这里用到了 react-icons 中的图标,可以自行选择使用什么来展示不同的主题模式
组件应用到什么位置自行控制,但是一定是要在 ThemeContextProvider 包裹中才能使用 useTheme哦
正常写样式即可
1 2 <div className="bg-white text-black dark:bg-black dark:text-white" ></div>