Skip to Content
Fumadocs

Internationalization

Support multiple languages in your Astro docs.

Fumadocs CLI

npx @fumadocs/cli init --i18n sets up a new docs with i18n, or configure it manually below.

Overview

Configure i18n routing on both Astro and Fumadocs.

Fumadocs handles i18n for the docs content and sidebar. Internationalize the rest of your app with your own i18n setup.

Setup

Define the i18n config in a file. This guide imports it as @/lib/i18n.

src/lib/i18n.ts
import { defineI18n } from 'fumadocs-core/i18n';

export const i18n = defineI18n({
  defaultLanguage: 'en',
  languages: ['cn', 'en'],
});

See available options for i18n config.

Routing

Add a [...lang] prefix to your pages. The rest parameter lets the default language have no prefix (e.g. /docs).

index.astro
[...slug].astro
search.ts

Enable hideLocale, so the URLs generated by Fumadocs match these routes:

src/lib/i18n.ts
export const i18n = defineI18n({
  defaultLanguage: 'en',
  languages: ['cn', 'en'],
  hideLocale: 'default-locale', 
});

Translations

Define your translations in lib/layout.shared.tsx. See Translations for more details, including the official language packs.

You can also add a locale parameter to baseOptions() for localized layout options:

src/lib/layout.shared.tsx
import { i18n } from '@/lib/i18n';
import { uiTranslations } from 'fumadocs-ui/i18n';
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';

export const translations = i18n
  .translations()
  .extend(uiTranslations());

export function baseOptions(): BaseLayoutProps { 
export function baseOptions(locale: string): BaseLayoutProps { 
  return {
    nav: {
      title: 'My App',
      url: locale === i18n.defaultLanguage ? '/' : `/${locale}`, 
    },
  };
}

Pass translations to <Provider />. The MDX content is an island of its own, so it needs a provider too:

import { navigate } from 'astro:transitions/client';
import type { ReactNode } from 'react';
import { RootProvider } from 'fumadocs-ui/provider/astro';
import { i18nProvider } from 'fumadocs-ui/i18n';
import { translations } from '@/lib/layout.shared';
import SearchDialog from '@/components/search';

export interface ProviderProps {
  lang: string; 
  pathname: string;
  params: Record<string, string | undefined>;
  children?: ReactNode;
}

export function Provider({ lang, pathname, params, children }: ProviderProps) {
  return (
    <RootProvider
      pathname={pathname}
      params={params}
      navigate={navigate}
      i18n={i18nProvider(translations, lang)}
      search={{ SearchDialog }}
    >
      {children}
    </RootProvider>
  );
}

/**
 * For the MDX content, which is an island of its own.
 */
export function ContentProvider({ lang, pathname, params, children }: ProviderProps) {
  return (
    <RootProvider
      pathname={pathname}
      params={params}
      navigate={navigate}
      i18n={i18nProvider(translations, lang)}
      theme={{ enabled: false }}
      search={{ enabled: false }}
    >
      {children}
    </RootProvider>
  );
}

Pass Locale

Pass the locale to Fumadocs in your source, layouts and pages.

import { i18n } from '@/lib/i18n'; 

export const source = loader({
  source: toFumadocsSource(await getCollection('docs'), await getCollection('meta')),
  baseUrl: '/docs',
  i18n, 
});

Configure i18n on your search solution.

  • Built-in Search: See Internationalization.
  • Cloud Solutions (e.g. Algolia): They usually have official support for multilingual.

Writing Documents

See i18n routing to learn how to create pages for specific locales.

Fumadocs only handles navigation for its own layouts (e.g. sidebar).

For other places, extract the locale from Astro.params or the path and prepend it to links.

---
const { lang } = Astro.params;
---

<a href={lang ? `/${lang}/about` : '/about'}>About Us</a>

The fumadocs-core/dynamic-link component supports dynamic hrefs, so you can use it to prepend the locale prefix in Markdown/MDX content.

Add it to your MDX components, since the MDX content renders them inside its own island:

src/components/mdx.tsx
import { DynamicLink } from 'fumadocs-core/dynamic-link'; 

export function getMDXComponents(components?: MDXComponents) {
  return {
    ...defaultMdxComponents,
    DynamicLink, 
    ...components,
  } satisfies MDXComponents;
}
content.mdx
<DynamicLink href="/[lang]/another-page">This is a link</DynamicLink>

How is this guide?

Last updated on