mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
UI: Change usage of I18nProvider
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
```
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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,
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user