UI: Change usage of I18nProvider

This commit is contained in:
fuma-nama
2024-07-25 22:12:19 +08:00
parent 4373231125
commit 8aebeab316
7 changed files with 128 additions and 45 deletions
+59
View File
@@ -0,0 +1,59 @@
---
'fumadocs-ui': major
---
**Change usage of I18nProvider**
**why:** Make possible to load translations lazily
**migrate:**
```tsx
import { RootProvider } from 'fumadocs-ui/provider';
import type { ReactNode } from 'react';
import { I18nProvider } from 'fumadocs-ui/i18n';
export default function Layout({
params: { lang },
children,
}: {
params: { lang: string };
children: ReactNode;
}) {
return (
<html lang={lang}>
<body>
<I18nProvider
locale={lang}
// options
locales={[
{
name: 'English',
locale: 'en',
},
{
name: 'Chinese',
locale: 'cn',
},
]}
// translations
translations={
{
cn: {
toc: '目錄',
search: '搜尋文檔',
lastUpdate: '最後更新於',
searchNoResult: '沒有結果',
previousPage: '上一頁',
nextPage: '下一頁',
},
}[lang]
}
>
<RootProvider>{children}</RootProvider>
</I18nProvider>
</body>
</html>
);
}
```
+9
View File
@@ -0,0 +1,9 @@
---
'fumadocs-ui': major
---
**Require `locale` prop on I18nProvider**
**why:** Fix problems related to pathname parsing
**migrate:** Pass `locale` parameter to the provider
@@ -187,15 +187,25 @@ We only provide english translation by default, you have to pass your translatio
import { I18nProvider } from 'fumadocs-ui/i18n';
<I18nProvider
translations={{
cn: {
name: 'Chinese', // required
search: 'Translated Content',
locales={[
{
name: 'English',
locale: 'en',
},
}}
>
...
</I18nProvider>;
{
name: 'Chinese',
locale: 'cn',
},
]}
translations={
{
cn: {
search: 'Translated Content',
},
}[locale]
}
// other props
/>;
```
### Add Language Switch
+19 -11
View File
@@ -22,26 +22,34 @@ export default function Layout({
<body>
<I18nProvider
locale={lang}
translations={{
en: {
locales={[
{
name: 'English',
locale: 'en',
},
cn: {
{
name: 'Chinese',
toc: '目錄',
search: '搜尋文檔',
lastUpdate: '最後更新於',
searchNoResult: '沒有結果',
previousPage: '上一頁',
nextPage: '下一頁',
locale: 'cn',
},
}}
]}
translations={
{
cn: {
toc: '目錄',
search: '搜尋文檔',
lastUpdate: '最後更新於',
searchNoResult: '沒有結果',
previousPage: '上一頁',
nextPage: '下一頁',
},
}[lang]
}
>
<RootProvider>
<DocsLayout
tree={pageTree[lang]}
nav={{
title: lang === 'cn' ? '目錄' : 'My App',
title: lang === 'cn' ? '文檔' : 'My App',
url: `/${lang}`,
}}
i18n
@@ -16,9 +16,7 @@ export type LanguageSelectProps = Omit<PopoverProps, 'open' | 'onOpenChange'>;
export function LanguageToggle(props: LanguageSelectProps): React.ReactElement {
const context = useI18n();
const [open, setOpen] = useState(false);
if (!context.translations) throw new Error('Missing `<I18nProvider />`');
const languages = Object.entries(context.translations);
if (!context.locales) throw new Error('Missing `<I18nProvider />`');
return (
<Popover open={open} onOpenChange={setOpen} {...props}>
@@ -32,21 +30,21 @@ export function LanguageToggle(props: LanguageSelectProps): React.ReactElement {
<p className="mb-1 p-2 text-xs font-medium text-fd-muted-foreground">
{context.text.chooseLanguage}
</p>
{languages.map(([lang, { name }]) => (
{context.locales.map((item) => (
<button
key={lang}
key={item.locale}
type="button"
className={cn(
'rounded-md p-2 text-left text-sm transition-colors duration-100',
lang === context.locale
item.locale === context.locale
? 'bg-fd-primary/10 font-medium text-fd-primary'
: 'hover:bg-fd-accent hover:text-fd-accent-foreground',
)}
onClick={() => {
context.onChange?.(lang);
context.onChange?.(item.locale);
}}
>
{name}
{item.name}
</button>
))}
</PopoverContent>
+3 -5
View File
@@ -14,18 +14,16 @@ export interface Translations {
chooseTheme: string;
}
export interface NamedTranslation extends Partial<Translations> {
/**
* The name of translation
*/
export interface LocaleItem {
name: string;
locale: string;
}
interface I18nContextType {
locale?: string;
onChange?: (v: string) => void;
text: Translations;
translations?: Record<string, NamedTranslation>;
locales?: LocaleItem[];
}
export const I18nContext = createContext<I18nContextType>({
+14 -13
View File
@@ -6,23 +6,24 @@ import {
useI18n,
type Translations,
I18nContext,
type NamedTranslation,
type LocaleItem,
} from './contexts/i18n';
interface I18nProviderProps {
// TODO: make required (next major)
/**
* Force a locale, by default, it is parsed from pathname
*
* **Highly recommended to specify one**
* Current locale
*/
locale?: string;
locale: string;
// TODO: only pass the current translation, reduce client bundle size
/**
* Translations for each language
* Translations of current locale
*/
translations?: Record<string, NamedTranslation>;
translations?: Partial<Translations>;
/**
* Available languages
*/
locales?: LocaleItem[];
/**
* Handle changes to the locale, redirect user when not specified.
@@ -33,7 +34,8 @@ interface I18nProviderProps {
}
export function I18nProvider({
translations = {},
locales = [],
locale,
...props
}: I18nProviderProps): React.ReactElement {
const context = useI18n();
@@ -42,7 +44,6 @@ export function I18nProvider({
.split('/')
.filter((v) => v.length > 0);
const locale = props.locale ?? segments[0];
const onChange = useCallback(
(v: string) => {
// If locale prefix hidden
@@ -61,10 +62,10 @@ export function I18nProvider({
<I18nContext.Provider
value={{
locale,
translations,
locales,
text: {
...context.text,
...translations[locale],
...props.translations,
},
onChange: props.onChange ?? onChange,
}}