mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 05:23:40 +00:00
Improve i18n examples
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'fumadocs-core': patch
|
||||
'@fumadocs/cli': patch
|
||||
'fumadocs-ui': patch
|
||||
---
|
||||
|
||||
Improve i18n api
|
||||
@@ -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.
|
||||
@@ -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';
|
||||
|
||||
export default function RootLayout({
|
||||
const cn: Translations = {
|
||||
search: 'Translated Content',
|
||||
// other props
|
||||
};
|
||||
|
||||
// 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>
|
||||
@@ -113,6 +124,46 @@ export default function RootLayout({
|
||||
}
|
||||
```
|
||||
|
||||
### Source
|
||||
|
||||
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);
|
||||
```
|
||||
|
||||
like:
|
||||
|
||||
```tsx title="app/[lang]/layout.tsx"
|
||||
import { source } from '@/lib/source';
|
||||
import { DocsLayout } from 'fumadocs-ui/docs';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
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.
|
||||
@@ -121,38 +172,12 @@ see [Page Conventions](/docs/ui/page-conventions#internationalization) to learn
|
||||
|
||||
Configure i18n on your search solution.
|
||||
|
||||
You don't need further changes if you're using the `createFromSource` shortcut.
|
||||
|
||||
For the built-in Orama search, see [Search I18n](/docs/headless/search/orama#internationalization).
|
||||
|
||||
### Adding Translations
|
||||
|
||||
We only provide English translation by default, you have to pass your translations to the provider.
|
||||
|
||||
```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
|
||||
/>;
|
||||
```
|
||||
- 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
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
/**
|
||||
@@ -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>,
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import {
|
||||
create,
|
||||
insertMultiple,
|
||||
type Orama,
|
||||
type TypedDocument,
|
||||
} from '@orama/orama';
|
||||
import { type SimpleOptions } from '@/search/server';
|
||||
|
||||
export type SimpleDocument = TypedDocument<Orama<typeof schema>>;
|
||||
export const schema = {
|
||||
url: 'string',
|
||||
title: 'string',
|
||||
description: 'string',
|
||||
content: 'string',
|
||||
keywords: 'string',
|
||||
} as const;
|
||||
|
||||
export async function createDBSimple({
|
||||
indexes,
|
||||
tokenizer,
|
||||
...rest
|
||||
}: SimpleOptions): Promise<Orama<typeof schema>> {
|
||||
const items = typeof indexes === 'function' ? await indexes() : indexes;
|
||||
const db = (await create({
|
||||
schema,
|
||||
components: {
|
||||
tokenizer,
|
||||
},
|
||||
...rest,
|
||||
})) as unknown as Orama<typeof schema>;
|
||||
|
||||
await insertMultiple(
|
||||
db,
|
||||
items.map((page) => ({
|
||||
title: page.title,
|
||||
description: page.description,
|
||||
url: page.url,
|
||||
content: page.content,
|
||||
keywords: page.keywords,
|
||||
})),
|
||||
);
|
||||
|
||||
return db;
|
||||
}
|
||||
+38
-1
@@ -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 = {
|
||||
@@ -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;
|
||||
}
|
||||
+1
-1
@@ -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)) {
|
||||
+2
-2
@@ -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>;
|
||||
|
||||
+4
-1
@@ -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';
|
||||
|
||||
+8
-3
@@ -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,
|
||||
@@ -2,19 +2,17 @@ import { type Orama, type SearchParams, save, create } from '@orama/orama';
|
||||
import { type NextRequest } from 'next/server';
|
||||
import type { StructuredData } from '@/mdx-plugins/remark-structure';
|
||||
import type { SortedResult } from '@/server/types';
|
||||
import { createEndpoint } from '@/search/create-endpoint';
|
||||
import { createEndpoint } from '@/search/orama/create-endpoint';
|
||||
import {
|
||||
type AdvancedDocument,
|
||||
type advancedSchema,
|
||||
createDB,
|
||||
} from '@/search/create-db';
|
||||
import { searchSimple } from '@/search/search/simple';
|
||||
import { searchAdvanced } from '@/search/search/advanced';
|
||||
import {
|
||||
createDBSimple,
|
||||
type schema,
|
||||
type SimpleDocument,
|
||||
} from './create-db-simple';
|
||||
simpleSchema,
|
||||
} from '@/search/orama/create-db';
|
||||
import { searchSimple } from '@/search/orama/search/simple';
|
||||
import { searchAdvanced } from '@/search/orama/search/advanced';
|
||||
|
||||
export interface SearchServer {
|
||||
search: (
|
||||
@@ -57,7 +55,7 @@ export interface SimpleOptions extends SharedOptions {
|
||||
/**
|
||||
* Customise search options on server
|
||||
*/
|
||||
search?: Partial<SearchParams<Orama<typeof schema>, SimpleDocument>>;
|
||||
search?: Partial<SearchParams<Orama<typeof simpleSchema>, SimpleDocument>>;
|
||||
}
|
||||
|
||||
export interface AdvancedOptions extends SharedOptions {
|
||||
@@ -133,7 +131,7 @@ export function initAdvancedSearch(options: AdvancedOptions): SearchServer {
|
||||
async export() {
|
||||
return {
|
||||
type: 'advanced',
|
||||
...save(await get),
|
||||
...(await save(await get)),
|
||||
};
|
||||
},
|
||||
async search(query, searchOptions) {
|
||||
@@ -144,5 +142,5 @@ export function initAdvancedSearch(options: AdvancedOptions): SearchServer {
|
||||
};
|
||||
}
|
||||
|
||||
export { createFromSource } from './create-from-source';
|
||||
export { createI18nSearchAPI } from './i18n-api';
|
||||
export { createFromSource } from './orama/create-from-source';
|
||||
export { createI18nSearchAPI } from './orama/create-i18n';
|
||||
|
||||
@@ -6,8 +6,12 @@ import {
|
||||
} from 'fumadocs-core/search/client';
|
||||
import { type ReactNode, useState } from 'react';
|
||||
import { useOnChange } from 'fumadocs-core/utils/use-on-change';
|
||||
import { SearchDialog, type SharedProps } from './search';
|
||||
import { type TagItem, TagsList } from './tag-list';
|
||||
import {
|
||||
SearchDialog,
|
||||
type SharedProps,
|
||||
TagsList,
|
||||
type TagItem,
|
||||
} from './search';
|
||||
|
||||
export interface AlgoliaSearchDialogProps extends SharedProps {
|
||||
index: AlgoliaOptions['index'];
|
||||
|
||||
@@ -4,8 +4,12 @@ import { useDocsSearch } from 'fumadocs-core/search/client';
|
||||
import { type ReactNode, useState } from 'react';
|
||||
import { useOnChange } from 'fumadocs-core/utils/use-on-change';
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import { SearchDialog, type SharedProps } from './search';
|
||||
import { type TagItem, TagsList } from './tag-list';
|
||||
import {
|
||||
SearchDialog,
|
||||
type SharedProps,
|
||||
type TagItem,
|
||||
TagsList,
|
||||
} from './search';
|
||||
|
||||
export interface DefaultSearchDialogProps extends SharedProps {
|
||||
/**
|
||||
|
||||
@@ -6,8 +6,12 @@ import {
|
||||
} from 'fumadocs-core/search/client';
|
||||
import { type ReactNode, useState } from 'react';
|
||||
import { useOnChange } from 'fumadocs-core/utils/use-on-change';
|
||||
import { SearchDialog, type SharedProps } from './search';
|
||||
import { type TagItem, TagsList } from './tag-list';
|
||||
import {
|
||||
SearchDialog,
|
||||
type SharedProps,
|
||||
type TagItem,
|
||||
TagsList,
|
||||
} from './search';
|
||||
|
||||
export interface OramaSearchDialogProps extends SharedProps {
|
||||
client: OramaCloudOptions['client'];
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
useRef,
|
||||
type ButtonHTMLAttributes,
|
||||
useCallback,
|
||||
type HTMLAttributes,
|
||||
} from 'react';
|
||||
import { useI18n } from '@/contexts/i18n';
|
||||
import { cn } from '@/utils/cn';
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
DialogTitle,
|
||||
} from '@radix-ui/react-dialog';
|
||||
import type { SortedResult } from 'fumadocs-core/server';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
export type SearchLink = [name: string, href: string];
|
||||
|
||||
@@ -287,3 +289,68 @@ function CommandItem({
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export interface TagItem {
|
||||
name: string;
|
||||
value: string | undefined;
|
||||
|
||||
props?: HTMLAttributes<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export interface TagsListProps extends HTMLAttributes<HTMLDivElement> {
|
||||
tag?: string;
|
||||
onTagChange: (tag: string | undefined) => void;
|
||||
allowClear?: boolean;
|
||||
|
||||
items: TagItem[];
|
||||
}
|
||||
|
||||
const itemVariants = cva(
|
||||
'rounded-md border px-2 py-0.5 text-xs font-medium text-fd-muted-foreground transition-colors',
|
||||
{
|
||||
variants: {
|
||||
active: {
|
||||
true: 'bg-fd-accent text-fd-accent-foreground',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export function TagsList({
|
||||
tag,
|
||||
onTagChange,
|
||||
items,
|
||||
allowClear,
|
||||
...props
|
||||
}: TagsListProps) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={cn('flex flex-row items-center gap-1', props.className)}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
data-active={tag === item.value}
|
||||
className={cn(
|
||||
itemVariants({ active: tag === item.value }),
|
||||
item.props?.className,
|
||||
)}
|
||||
onClick={() => {
|
||||
if (tag === item.value && allowClear) {
|
||||
onTagChange(undefined);
|
||||
} else {
|
||||
onTagChange(item.value);
|
||||
}
|
||||
}}
|
||||
tabIndex={-1}
|
||||
{...item.props}
|
||||
>
|
||||
{item.name}
|
||||
</button>
|
||||
))}
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
export interface TagItem {
|
||||
name: string;
|
||||
value: string | undefined;
|
||||
|
||||
props?: HTMLAttributes<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export interface TagsListProps extends HTMLAttributes<HTMLDivElement> {
|
||||
tag?: string;
|
||||
onTagChange: (tag: string | undefined) => void;
|
||||
allowClear?: boolean;
|
||||
|
||||
items: TagItem[];
|
||||
}
|
||||
|
||||
const itemVariants = cva(
|
||||
'rounded-md border px-2 py-0.5 text-xs font-medium text-fd-muted-foreground transition-colors',
|
||||
{
|
||||
variants: {
|
||||
active: {
|
||||
true: 'bg-fd-accent text-fd-accent-foreground',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export function TagsList({
|
||||
tag,
|
||||
onTagChange,
|
||||
items,
|
||||
allowClear,
|
||||
...props
|
||||
}: TagsListProps) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={cn('flex flex-row items-center gap-1', props.className)}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
data-active={tag === item.value}
|
||||
className={cn(
|
||||
itemVariants({ active: tag === item.value }),
|
||||
item.props?.className,
|
||||
)}
|
||||
onClick={() => {
|
||||
if (tag === item.value && allowClear) {
|
||||
onTagChange(undefined);
|
||||
} else {
|
||||
onTagChange(item.value);
|
||||
}
|
||||
}}
|
||||
tabIndex={-1}
|
||||
{...item.props}
|
||||
>
|
||||
{item.name}
|
||||
</button>
|
||||
))}
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -28,19 +28,21 @@ interface I18nContextType {
|
||||
locales?: LocaleItem[];
|
||||
}
|
||||
|
||||
export const defaultTranslations: Translations = {
|
||||
search: 'Search',
|
||||
searchNoResult: 'No results found',
|
||||
toc: 'On this page',
|
||||
tocNoHeadings: 'No Headings',
|
||||
lastUpdate: 'Last updated on',
|
||||
chooseLanguage: 'Choose a language',
|
||||
nextPage: 'Next',
|
||||
previousPage: 'Previous',
|
||||
chooseTheme: 'Theme',
|
||||
editOnGithub: 'Edit on GitHub',
|
||||
};
|
||||
|
||||
export const I18nContext = createContext<I18nContextType>({
|
||||
text: {
|
||||
search: 'Search',
|
||||
searchNoResult: 'No results found',
|
||||
toc: 'On this page',
|
||||
tocNoHeadings: 'No Headings',
|
||||
lastUpdate: 'Last updated on',
|
||||
chooseLanguage: 'Choose a language',
|
||||
nextPage: 'Next',
|
||||
previousPage: 'Previous',
|
||||
chooseTheme: 'Theme',
|
||||
editOnGithub: 'Edit on GitHub',
|
||||
},
|
||||
text: defaultTranslations,
|
||||
});
|
||||
|
||||
export function I18nLabel(props: { label: keyof Translations }): string {
|
||||
|
||||
+21
-18
@@ -1,11 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, type ReactNode } from 'react';
|
||||
import { useCallback, type ReactNode, useRef } from 'react';
|
||||
import { useRouter, usePathname } from 'next/navigation';
|
||||
import {
|
||||
useI18n,
|
||||
type Translations,
|
||||
I18nContext,
|
||||
defaultTranslations,
|
||||
type LocaleItem,
|
||||
} from './contexts/i18n';
|
||||
|
||||
@@ -40,24 +41,26 @@ export function I18nProvider({
|
||||
}: I18nProviderProps) {
|
||||
const context = useI18n();
|
||||
const router = useRouter();
|
||||
const segments = usePathname()
|
||||
.split('/')
|
||||
.filter((v) => v.length > 0);
|
||||
const pathname = usePathname();
|
||||
|
||||
const onChange = useCallback(
|
||||
(v: string) => {
|
||||
// If locale prefix hidden
|
||||
if (segments[0] !== locale) {
|
||||
segments.unshift(v);
|
||||
} else {
|
||||
segments[0] = v;
|
||||
}
|
||||
const onChangeCallback = (locale: string) => {
|
||||
const segments = pathname.split('/').filter((v) => v.length > 0);
|
||||
|
||||
router.push(`/${segments.join('/')}`);
|
||||
router.refresh();
|
||||
},
|
||||
[locale, segments, router],
|
||||
);
|
||||
// If locale prefix hidden
|
||||
if (segments[0] !== locale) {
|
||||
segments.unshift(locale);
|
||||
} else {
|
||||
segments[0] = locale;
|
||||
}
|
||||
|
||||
router.push(`/${segments.join('/')}`);
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
const onChangeRef = useRef(onChangeCallback);
|
||||
onChangeRef.current = onChangeCallback;
|
||||
|
||||
const onChange = useCallback((v: string) => onChangeRef.current(v), []);
|
||||
|
||||
return (
|
||||
<I18nContext.Provider
|
||||
@@ -76,4 +79,4 @@ export function I18nProvider({
|
||||
);
|
||||
}
|
||||
|
||||
export { type Translations };
|
||||
export { defaultTranslations, type Translations };
|
||||
|
||||
Reference in New Issue
Block a user