Fumadocs
AI translation for Fumadocs with Lingo.dev CLI
What is Fumadocs?
Fumadocs is an open-source documentation framework. It provides a fast, type-safe documentation site with built-in search, internationalization support, and a beautiful UI.
What is Lingo.dev CLI?
Lingo.dev is an AI-powered translation platform. The Lingo.dev CLI reads source files, sends translatable content to large language models, and writes translated files back to your project.
About this guide
This guide explains how to set up Lingo.dev CLI in a Fumadocs documentation site. You'll learn how to scaffold a project with Fumadocs, set up a translation pipeline, and view the results.
Step 1. Set up a Fumadocs project
-
Create a new Fumadocs application:
npm create fumadocs-app
-
Follow the prompts to configure the project with the preferred settings.
-
Navigate into the project directory:
cd <project-name>
Step 2. Configure internationalization support
Fumadocs needs to know which languages your documentation will support. You'll create configuration files that tell Fumadocs how to handle multiple languages.
-
Create a
lib/i18n.ts
file to define the supported languages:import { defineI18n } from 'fumadocs-core/i18n'; export const i18n = defineI18n({ defaultLanguage: 'en', languages: ['en', 'es'], parser: "dir", });
-
Update the
lib/source.ts
file to use the i18n settings:import { docs } from "@/.source"; import { loader } from "fumadocs-core/source"; import { i18n } from "@/lib/i18n"; // See https://fumadocs.vercel.app/docs/headless/source-api for more info export const source = loader({ // it assigns a URL to your pages baseUrl: "/docs", source: docs.toFumadocsSource(), i18n, });
-
Create a middleware to detect and redirect users based on their language preference:
// middleware.ts import { createI18nMiddleware } from 'fumadocs-core/i18n/middleware'; import { i18n } from '@/lib/i18n'; export default createI18nMiddleware(i18n); export const config = { // Matcher ignoring `/_next/` and `/api/` // You may need to adjust it to ignore static assets in `/public` folder matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], };
Step 3. Update app structure for multiple languages
-
Create a language parameter directory in the
app/
folder:mkdir app/[lang]
-
Move your existing pages into the language parameter directory:
app/docs/
→app/[lang]/docs/
app/(home)/
→app/[lang]/(home)/
-
Create a
app/[lang]/layout.tsx
file to wrap all your language-specific pages:import { RootProvider } from "fumadocs-ui/provider"; import { defineI18nUI } from "fumadocs-ui/i18n"; import { i18n } from "@/lib/i18n"; const { provider } = defineI18nUI(i18n, { translations: { en: { displayName: "English", }, es: { displayName: "Español", }, }, }); export default async function RootLayout({ params, children, }: LayoutProps<"/[lang]">) { const lang = (await params).lang; return ( <html lang={lang}> <body> <RootProvider i18n={provider(lang)}>{children}</RootProvider> </body> </html> ); }
Step 4. Create shared layout options
-
Create a
lib/layout.shared.tsx
file for shared layout configurations:// lib/layout.shared.tsx import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared"; import { i18n } from "@/lib/i18n"; /** * Shared layout configurations * * you can customise layouts individually from: * Home Layout: app/(home)/layout.tsx * Docs Layout: app/docs/layout.tsx */ export function baseOptions(locale: string): BaseLayoutProps { return { i18n, nav: { title: ( <> <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" aria-label="Logo" > <circle cx={12} cy={12} r={12} fill="currentColor" /> </svg> My App </> ), }, // see https://fumadocs.dev/docs/ui/navigation/links links: [], }; }
-
Update the
app/[lang]/docs/layout.tsx
file to use the shared options:// app/[lang]/docs/layout.tsx import type { ReactNode } from "react"; import { source } from "@/lib/source"; import { DocsLayout } from "fumadocs-ui/layouts/docs"; import { baseOptions } from "@/lib/layout.shared"; export default async function Layout({ params, children, }: LayoutProps<"/[lang]/docs">) { const { lang } = await params; return ( <DocsLayout {...baseOptions(lang)} tree={source.pageTree[lang]}> {children} </DocsLayout> ); }
-
Update the
app/[lang]/(home)/layout.tsx
file to use the shared options:// app/[lang]/(home)/layout.tsx import { HomeLayout } from "fumadocs-ui/layouts/home"; import { baseOptions } from "@/lib/layout.shared"; export default async function Layout({ children, params, }: LayoutProps<"/[lang]">) { const { lang } = await params; return <HomeLayout {...baseOptions(lang)}>{children}</HomeLayout>; }
Step 5. Update page components
Update the page components (e.g., app/[lang]/docs/[[...slug]]/page.tsx
) to handle the language parameter:
import { source } from "@/lib/source";
import {
DocsBody,
DocsDescription,
DocsPage,
DocsTitle,
} from "fumadocs-ui/page";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { createRelativeLink } from "fumadocs-ui/mdx";
import { getMDXComponents } from "@/mdx-components";
export default async function Page(
props: PageProps<"/[lang]/docs/[[...slug]]">,
) {
const params = await props.params;
const page = source.getPage(params.slug, params.lang);
if (!page) notFound();
const MDXContent = page.data.body;
return (
<DocsPage toc={page.data.toc} full={page.data.full}>
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
<MDXContent
components={getMDXComponents({
// this allows you to link to other pages with relative file paths
a: createRelativeLink(source, page),
})}
/>
</DocsBody>
</DocsPage>
);
}
export async function generateStaticParams() {
return source.generateParams();
}
export async function generateMetadata(
props: PageProps<"/[lang]/docs/[[...slug]]">,
): Promise<Metadata> {
const params = await props.params;
const page = source.getPage(params.slug, params.lang);
if (!page) notFound();
return {
title: page.data.title,
description: page.data.description,
};
}
Step 6. Organize content for translation
-
Create language-specific directories for the content:
mkdir -p content/docs/en
-
Move the existing MDX files into the English directory:
content/docs/index.mdx
→content/docs/en/index.mdx
content/docs/test.mdx
→content/docs/en/test.mdx
Step 7. Configure the CLI
In the root of the project, create an i18n.json
file:
{
"$schema": "https://lingo.dev/schema/i18n.json",
"version": 1.8,
"locale": {
"source": "en",
"targets": ["es"]
},
"buckets": {
"mdx": {
"include": ["content/docs/[locale]/*.mdx"]
}
}
}
This file defines:
- the files that Lingo.dev CLI should translate
- the languages to translate between
In this case, the configuration translates MDX files from English to Spanish.
It's important to note that:
[locale]
is a placeholder that's replaced at runtime. It ensures that content is read from one location (e.g.,src/content/docs/en/index.mdx
) and written to a different location (e.g.,src/content/docs/es/index.mdx
).- Lingo.dev CLI does not support recursive glob patterns (e.g.,
**/*.mdx
). You'll need to create additionalinclude
patterns to translate files that exist within nested directories.
To learn more, see i18n.json configuration.
Step 8. Translate the content
-
Log in to Lingo.dev via the CLI:
npx lingo.dev@latest login
-
Run the translation pipeline:
npx lingo.dev@latest run
The CLI will create a
content/docs/es/
directory for storing the translated content and ai18n.lock
file for keeping track of what has been translated (to prevent unnecessary retranslations).
Step 9. View the translated documentation
-
Start the development server:
npm run dev
-
Navigate to the following URLs:
- http://localhost:3000/en/docs for English content
- http://localhost:3000/es/docs for Spanish content