Skip to content

Commit

Permalink
Improve i18n examples
Browse files Browse the repository at this point in the history
  • Loading branch information
fuma-nama committed Dec 27, 2024
1 parent 96b400e commit 969da26
Show file tree
Hide file tree
Showing 21 changed files with 283 additions and 231 deletions.
7 changes: 7 additions & 0 deletions .changeset/rare-garlics-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'fumadocs-core': patch
'@fumadocs/cli': patch
'fumadocs-ui': patch
---

Improve i18n api
137 changes: 81 additions & 56 deletions apps/docs/content/docs/ui/internationalization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,6 @@ export const source = loader({
});
```

Update the usages to your source to include a locale code:

```ts
import { source } from '@/lib/source';

// get page tree
source.pageTree[params.lang];

// get page
source.getPage(params.slug, params.lang);

// get pages
source.getPages(params.lang);
```

Note that without providing a locale code, it uses your default locale instead.
You can see [Source API](/docs/headless/source-api) for other usages.

### Middleware

Create a middleware that redirects users to appropriate locale.
Expand All @@ -83,28 +65,57 @@ Create a middleware that redirects users to appropriate locale.

See [Middleware](/docs/headless/internationalization#middleware) for customisable options.

### Root Layout
### Routing

Create a dynamic route `/app/[lang]`, and move all special files from `/app` to
the folder.

A `I18nProvider` is needed for localization. Wrap the root provider inside your I18n provider.
A `I18nProvider` is needed for localization.
Wrap the root provider inside your I18n provider, and provide available languages & translations to it.

Note that only English translations are provided by default.

```tsx
import { RootProvider } from 'fumadocs-ui/provider';
import { I18nProvider } from 'fumadocs-ui/i18n';
import { I18nProvider, type Translations } from 'fumadocs-ui/i18n';

const cn: Translations = {
search: 'Translated Content',
// other props
};

