Fumadocs

Lingo.dev CLIを使用したFumadocsのAI翻訳

Fumadocsとは?

Fumadocsは、オープンソースのドキュメントフレームワークです。組み込みの検索機能、国際化サポート、美しいUIを備えた高速で型安全なドキュメントサイトを提供します。

Lingo.dev CLIとは?

Lingo.devは、AI駆動の翻訳プラットフォームです。Lingo.dev CLIは、ソースファイルを読み取り、翻訳可能なコンテンツを大規模言語モデルに送信し、翻訳されたファイルをプロジェクトに書き戻します。

このガイドについて

このガイドでは、FumadocsドキュメントサイトでLingo.dev CLIをセットアップする方法を説明します。Fumadocsでプロジェクトをスキャフォールドし、翻訳パイプラインをセットアップし、結果を確認する方法を学習します。

ステップ1. Fumadocsプロジェクトのセットアップ

  1. 新しいFumadocsアプリケーションを作成します:

    npm create fumadocs-app
    
  2. プロンプトに従って、希望する設定でプロジェクトを構成します。

  3. プロジェクトディレクトリに移動します:

    cd <project-name>
    

ステップ2. 国際化サポートの設定

Fumadocsは、ドキュメントがサポートする言語を認識する必要があります。Fumadocsに複数の言語を処理する方法を指示する設定ファイルを作成します。

  1. サポートする言語を定義するためにlib/i18n.tsファイルを作成します:

    import { defineI18n } from "fumadocs-core/i18n";
    
    export const i18n = defineI18n({
      defaultLanguage: "en",
      languages: ["en", "es"],
      parser: "dir",
    });
    
  2. i18n設定を使用するようにlib/source.tsファイルを更新します:

    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,
    });
    
  3. ユーザーの言語設定に基づいて検出およびリダイレクトするミドルウェアを作成します:

    // 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).*)"],
    };
    

ステップ3. 複数言語対応のアプリ構造の更新

  1. app/フォルダ内に言語パラメータディレクトリを作成します:

    mkdir app/[lang]
    
  2. 既存のページを言語パラメータディレクトリに移動します:

    • app/docs/app/[lang]/docs/
    • app/(home)/app/[lang]/(home)/
  3. すべての言語固有のページをラップする app/[lang]/layout.tsx ファイルを作成します:

    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>
      );
    }
    

ステップ 4. 共有レイアウトオプションを作成する

  1. 共有レイアウト設定用の lib/layout.shared.tsx ファイルを作成します:

    // 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: [],
      };
    }
    
  2. 共有オプションを使用するように app/[lang]/docs/layout.tsx ファイルを更新します:

    // 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>
      );
    }
    
  3. 共有オプションを使用するように app/[lang]/(home)/layout.tsx ファイルを更新します:

    // 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>;
    }
    

ステップ 5. ページコンポーネントを更新する

言語パラメータを処理するようにページコンポーネント(例: app/[lang]/docs/[[...slug]]/page.tsx)を更新します:

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,
  };
}

ステップ 6. 翻訳用のコンテンツを整理する

  1. コンテンツ用の言語固有のディレクトリを作成します:

    mkdir -p content/docs/en
    
  2. 既存のMDXファイルを英語ディレクトリに移動します:

    • content/docs/index.mdxcontent/docs/en/index.mdx
    • content/docs/test.mdxcontent/docs/en/test.mdx

ステップ 7. CLIを設定する

プロジェクトのルートに i18n.json ファイルを作成します:

{
  "$schema": "https://lingo.dev/schema/i18n.json",
  "version": "1.10",
  "locale": {
    "source": "en",
    "targets": ["es"]
  },
  "buckets": {
    "mdx": {
      "include": ["content/docs/[locale]/*.mdx"]
    }
  }
}

このファイルは以下を定義します:

  • Lingo.dev CLIが翻訳すべきファイル
  • 翻訳する言語間の設定

この場合、設定はMDXファイルを英語からスペイン語に翻訳します。

以下の点に注意することが重要です:

  • [locale] は実行時に置き換えられるプレースホルダーです。これにより、コンテンツが1つの場所(例: src/content/docs/en/index.mdx)から読み取られ、別の場所(例: src/content/docs/es/index.mdx)に書き込まれることが保証されます。
  • Lingo.dev CLIは再帰的なglobパターン(例: **/*.mdx)をサポートしていません。ネストされたディレクトリ内に存在するファイルを翻訳するには、追加の include パターンを作成する必要があります。

詳細については、i18n.json設定を参照してください。

ステップ 8. コンテンツを翻訳する

  1. Lingo.devアカウントにサインアップしてください。

  2. CLI経由でLingo.devにログインします:

    npx lingo.dev@latest login
    
  3. 翻訳パイプラインを実行します:

    npx lingo.dev@latest run
    

    CLIは翻訳されたコンテンツを保存するための content/docs/es/ ディレクトリと、翻訳済みの内容を追跡するための i18n.lock ファイルを作成します(不要な再翻訳を防ぐため)。

ステップ 9. 翻訳されたドキュメントを表示する

  1. 開発サーバーを起動します:

    npm run dev
    
  2. 以下のURLにアクセスします: