1. Data Fetch
1.1. Client Component
"use client"
export default async function page({ params: { locale } }: { params: { locale: Locale } }) {
useEffect(() => {
// get data
}, [])
}
client component获取数据跟普通的获取没啥区别,值得注意的是 next默认是 server component,需要声明 use client。
1.2. Server Component
Next 官方推荐使用 fetch,但是 axios 同样能用于服务端。
async function getData () {
const { data } = await getMemberList()
return data.data.list
}
export default async function page({ params: { locale } }: { params: { locale: Locale } }) {
const list = await getData()
return (
<div className={styles.team}>
<div className='imgWrapper'>
<img src='/images/Team-3.jpg' alt='icon' />
</div>
<div className='alignwide'>
<h1>我们的团队</h1>
<Row gutter={[10, 10]} style={{width: '100%'}}>
{
list.map((item: any) => (
<Col key={item.ID} md={8} sm={24} xs={24}>
<Staff avatar={getImgUrl(item.avatar)} name={lt(locale, item.name)} title={lt(locale, item.title)} />
</Col>
))
}
</Row>
</div>
</div>
)
}
2. i18n
2.1. 安装依赖
npm install i18next react-i18next i18next-resources-to-backend next-i18n-router
react-i18next、i18next:i18n 基础包i18next-resources-to-backend:加载资源到 servernext-i18n-router用于在 app router 中实现国际化路由和地区检测
2.2. 改造项目
/app/[locale]用于传递 locale 路由参数/app/lib/i18n/config.tsi18n配置/app/lib/i18n/index.ts翻译函数/app/locales本地翻译资源/middleware.ts这里通过next-i18n-router国际化路由/app/ui/TranslationsProvider/index.tsxi18n provider for client render
/app/lib/i18n/config.ts
export default {
locales: ['en-US', 'zh-CN'],
defaultLocale: 'en-US',
prefixDefault: false
}
/app/lib/i18n/index.ts
import { createInstance, i18n } from 'i18next'
import resourcesToBackend from 'i18next-resources-to-backend'
import { initReactI18next } from 'react-i18next/initReactI18next'
import i18nConfig from './config'
// Local i18n
export async function initTranslations(
locale: string,
namespaces: string[],
i18nInstance?: i18n,
resources?: any
) {
i18nInstance = i18nInstance || createInstance();
i18nInstance.use(initReactI18next);
if (!resources) {
i18nInstance.use(
resourcesToBackend(
(language: string, namespace: string) =>
import(`@/locales/${language}/${namespace}.json`) // 读取翻译资源
)
);
}
await i18nInstance.init({
lng: locale,
resources,
fallbackLng: i18nConfig.defaultLocale,
supportedLngs: i18nConfig.locales,
defaultNS: namespaces[0],
fallbackNS: namespaces[0],
ns: namespaces,
preload: resources ? [] : i18nConfig.locales
});
return {
i18n: i18nInstance,
resources: i18nInstance.services.resourceStore.data,
t: i18nInstance.t
};
}
// BackEnd i18n
export type Locale = 'en-US' | 'zh-CN'
export type LocalizedText<T extends Locale> = {
[key in T]: string;
}
export function getLocaleText<T extends Locale>(
locale: T, text: LocalizedText<T>, tryonly?: boolean,
): string {
if (!text[locale] || text[locale] === '' || text[locale] === '\n') {
if (!tryonly) {
for (const key in text) {
if (text[key]) {
return text[key];
}
}
} else {
return '';
}
}
return text[locale];
}
export const lt = getLocaleText
/middleware.ts(根目录,不要搞错了)
import { i18nRouter } from 'next-i18n-router';
import i18nConfig from './app/lib/i18n/config';
export function middleware(request: any) {
return i18nRouter(request, i18nConfig);
}
// applies this middleware only to files in the app directory
export const config = {
matcher: '/((?!api|static|.*\..*|_next).*)'
};
/app/ui/TranslationsProvider/index.tsx
'use client'
import { I18nextProvider } from 'react-i18next'
import { initTranslations } from '@/lib/i18n'
import { createInstance } from 'i18next'
export default function TranslationsProvider({
children,
locale,
namespaces,
resources
}: {
children: React.ReactNode,
locale: string,
namespaces: string[],
resources: any
}) {
const i18n = createInstance()
initTranslations(locale, namespaces, i18n, resources)
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>
}
2.3. 使用
/app/[locale]/layout.tsx
type RootLayoutProps = Readonly<{
children: React.ReactNode
params: {
locale: string
}
}>
export default function RootLayout({
children,
params: {
locale
}
}: RootLayoutProps) {
return (
<html lang={locale}>
<body className={inter.className}>
<AntdRegistry>
<Header locale={locale} />
{children}
<Footer />
</AntdRegistry>
</body>
</html>
);
}
/app/ui/Header.tsx
import { initTranslations } from '@/lib/i18n'
import LanguageChanger from '../LanguageChanger' // langChange component
import TranslationsProvider from '../TranslationsProvider' // CR component
import styles from './index.module.scss'
import Image from 'next/image'
const i18nNamespaces = ['home', 'common']
export default async function Header({ locale }: { locale: string }) {
const { resources, t } = await initTranslations(locale, i18nNamespaces)
return (
<div className={styles.header}>
<div className={styles.imgWrapper}>
<a href='/'>
<Image src='/images/logo.jpg' width={500} height={59} alt='logo' />
</a>
</div>
<div className={styles.menuWrapper}>
<ul className={styles.menu}>
{
menus.map((item) => (
<li key={item.title} className={styles.item}>
<a href={item.url} className={styles.txt}>{item.title}</a>
</li>
))
}
<li>
{/* Server render */}
{t('title')}
</li>
<li>
{/* Client Render */}
<TranslationsProvider namespaces={i18nNamespaces} locale={locale} resources={resources}>
<LanguageChanger />
</TranslationsProvider>
</li>
</ul>
</div>
</div>
)
}
async/await只能在server component中使用,所以i18n不能直接在client render中使用。client render需要在server component的后代组件中使用。- 在包裹
TranslationsProvider中的组件可以直接使用useTranslation。
2.4. 切换语言
'use client'
import { useRouter } from 'next/navigation'
import { usePathname } from 'next/navigation'
import { useTranslation } from 'react-i18next'
import i18nConfig from '../../lib/i18n/config'
import { Select } from 'antd'
import Cookies from 'js-cookie'
export default function LanguageChanger() {
const { t, i18n } = useTranslation(['common'])
const router = useRouter()
const currentPathname = usePathname()
const currentLocale = i18n.language
const handleChange = (value: string) => {
const newLocale = value
// set cookie for next-i18n-router
Cookies.set('NEXT_LOCALE', newLocale, { expires: 30 })
// redirect to the new locale path
if (
currentLocale === i18nConfig.defaultLocale &&
!i18nConfig.prefixDefault
) {
router.push('/' + newLocale + currentPathname);
} else {
router.push(
currentPathname.replace(`/${currentLocale}`, `/${newLocale}`)
);
}
router.refresh();
};
const options = i18nConfig.locales.map((item) => ({
label: t(item),
value: item
}))
return (
<Select
defaultValue={currentLocale}
style={{ width: 120 }}
onChange={handleChange}
options={options}
/>
);
}
next-i18n-router使用NEXT_LOCALE存储locale,那么我们可以借助它来设置区域。