export default function RootLayout({
// available languages that will be displayed on UI
// make sure `locale` is consistent with your i18n config
const locales = [
{
name: 'English',
locale: 'en',
},
{
name: 'Chinese',
locale: 'cn',
},
];

export default async function RootLayout({
params,
children,
}: {
params: { lang: string };
params: Promise<{ lang: string }>;
children: React.ReactNode;
}) {
return (
<html lang={params.lang}>
<html lang={(await params).lang}>
<body>
<I18nProvider locale={params.lang}>
<I18nProvider
locale={(await params).lang}
locales={locales}
translations={
{
cn,
}[(await params).lang]
}
>
<RootProvider>{children}</RootProvider>
</I18nProvider>
</body>
Expand All @@ -113,47 +124,61 @@ export default function RootLayout({
}
```

### Writing Documents
### Source

see [Page Conventions](/docs/ui/page-conventions#internationalization) to learn how to organize your documents.
Update the usages to your source to include a locale code:

### Search
```ts
import { source } from '@/lib/source';

Configure i18n on your search solution.
// get page tree
source.pageTree[params.lang];

You don't need further changes if you're using the `createFromSource` shortcut.
// get page
source.getPage(params.slug, params.lang);

For the built-in Orama search, see [Search I18n](/docs/headless/search/orama#internationalization).
// get pages
source.getPages(params.lang);
```

### Adding Translations
like:

We only provide English translation by default, you have to pass your translations to the provider.
```tsx title="app/[lang]/layout.tsx"
import { source } from '@/lib/source';
import { DocsLayout } from 'fumadocs-ui/docs';
import type { ReactNode } from 'react';

```tsx
import { I18nProvider } from 'fumadocs-ui/i18n';

<I18nProvider
locales={[
{
name: 'English',
locale: 'en',
},
{
name: 'Chinese',
locale: 'cn',
},
]}
translations={
{
cn: {
search: 'Translated Content',
},
}[locale]
}
// other props
/>;
export default async function Layout({
params,
children,
}: {
params: Promise<{ lang: string }>;
children: ReactNode;
}) {
const pageTree = source.pageTree[(await params).lang];

return <DocsLayout pageTree={pageTree}>{children}</DocsLayout>;
}
```

Note that without providing a locale code, it uses your default locale instead.
You can see [Source API](/docs/headless/source-api) for other usages.

### Writing Documents

see [Page Conventions](/docs/ui/page-conventions#internationalization) to learn how to organize your documents.

### Search

Configure i18n on your search solution.

- Built-in Search (Orama):
- For `createFromSource` and most languages, no further changes are needed.
- For special languages like Chinese & Japanese, they require additional config.
See [Orama I18n](/docs/headless/search/orama#internationalization) guide.
- Cloud Solutions (e.g. Algolia):
- They usually have official support for multilingual.

### Add Language Switch

To allow users changing their language, enable `i18n` on your layouts.
Expand Down
2 changes: 1 addition & 1 deletion examples/i18n/app/api/search/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createFromSource } from 'fumadocs-core/search/server';
import { createTokenizer } from '@orama/tokenizers/mandarin';
import { stopwords } from '@orama/stopwords/mandarin';

export const { GET, search } = createFromSource(source, undefined, {
export const { GET } = createFromSource(source, undefined, {
localeMap: {
// the prop name should be its locale code in your i18n config, (e.g. `cn`)
cn: {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/plugins/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ export const i18nPlugin: Plugin = {
type: 'code',
title: 'page.tsx',
code: `
export default function Page({
export default async function Page({
params,
}: {
${picocolors.underline(picocolors.bold('params: { lang: string; slug?: string[] };'))}
${picocolors.underline(picocolors.bold('params: Promise<{ lang: string; slug?: string[] }>'))}
})
`.trim(),
},
Expand Down
15 changes: 10 additions & 5 deletions packages/core/src/search/client/static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
type RawData,
} from '@orama/orama';
import { type SortedResult } from '@/server';
import { searchSimple } from '@/search/search/simple';
import { searchAdvanced } from '@/search/search/advanced';
import { type advancedSchema } from '@/search/create-db';
import { type schema } from '@/search/create-db-simple';
import { searchSimple } from '@/search/orama/search/simple';
import { searchAdvanced } from '@/search/orama/search/advanced';
import {
type advancedSchema,
type simpleSchema,
} from '@/search/orama/create-db';

export interface StaticOptions {
/**
Expand Down Expand Up @@ -86,7 +88,10 @@ export function createStaticClient({

if (!cached) return [];
if (cached.type === 'simple')
return searchSimple(cached as unknown as Orama<typeof schema>, query);
return searchSimple(
cached as unknown as Orama<typeof simpleSchema>,
query,
);

return searchAdvanced(
cached.db as Orama<typeof advancedSchema>,
Expand Down
44 changes: 0 additions & 44 deletions packages/core/src/search/create-db-simple.ts

This file was deleted.

File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
type PartialSchemaDeep,
type TypedDocument,
} from '@orama/orama';
import { type AdvancedOptions } from '@/search/server';
import { type AdvancedOptions, type SimpleOptions } from '@/search/server';

export type AdvancedDocument = TypedDocument<Orama<typeof advancedSchema>>;
export const advancedSchema = {
Expand Down Expand Up @@ -85,3 +85,40 @@ export async function createDB({
await insertMultiple(db, mapTo);
return db;
}

export type SimpleDocument = TypedDocument<Orama<typeof simpleSchema>>;
export const simpleSchema = {
url: 'string',
title: 'string',
description: 'string',
content: 'string',
keywords: 'string',
} as const;

export async function createDBSimple({
indexes,
tokenizer,
...rest
}: SimpleOptions): Promise<Orama<typeof simpleSchema>> {
const items = typeof indexes === 'function' ? await indexes() : indexes;
const db = (await create({
schema: simpleSchema,
components: {
tokenizer,
},
...rest,
})) as unknown as Orama<typeof simpleSchema>;

await insertMultiple(
db,
items.map((page) => ({
title: page.title,
description: page.description,
url: page.url,
content: page.content,
keywords: page.keywords,
})),
);

return db;
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
type Page,
} from '@/source';
import { type StructuredData } from '@/mdx-plugins';
import { type LocaleMap } from '@/search/i18n-api';
import { type LocaleMap } from '@/search/orama/create-i18n';

function pageToIndex(page: Page): AdvancedIndex {
if (!('structuredData' in page.data)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {
type SearchServer,
type SimpleOptions,
} from '@/search/server';
import { createEndpoint } from '@/search/create-endpoint';
import { createEndpoint } from '@/search/orama/create-endpoint';
import { type I18nConfig } from '@/i18n';
import { STEMMERS } from '@/search/_stemmers';
import { STEMMERS } from '@/search/orama/_stemmers';

export type LocaleMap<O> = Record<string, Language | O>;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { getByID, type Orama, search, type SearchParams } from '@orama/orama';
import { type AdvancedDocument, type advancedSchema } from '@/search/create-db';
import {
type AdvancedDocument,
type advancedSchema,
} from '@/search/orama/create-db';
import { removeUndefined } from '@/utils/remove-undefined';
import type { SortedResult } from '@/server';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { type Orama, search, type SearchParams } from '@orama/orama';
import type { SortedResult } from '@/server';
import { type schema, type SimpleDocument } from '@/search/create-db-simple';
import {
type simpleSchema,
type SimpleDocument,
} from '@/search/orama/create-db';

export async function searchSimple(
db: Orama<typeof schema>,
db: Orama<typeof simpleSchema>,
query: string,
params: Partial<SearchParams<Orama<typeof schema>, SimpleDocument>> = {},
params: Partial<
SearchParams<Orama<typeof simpleSchema>, SimpleDocument>
> = {},
): Promise<SortedResult[]> {
const result = await search(db, {
term: query,
Expand Down
Loading

0 comments on commit 969da26

Please sign in to comment.