Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5e4e9ec

Browse files
committed
UI: Deprecate I18nProvider in favour of <RootProvider /> i18n prop
1 parent 293178f commit 5e4e9ec

File tree

5 files changed

+178
-105
lines changed

5 files changed

+178
-105
lines changed

.changeset/early-deer-dance.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'fumadocs-ui': patch
3+
---
4+
5+
Deprecate I18nProvider in favour of `<RootProvider />` `i18n` prop

examples/i18n/app/[lang]/layout.tsx

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import 'fumadocs-ui/style.css';
22
import { RootProvider } from 'fumadocs-ui/provider';
33
import { Inter } from 'next/font/google';
44
import type { ReactNode } from 'react';
5-
import { I18nProvider } from 'fumadocs-ui/i18n';
65

76
const inter = Inter({
87
subsets: ['latin'],
@@ -25,20 +24,20 @@ export default async function Layout({
2524
minHeight: '100vh',
2625
}}
2726
>
28-
<I18nProvider
29-
locale={lang}
30-
locales={[
31-
{
32-
name: 'English',
33-
locale: 'en',
34-
},
35-
{
36-
name: 'Chinese',
37-
locale: 'cn',
38-
},
39-
]}
40-
translations={
41-
{
27+
<RootProvider
28+
i18n={{
29+
locale: lang,
30+
locales: [
31+
{
32+
name: 'English',
33+
locale: 'en',
34+
},
35+
{
36+
name: 'Chinese',
37+
locale: 'cn',
38+
},
39+
],
40+
translations: {
4241
cn: {
4342
toc: '目錄',
4443
search: '搜尋文檔',
@@ -48,11 +47,11 @@ export default async function Layout({
4847
nextPage: '下一頁',
4948
chooseLanguage: '選擇語言',
5049
},
51-
}[lang]
52-
}
50+
}[lang],
51+
}}
5352
>
54-
<RootProvider>{children}</RootProvider>
55-
</I18nProvider>
53+
{children}
54+
</RootProvider>
5655
</body>
5756
</html>
5857
);

packages/ui/src/contexts/i18n.tsx

Lines changed: 1 addition & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
'use client';
2-
import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
3-
import { createContext, type ReactNode, useContext, useMemo } from 'react';
4-
import { usePathname, useRouter } from 'next/navigation';
2+
import { createContext, useContext } from 'react';
53

64
export interface Translations {
75
search: string;
@@ -56,79 +54,3 @@ export function I18nLabel(props: { label: keyof Translations }): string {
5654
export function useI18n(): I18nContextType {
5755
return useContext(I18nContext);
5856
}
59-
60-
export interface I18nProviderProps {
61-
/**
62-
* Current locale
63-
*/
64-
locale: string;
65-
66-
/**
67-
* Handle changes to the locale, redirect user when not specified.
68-
*/
69-
onLocaleChange?: (v: string) => void;
70-
71-
/**
72-
* Translations of current locale
73-
*/
74-
translations?: Partial<Translations>;
75-
76-
/**
77-
* Available languages
78-
*/
79-
locales?: LocaleItem[];
80-
81-
children?: ReactNode;
82-
}
83-
84-
export function I18nProvider({
85-
locales = [],
86-
locale,
87-
onChange: _onChange,
88-
onLocaleChange = _onChange,
89-
...props
90-
}: I18nProviderProps & {
91-
// TODO: remove next major
92-
/**
93-
* @deprecated use `onLocaleChange` instead
94-
*/
95-
onChange?: I18nProviderProps['onLocaleChange'];
96-
}) {
97-
const router = useRouter();
98-
const pathname = usePathname();
99-
const onChange = useEffectEvent((value: string) => {
100-
if (onLocaleChange) {
101-
return onLocaleChange(value);
102-
}
103-
const segments = pathname.split('/').filter((v) => v.length > 0);
104-
105-
// If locale prefix hidden
106-
if (segments[0] !== locale) {
107-
segments.unshift(value);
108-
} else {
109-
segments[0] = value;
110-
}
111-
112-
router.push(`/${segments.join('/')}`);
113-
router.refresh();
114-
});
115-
116-
return (
117-
<I18nContext.Provider
118-
value={useMemo(
119-
() => ({
120-
locale,
121-
locales,
122-
text: {
123-
...defaultTranslations,
124-
...props.translations,
125-
},
126-
onChange,
127-
}),
128-
[locale, locales, onChange, props.translations],
129-
)}
130-
>
131-
{props.children}
132-
</I18nContext.Provider>
133-
);
134-
}

packages/ui/src/i18n.tsx

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,64 @@
1-
export {
2-
defaultTranslations,
3-
type Translations,
4-
I18nProvider,
5-
type I18nProviderProps,
6-
} from './contexts/i18n';
1+
'use client';
2+
import { usePathname, useRouter } from 'next/navigation';
3+
import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
4+
import { useMemo } from 'react';
5+
import { defaultTranslations, I18nContext } from '@/contexts/i18n';
6+
import type { I18nProviderProps } from './provider/base';
7+
8+
export type { I18nProviderProps };
9+
export { defaultTranslations, type Translations } from './contexts/i18n';
10+
11+
// TODO: remove next major
12+
/**
13+
* @deprecated legacy I18n Provider, use `<RootProvider i18n={...} />` instead
14+
*/
15+
export function I18nProvider({
16+
locales = [],
17+
locale,
18+
onChange: _onChange,
19+
onLocaleChange = _onChange,
20+
...props
21+
}: I18nProviderProps & {
22+
/**
23+
* @deprecated use `onLocaleChange` instead
24+
*/
25+
onChange?: I18nProviderProps['onLocaleChange'];
26+
}) {
27+
const router = useRouter();
28+
const pathname = usePathname();
29+
const onChange = useEffectEvent((value: string) => {
30+
if (onLocaleChange) {
31+
return onLocaleChange(value);
32+
}
33+
const segments = pathname.split('/').filter((v) => v.length > 0);
34+
35+
// If locale prefix hidden
36+
if (segments[0] !== locale) {
37+
segments.unshift(value);
38+
} else {
39+
segments[0] = value;
40+
}
41+
42+
router.push(`/${segments.join('/')}`);
43+
router.refresh();
44+
});
45+
46+
return (
47+
<I18nContext.Provider
48+
value={useMemo(
49+
() => ({
50+
locale,
51+
locales,
52+
text: {
53+
...defaultTranslations,
54+
...props.translations,
55+
},
56+
onChange,
57+
}),
58+
[locale, locales, onChange, props.translations],
59+
)}
60+
>
61+
{props.children}
62+
</I18nContext.Provider>
63+
);
64+
}

packages/ui/src/provider/base.tsx

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
'use client';
22

33
import { ThemeProvider } from 'next-themes';
4-
import { type ComponentPropsWithoutRef, lazy, type ReactNode } from 'react';
4+
import {
5+
type ComponentPropsWithoutRef,
6+
lazy,
7+
type ReactNode,
8+
useMemo,
9+
} from 'react';
510
import { DirectionProvider } from '@radix-ui/react-direction';
611
import type { DefaultSearchDialogProps } from '@/components/dialog/search-default';
712
import { SidebarProvider } from '@/contexts/sidebar';
813
import { SearchProvider, type SearchProviderProps } from '@/contexts/search';
14+
import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
15+
import {
16+
defaultTranslations,
17+
I18nContext,
18+
type LocaleItem,
19+
type Translations,
20+
} from '@/contexts/i18n';
21+
import { usePathname, useRouter } from 'fumadocs-core/framework';
922

1023
interface SearchOptions
1124
extends Omit<SearchProviderProps, 'options' | 'children'> {
@@ -41,6 +54,32 @@ export interface RootProviderProps {
4154
enabled?: boolean;
4255
};
4356

57+
i18n?: Omit<I18nProviderProps, 'children'>;
58+
59+
children?: ReactNode;
60+
}
61+
62+
export interface I18nProviderProps {
63+
/**
64+
* Current locale
65+
*/
66+
locale: string;
67+
68+
/**
69+
* Handle changes to the locale, redirect user when not specified.
70+
*/
71+
onLocaleChange?: (v: string) => void;
72+
73+
/**
74+
* Translations of current locale
75+
*/
76+
translations?: Partial<Translations>;
77+
78+
/**
79+
* Available languages
80+
*/
81+
locales?: LocaleItem[];
82+
4483
children?: ReactNode;
4584
}
4685

@@ -53,9 +92,14 @@ export function RootProvider({
5392
dir = 'ltr',
5493
theme = {},
5594
search,
95+
i18n,
5696
}: RootProviderProps) {
5797
let body = children;
5898

99+
if (i18n) {
100+
body = <I18nProvider {...i18n}>{body}</I18nProvider>;
101+
}
102+
59103
if (search?.enabled !== false)
60104
body = (
61105
<SearchProvider SearchDialog={DefaultSearchDialog} {...search}>
@@ -82,3 +126,48 @@ export function RootProvider({
82126
</DirectionProvider>
83127
);
84128
}
129+
130+
function I18nProvider({
131+
locales = [],
132+
locale,
133+
onLocaleChange,
134+
...props
135+
}: I18nProviderProps) {
136+
const router = useRouter();
137+
const pathname = usePathname();
138+
const onChange = useEffectEvent((value: string) => {
139+
if (onLocaleChange) {
140+
return onLocaleChange(value);
141+
}
142+
const segments = pathname.split('/').filter((v) => v.length > 0);
143+
144+
// If locale prefix hidden
145+
if (segments[0] !== locale) {
146+
segments.unshift(value);
147+
} else {
148+
segments[0] = value;
149+
}
150+
151+
router.push(`/${segments.join('/')}`);
152+
router.refresh();
153+
});
154+
155+
return (
156+
<I18nContext.Provider
157+
value={useMemo(
158+
() => ({
159+
locale,
160+
locales,
161+
text: {
162+
...defaultTranslations,
163+
...props.translations,
164+
},
165+
onChange,
166+
}),
167+
[locale, locales, onChange, props.translations],
168+
)}
169+
>
170+
{props.children}
171+
</I18nContext.Provider>
172+
);
173+
}

0 commit comments

Comments
 (0)