mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
Docs: Simplify i18n setup
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["scripts", "src", "test"],
|
||||
"exclude": ["node_modules", "template", "dist", "eslint.config.mjs"]
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"module": "ESNext",
|
||||
"target": "ES2020"
|
||||
},
|
||||
"exclude": ["node_modules", "eslint.config.mjs"]
|
||||
"exclude": ["node_modules", "eslint.config.mjs", "dist"]
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user