Docs: Simplify i18n setup

This commit is contained in:
Fuma Nama
2026-03-10 21:12:07 +08:00
parent 83823637c9
commit 02201dfdd4
11 changed files with 220 additions and 181 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'fumadocs-ui': patch
'@fumadocs/base-ui': patch
---
Simplify i18n setup
@@ -34,6 +34,70 @@ Make sure its behaviour aligns with the [`hidePrefix`](/docs/headless/internatio
</Callout>
### Translations
Define your translations in a file (e.g. `lib/layout.shared.tsx`), the English translations are used when `translations` is not specified.
You can also add `locale` parameter to `baseOptions()` for localized layout options:
```tsx title="lib/layout.shared.tsx"
import { i18n } from '@/lib/i18n';
import { defineI18nUI } from 'fumadocs-ui/i18n';
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
// [!code ++:11]
export const i18nUI = defineI18nUI(i18n, {
translations: {
en: {
displayName: 'English',
},
cn: {
displayName: 'Chinese',
search: '搜尋文檔',
},
},
});
// [!code highlight]
export function baseOptions(locale: string): BaseLayoutProps {
return {
i18n, // required for i18n [!code ++]
// different props based on `locale`
};
}
```
Pass translations to `<RootProvider />`:
```tsx title="app/[lang]/layout.tsx"
import { RootProvider } from 'fumadocs-ui/provider/next';
import { defineI18nUI } from 'fumadocs-ui/i18n';
import { i18nUI } from '@/lib/layout.shared';
export default async function RootLayout({
params,
children,
}: {
params: Promise<{ lang: string }>;
children: React.ReactNode;
}) {
const lang = (await params).lang;
return (
<html lang={lang}>
<body>
<RootProvider
// [!code ++]
i18n={i18nUI.provider(lang)}
>
{children}
</RootProvider>
</body>
</html>
);
}
```
### Routing
Create a `/app/[lang]` folder, and move your pages/layouts into it, except route handlers.
@@ -54,67 +118,8 @@ Create a `/app/[lang]` folder, and move your pages/layouts into it, except route
correct!
</Callout>
Provide UI translations and other config to `<RootProvider />`, the English translations are used when `translations` is not specified.
```tsx title="app/[lang]/layout.tsx"
import { RootProvider } from 'fumadocs-ui/provider/next';
import { defineI18nUI } from 'fumadocs-ui/i18n';
import { i18n } from '@/lib/i18n';
// [!code ++:11]
const { provider } = defineI18nUI(i18n, {
translations: {
en: {
displayName: 'English',
},
cn: {
displayName: 'Chinese',
search: '搜尋文檔',
},
},
});
export default async function RootLayout({
params,
children,
}: {
params: Promise<{ lang: string }>;
children: React.ReactNode;
}) {
const lang = (await params).lang;
return (
<html lang={lang}>
<body>
<RootProvider
// [!code ++]
i18n={provider(lang)}
>
{children}
</RootProvider>
</body>
</html>
);
}
```
### Pass Locale
Add `locale` parameter to `baseOptions()` and add `i18n` into it:
```tsx title="lib/layout.shared.tsx"
import { i18n } from '@/lib/i18n';
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
// [!code highlight]
export function baseOptions(locale: string): BaseLayoutProps {
return {
i18n, // [!code ++]
// different props based on `locale`
};
}
```
Pass the locale to Fumadocs in your pages and layouts.
```ts tab="lib/source.ts"
@@ -37,17 +37,6 @@ export default [
You can also use `:lang?` prefix, and update the i18n config:
```ts tab="app/lib/i18n.ts"
import { defineI18n } from 'fumadocs-core/i18n';
export const i18n = defineI18n({
defaultLanguage: 'en',
languages: ['cn', 'en'],
// [!code ++]
hideLocale: 'default-locale',
});
```
```ts tab="app/routes.ts"
import { route, type RouteConfig } from '@react-router/dev/routes';
@@ -59,20 +48,30 @@ export default [
] satisfies RouteConfig;
```
### Pages
```ts tab="app/lib/i18n.ts"
import { defineI18n } from 'fumadocs-core/i18n';
Provide UI translations and other config to `<RootProvider />`, the English translations are used as fallbacks.
export const i18n = defineI18n({
defaultLanguage: 'en',
languages: ['cn', 'en'],
// [!code ++]
hideLocale: 'default-locale',
});
```
```tsx title="app/root.tsx"
import { Links, Meta, Scripts, ScrollRestoration, useParams } from 'react-router';
import { RootProvider } from 'fumadocs-ui/provider/base';
import { ReactRouterProvider } from 'fumadocs-core/framework/react-router';
import { defineI18nUI } from 'fumadocs-ui/i18n';
### Translations
Define your translations in a file (e.g. `lib/layout.shared.tsx`), the English translations are used when `translations` is not specified.
You can also add `locale` parameter to `baseOptions()` for localized layout options:
```tsx title="lib/layout.shared.tsx"
import { i18n } from '@/lib/i18n';
import './app.css';
import { defineI18nUI } from 'fumadocs-ui/i18n';
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
// [!code ++:11]
const { provider } = defineI18nUI(i18n, {
export const i18nUI = defineI18nUI(i18n, {
translations: {
en: {
displayName: 'English',
@@ -84,8 +83,27 @@ const { provider } = defineI18nUI(i18n, {
},
});
// [!code highlight]
export function baseOptions(locale: string): BaseLayoutProps {
return {
i18n, // required for i18n [!code ++]
// different props based on `locale`
};
}
```
### Pages
Pass translations to `<RootProvider />`:
```tsx title="app/root.tsx"
import { Links, Meta, Scripts, ScrollRestoration, useParams } from 'react-router';
import { RootProvider } from 'fumadocs-ui/provider/react-router';
import { i18nUI } from '@/lib/layout.shared';
import './app.css';
export function Layout({ children }: { children: React.ReactNode }) {
const { lang = i18n.defaultLanguage } = useParams();
const { lang = i18nUI.defaultLanguage } = useParams();
return (
<html lang="en" suppressHydrationWarning>
@@ -96,14 +114,12 @@ export function Layout({ children }: { children: React.ReactNode }) {
<Links />
</head>
<body className="flex flex-col min-h-screen">
<ReactRouterProvider>
<RootProvider
// [!code ++]
i18n={provider(lang)}
>
{children}
</RootProvider>
</ReactRouterProvider>
<RootProvider
// [!code ++]
i18n={i18nUI.provider(lang)}
>
{children}
</RootProvider>
<ScrollRestoration />
<Scripts />
</body>
@@ -114,20 +130,6 @@ export function Layout({ children }: { children: React.ReactNode }) {
### Pass Locale
Add `locale` parameter to `baseOptions()` and include `i18n` in props:
```tsx title="app/lib/layout.shared.tsx"
import { i18n } from '@/lib/i18n';
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
export function baseOptions(locale: string): BaseLayoutProps {
return {
i18n, // [!code ++]
// different props based on `locale`
};
}
```
Pass the locale to Fumadocs in your pages and layouts.
```ts tab="app/lib/source.ts"
@@ -18,6 +18,69 @@ export const i18n = defineI18n({
> See [available options](/docs/headless/internationalization/config) for i18n config.
### Translations
Define your translations in a file (e.g. `lib/layout.shared.tsx`), the English translations are used when `translations` is not specified.
You can also add `locale` parameter to `baseOptions()` for localized layout options:
```tsx title="lib/layout.shared.tsx"
import { i18n } from '@/lib/i18n';
import { defineI18nUI } from 'fumadocs-ui/i18n';
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
// [!code ++:11]
export const i18nUI = defineI18nUI(i18n, {
translations: {
en: {
displayName: 'English',
},
cn: {
displayName: 'Chinese',
search: '搜尋文檔',
},
},
});
// [!code highlight]
export function baseOptions(locale: string): BaseLayoutProps {
return {
i18n, // required for i18n [!code ++]
// different props based on `locale`
};
}
```
Pass translations to `<RootProvider />`:
```tsx title="src/routes/__root.tsx"
import { HeadContent, Scripts, useParams } from '@tanstack/react-router';
import * as React from 'react';
import { RootProvider } from 'fumadocs-ui/provider/tanstack';
import { i18nUI } from '@/lib/layout.shared';
function RootDocument({ children }: { children: React.ReactNode }) {
const { lang } = useParams({ strict: false });
return (
<html suppressHydrationWarning>
<head>
<HeadContent />
</head>
<body className="flex flex-col min-h-screen">
<RootProvider
// [!code ++]
i18n={i18nUI.provider(lang)}
>
{children}
</RootProvider>
<Scripts />
</body>
</html>
);
}
```
### Routing
Create a `$lang` directory under `routes`, and move all pages into it.
@@ -34,69 +97,8 @@ Create a `$lang` directory under `routes`, and move all pages into it.
</Folder>
</Files>
Provide UI translations and other config to `<RootProvider />`, the English translations are used as fallbacks.
```tsx title="src/routes/__root.tsx"
import { HeadContent, Scripts, useParams } from '@tanstack/react-router';
import * as React from 'react';
import { RootProvider } from 'fumadocs-ui/provider/base';
import { TanstackProvider } from 'fumadocs-core/framework/tanstack';
import { defineI18nUI } from 'fumadocs-ui/i18n';
import { i18n } from '@/lib/i18n';
// [!code ++:11]
const { provider } = defineI18nUI(i18n, {
translations: {
cn: {
displayName: 'Chinese',
search: 'Translated Content',
},
en: {
displayName: 'English',
},
},
});
function RootDocument({ children }: { children: React.ReactNode }) {
const { lang } = useParams({ strict: false });
return (
<html suppressHydrationWarning>
<head>
<HeadContent />
</head>
<body className="flex flex-col min-h-screen">
<TanstackProvider>
<RootProvider
// [!code ++]
i18n={provider(lang)}
>
{children}
</RootProvider>
</TanstackProvider>
<Scripts />
</body>
</html>
);
}
```
### Pass Locale
Add `locale` parameter to `baseOptions()` and include `i18n` in props:
```tsx title="src/lib/layout.shared.tsx"
import { i18n } from '@/lib/i18n';
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
export function baseOptions(locale: string): BaseLayoutProps {
return {
i18n, // [!code ++]
// different props based on `locale`
};
}
```
Pass the locale to Fumadocs in your pages and layouts.
```ts tab="src/lib/source.ts"
@@ -44,6 +44,9 @@ export default [
Create the following files:
<include cwd meta='tab="app/components/mdx.tsx"'>
../../examples/react-router-min/app/components/mdx.tsx
</include>
<include cwd meta='tab="app/lib/layout.shared.tsx"'>
../../examples/react-router-min/app/lib/layout.shared.tsx
</include>
@@ -29,8 +29,11 @@ Add the following to your Tailwind CSS file:
### Create Pages
Create the following routes:
Create the following files:
<include cwd meta='tab="components/mdx.tsx"'>
../../examples/tanstack-start-min/src/components/mdx.tsx
</include>
<include cwd meta='tab="lib/layout.shared.tsx"'>
../../examples/tanstack-start-min/src/lib/layout.shared.tsx
</include>
@@ -31,29 +31,32 @@ Add the following to your Tailwind CSS file:
Create a file for sharing layout props:
<include cwd meta='title="src/lib/layout.shared.tsx"'>
<include cwd meta='title="lib/layout.shared.tsx"'>
../../examples/waku/src/lib/layout.shared.tsx
</include>
Create the following routes:
Create the following files:
<include cwd meta='tab="src/pages/docs/_layout.tsx"'>
<include cwd meta='tab="components/mdx.tsx"'>
../../examples/waku/src/components/mdx.tsx
</include>
<include cwd meta='tab="pages/docs/_layout.tsx"'>
../../examples/waku/src/pages/docs/_layout.tsx
</include>
<include cwd meta='tab="src/pages/docs/[...slugs].tsx"'>
<include cwd meta='tab="pages/docs/[...slugs].tsx"'>
../../examples/waku/src/pages/docs/[...slugs].tsx
</include>
<include cwd meta='tab="src/pages/_api/api/search.ts"'>
<include cwd meta='tab="pages/_api/api/search.ts"'>
../../examples/waku/src/pages/_api/api/search.ts
</include>
Finally, wrap your entire app under Fumadocs providers:
<include cwd meta='tab="src/components/provider.tsx"'>
<include cwd meta='tab="components/provider.tsx"'>
../../examples/waku/src/components/provider.tsx
</include>
```tsx tab="src/pages/_layout.tsx"
```tsx tab="pages/_layout.tsx"
import '@/styles/globals.css';
import type { ReactNode } from 'react';
import { Provider } from '@/components/provider';
+11 -4
View File
@@ -1,8 +1,14 @@
import type { I18nProviderProps, Translations } from '@/contexts/i18n';
import type { I18nConfig } from 'fumadocs-core/i18n';
export type { I18nProviderProps, Translations };
export { defaultTranslations } from './contexts/i18n';
export { defaultTranslations, type I18nProviderProps, type Translations } from '@/contexts/i18n';
export interface I18nUIConfig<Languages extends string> extends I18nConfig<Languages> {
/**
* get i18n config for Fumadocs UI `<RootProvider i18n={config} />`.
*/
provider: (locale?: string) => I18nProviderProps;
}
export function defineI18nUI<Languages extends string>(
config: I18nConfig<Languages>,
@@ -11,11 +17,12 @@ export function defineI18nUI<Languages extends string>(
[K in Languages]?: Partial<Translations> & { displayName?: string };
};
},
) {
): I18nUIConfig<Languages> {
const { translations } = options;
return {
provider(locale: string = config.defaultLanguage): I18nProviderProps {
...config,
provider(locale = config.defaultLanguage) {
return {
locale,
translations: translations[locale as Languages],
+2 -1
View File
@@ -6,7 +6,8 @@
"moduleResolution": "bundler",
"paths": {
"@/*": ["./src/*"]
}
},
"noEmit": true
},
"include": ["scripts", "src", "test"],
"exclude": ["node_modules", "template", "dist", "eslint.config.mjs"]
+1 -1
View File
@@ -9,5 +9,5 @@
"module": "ESNext",
"target": "ES2020"
},
"exclude": ["node_modules", "eslint.config.mjs"]
"exclude": ["node_modules", "eslint.config.mjs", "dist"]
}
+11 -4
View File
@@ -1,8 +1,14 @@
import type { I18nProviderProps, Translations } from '@/contexts/i18n';
import type { I18nConfig } from 'fumadocs-core/i18n';
export type { I18nProviderProps, Translations };
export { defaultTranslations } from '@/contexts/i18n';
export { defaultTranslations, type I18nProviderProps, type Translations } from '@/contexts/i18n';
export interface I18nUIConfig<Languages extends string> extends I18nConfig<Languages> {
/**
* get i18n config for Fumadocs UI `<RootProvider i18n={config} />`.
*/
provider: (locale?: string) => I18nProviderProps;
}
export function defineI18nUI<Languages extends string>(
config: I18nConfig<Languages>,
@@ -11,11 +17,12 @@ export function defineI18nUI<Languages extends string>(
[K in Languages]?: Partial<Translations> & { displayName?: string };
};
},
) {
): I18nUIConfig<Languages> {
const { translations } = options;
return {
provider(locale: string = config.defaultLanguage): I18nProviderProps {
...config,
provider(locale = config.defaultLanguage) {
return {
locale,
translations: translations[locale as Languages],