mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
Core & UI: Support type-safe i18n config
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
---
|
||||
'fumadocs-core': minor
|
||||
'fumadocs-ui': minor
|
||||
---
|
||||
|
||||
**Support type-safe i18n config**
|
||||
|
||||
```ts
|
||||
// lib/source.ts
|
||||
import { defineI18n } from 'fumadocs-core/i18n';
|
||||
|
||||
export const i18n = defineI18n({
|
||||
defaultLanguage: 'en',
|
||||
languages: ['en', 'cn'],
|
||||
});
|
||||
```
|
||||
|
||||
```tsx
|
||||
// root layout
|
||||
import { defineI18nUI } from 'fumadocs-ui/i18n';
|
||||
import { i18n } from '@/lib/i18n';
|
||||
|
||||
const { provider } = defineI18nUI(i18n, {
|
||||
translations: {
|
||||
cn: {
|
||||
displayName: 'Chinese',
|
||||
search: 'Translated Content'
|
||||
},
|
||||
en: {
|
||||
displayName: 'English'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<RootProvider i18n={provider(lang)}>{children}</RootProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Although optional, we highly recommend you to refactor the import to i18n middleware:
|
||||
|
||||
```ts
|
||||
// here!
|
||||
import { createI18nMiddleware } from 'fumadocs-core/i18n/middleware';
|
||||
import { i18n } from '@/lib/i18n';
|
||||
|
||||
export default createI18nMiddleware(i18n);
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './index';
|
||||
export * from './middleware';
|
||||
@@ -1,17 +1,15 @@
|
||||
export * from './middleware';
|
||||
|
||||
export interface I18nConfig {
|
||||
export interface I18nConfig<Languages extends string = string> {
|
||||
/**
|
||||
* Supported locale codes.
|
||||
*
|
||||
* A page tree will be built for each language.
|
||||
*/
|
||||
languages: string[];
|
||||
languages: Languages[];
|
||||
|
||||
/**
|
||||
* Default locale if not specified
|
||||
*/
|
||||
defaultLanguage: string;
|
||||
defaultLanguage: Languages;
|
||||
|
||||
/**
|
||||
* Don't show the locale prefix on URL.
|
||||
@@ -33,3 +31,9 @@ export interface I18nConfig {
|
||||
*/
|
||||
parser?: 'dot' | 'dir';
|
||||
}
|
||||
|
||||
export function defineI18n<Languages extends string>(
|
||||
config: I18nConfig<Languages>,
|
||||
): I18nConfig<Languages> {
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ import {
|
||||
type Page,
|
||||
} from '@/source';
|
||||
import { type StructuredData } from '@/mdx-plugins';
|
||||
import { type LocaleMap } from '@/search/orama/create-i18n';
|
||||
import { basename, extname } from '@/source/path';
|
||||
import type { I18nConfig } from '@/i18n';
|
||||
import type { Language } from '@orama/orama';
|
||||
|
||||
function pageToIndex(page: Page): AdvancedIndex {
|
||||
if (!('structuredData' in page.data)) {
|
||||
@@ -36,14 +37,21 @@ function pageToIndex(page: Page): AdvancedIndex {
|
||||
};
|
||||
}
|
||||
|
||||
interface Options<Page = unknown> extends Omit<AdvancedOptions, 'indexes'> {
|
||||
localeMap?: LocaleMap<Partial<AdvancedOptions>>;
|
||||
buildIndex?: (page: Page) => AdvancedIndex;
|
||||
interface Options<S extends LoaderOutput<LoaderConfig>>
|
||||
extends Omit<AdvancedOptions, 'indexes'> {
|
||||
localeMap?: {
|
||||
[K in S extends LoaderOutput<infer C>
|
||||
? C['i18n'] extends I18nConfig<infer Languages>
|
||||
? Languages
|
||||
: string
|
||||
: string]?: Partial<AdvancedOptions> | Language;
|
||||
};
|
||||
buildIndex?: (page: InferPageType<S>) => AdvancedIndex;
|
||||
}
|
||||
|
||||
export function createFromSource<S extends LoaderOutput<LoaderConfig>>(
|
||||
source: S,
|
||||
options?: Options<InferPageType<S>>,
|
||||
options?: Options<S>,
|
||||
): SearchAPI;
|
||||
|
||||
/**
|
||||
@@ -52,17 +60,17 @@ export function createFromSource<S extends LoaderOutput<LoaderConfig>>(
|
||||
export function createFromSource<S extends LoaderOutput<LoaderConfig>>(
|
||||
source: S,
|
||||
pageToIndexFn?: (page: InferPageType<S>) => AdvancedIndex,
|
||||
options?: Omit<Options, 'buildIndex'>,
|
||||
options?: Omit<Options<S>, 'buildIndex'>,
|
||||
): SearchAPI;
|
||||
|
||||
export function createFromSource<S extends LoaderOutput<LoaderConfig>>(
|
||||
source: S,
|
||||
_buildIndexOrOptions:
|
||||
| ((page: InferPageType<S>) => AdvancedIndex)
|
||||
| Options<InferPageType<S>> = pageToIndex,
|
||||
_options?: Omit<Options, 'buildIndex'>,
|
||||
| Options<S> = pageToIndex,
|
||||
_options?: Omit<Options<S>, 'buildIndex'>,
|
||||
): SearchAPI {
|
||||
const options: Options<InferPageType<S>> = {
|
||||
const options: Options<S> = {
|
||||
...(typeof _buildIndexOrOptions === 'function'
|
||||
? {
|
||||
buildIndex: _buildIndexOrOptions,
|
||||
|
||||
@@ -14,8 +14,6 @@ import { createEndpoint } from '@/search/orama/create-endpoint';
|
||||
import { type I18nConfig } from '@/i18n';
|
||||
import { STEMMERS } from '@/search/orama/_stemmers';
|
||||
|
||||
export type LocaleMap<O> = Record<string, Language | O>;
|
||||
|
||||
type I18nOptions<O extends SimpleOptions | AdvancedOptions, Idx> = Omit<
|
||||
O,
|
||||
'language' | 'indexes'
|
||||
@@ -25,7 +23,7 @@ type I18nOptions<O extends SimpleOptions | AdvancedOptions, Idx> = Omit<
|
||||
/**
|
||||
* Map locale name from i18n config to Orama compatible `language` or options
|
||||
*/
|
||||
localeMap?: LocaleMap<Partial<O>>;
|
||||
localeMap?: Record<string, Language | Partial<O> | undefined>;
|
||||
|
||||
indexes: WithLocale<Idx>[] | Dynamic<WithLocale<Idx>>;
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ import { joinPath } from '@/utils/path';
|
||||
|
||||
export interface LoaderConfig {
|
||||
source: SourceConfig;
|
||||
i18n: boolean;
|
||||
i18n: I18nConfig | undefined;
|
||||
}
|
||||
|
||||
export interface SourceConfig {
|
||||
@@ -126,7 +126,7 @@ export interface LanguageEntry<Data = PageData> {
|
||||
}
|
||||
|
||||
export interface LoaderOutput<Config extends LoaderConfig> {
|
||||
pageTree: Config['i18n'] extends true
|
||||
pageTree: Config['i18n'] extends I18nConfig
|
||||
? Record<string, PageTree.Root>
|
||||
: PageTree.Root;
|
||||
|
||||
@@ -279,7 +279,7 @@ export function loader<
|
||||
options: LoaderOptions<Config, I18n>,
|
||||
): LoaderOutput<{
|
||||
source: Config;
|
||||
i18n: I18n extends I18nConfig ? true : false;
|
||||
i18n: I18n;
|
||||
}> {
|
||||
// @ts-expect-error -- forced type cast
|
||||
return createOutput(options);
|
||||
|
||||
@@ -19,7 +19,7 @@ export default defineConfig({
|
||||
'src/utils/use-on-change.ts',
|
||||
'src/utils/use-effect-event.ts',
|
||||
'src/utils/use-media-query.ts',
|
||||
'src/i18n/index.ts',
|
||||
'src/i18n/*.ts',
|
||||
'src/highlight/index.ts',
|
||||
'src/highlight/client.tsx',
|
||||
'src/framework/*',
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useEffectEvent } from 'fumadocs-core/utils/use-effect-event';
|
||||
import { useMemo } from 'react';
|
||||
import { defaultTranslations, I18nContext } from '@/contexts/i18n';
|
||||
import {
|
||||
defaultTranslations,
|
||||
I18nContext,
|
||||
type Translations,
|
||||
} from '@/contexts/i18n';
|
||||
import type { I18nProviderProps } from './provider/base';
|
||||
import type { I18nConfig } from 'fumadocs-core/i18n';
|
||||
|
||||
export type { I18nProviderProps };
|
||||
export { defaultTranslations, type Translations } from './contexts/i18n';
|
||||
export type { I18nProviderProps, Translations };
|
||||
export { defaultTranslations } from './contexts/i18n';
|
||||
|
||||
// TODO: remove next major
|
||||
/**
|
||||
@@ -62,3 +67,27 @@ export function I18nProvider({
|
||||
</I18nContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function defineI18nUI<Languages extends string>(
|
||||
config: I18nConfig<Languages>,
|
||||
options: {
|
||||
translations: {
|
||||
[K in Languages]?: Partial<Translations> & { displayName?: string };
|
||||
};
|
||||
},
|
||||
) {
|
||||
const { translations } = options;
|
||||
|
||||
return {
|
||||
provider(locale: string = config.defaultLanguage): I18nProviderProps {
|
||||
return {
|
||||
locale,
|
||||
translations: translations[locale as Languages],
|
||||
locales: config.languages.map((locale) => ({
|
||||
locale,
|
||||
name: translations[locale]?.displayName ?? locale,
|
||||
})),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user