# Fumadocs (Framework Mode, Astro): 国際化
URL: /ja/docs/astro/internationalization
Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/(framework)/[framework]/internationalization.ja.mdx

Astroのドキュメントを多言語に対応させます。

<Callout title="Fumadocs CLI">
`npx @fumadocs/cli init --i18n` を実行すると、i18n に対応した新しいドキュメントをセットアップできます。手動で設定する場合は、以下の手順に従ってください。
</Callout>

## 概要 [#overview]

Astro と Fumadocs の両方で i18n ルーティングを設定します。

Fumadocs が担当するのは、ドキュメントのコンテンツとサイドバーの i18n です。アプリのその他の部分は、独自の i18n 設定で国際化してください。

## セットアップ [#setup]

i18n の設定をファイルに定義します。このガイドでは、`@/lib/i18n` としてインポートします。

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

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

> i18n の設定で使用できるオプションについては、[こちら](/docs/headless/internationalization/config)を参照してください。

### ルーティング [#routing]

ページに `[...lang]` プレフィックスを追加します。レストパラメーターを使うことで、デフォルト言語をプレフィックスなし (例: `/docs`) で扱えます。

<Files>
<Folder name="src/pages" defaultOpen>
<Folder name="[...lang]" defaultOpen>
<File name="index.astro" />

<Folder name="docs" defaultOpen>
<File name="[...slug].astro" />
</Folder>
</Folder>

<Folder name="api" defaultOpen>
<File name="search.ts" />
</Folder>
</Folder>
</Files>

Fumadocs が生成する URL をこれらのルートと一致させるため、`hideLocale` を有効にします。

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

### 翻訳 [#translations]

翻訳は `lib/layout.shared.tsx` で定義します。公式の言語パックなど、詳しくは[翻訳](/docs/ui/translations)を参照してください。

また、`baseOptions()` に `locale` パラメーターを追加すると、ロケールごとにレイアウトオプションを切り替えられます。

```tsx title="src/lib/layout.shared.tsx"
// [!code ++:2]
import { i18n } from '@/lib/i18n';
import { uiTranslations } from 'fumadocs-ui/i18n';
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';

// [!code ++:3]
export const translations = i18n
  .translations()
  .extend(uiTranslations());

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

翻訳を `<Provider />` に渡します。MDX コンテンツはそれ自体が独立したアイランドなので、こちらにもプロバイダーが必要です。

```tsx tab="src/components/provider.tsx"
import { navigate } from 'astro:transitions/client';
import type { ReactNode } from 'react';
import { RootProvider } from 'fumadocs-ui/provider/astro';
// [!code ++:2]
import { i18nProvider } from 'fumadocs-ui/i18n';
import { translations } from '@/lib/layout.shared';
import SearchDialog from '@/components/search';

export interface ProviderProps {
  lang: string; // [!code ++]
  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}
      // [!code ++]
      i18n={i18nProvider(translations, lang)}
      search={{ SearchDialog }}
    >
      {children}
    </RootProvider>
  );
}

// [!code ++:16]
/**
 * MDX コンテンツ用(それ自体が独立したアイランドのため)。
 */
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>
  );
}
```

```tsx tab="src/components/mdx.tsx"
import defaultMdxComponents from 'fumadocs-ui/mdx';
import { DocsBody } from 'fumadocs-ui/layouts/docs/page';
import type { MDXComponents } from 'mdx/types';
import { ContentProvider, type ProviderProps } from '@/components/provider'; // [!code ++]

export function getMDXComponents(components?: MDXComponents) {
  return {
    ...defaultMdxComponents,
    ...components,
  } satisfies MDXComponents;
}

// Fumadocs のインテグレーションでは、`wrapper` は `<Content />` の props を受け取ります
export default getMDXComponents({
  wrapper: DocsBody, // [!code --]
  // [!code ++:5]
  wrapper: ({ children, ...props }: ProviderProps) => (
    <ContentProvider {...props}>
      <DocsBody>{children}</DocsBody>
    </ContentProvider>
  ),
});
```

### ロケールを渡す [#pass-locale]

ソース、レイアウト、ページで、ロケールを Fumadocs に渡します。

```ts tab="src/lib/source.ts"
import { i18n } from '@/lib/i18n'; // [!code ++]

export const source = loader({
  source: toFumadocsSource(await getCollection('docs'), await getCollection('meta')),
  baseUrl: '/docs',
  i18n, // [!code ++]
});
```

```tsx tab="src/components/docs.tsx"
export function Docs({ tree, title, description, toc, children, ...props }: DocsProps) {
  const data = useFumadocsLoader({ tree });

  return (
    <Provider {...props}>
      {/* [!code highlight] */}
      <DocsLayout {...baseOptions(props.lang)} tree={data.tree}>
        {/* ... */}
      </DocsLayout>
    </Provider>
  );
}
```

```astro tab="src/layouts/Layout.astro"
---
interface Props {
  lang: string; // [!code ++]
  title: string;
  description?: string;
}

const { lang, title, description } = Astro.props;
---

<html lang={lang} transition:animate="none">
  {/* ... */}
</html>
```

```astro tab="ドキュメントページ" title="src/pages/[...lang]/docs/[...slug].astro"
---
import type { GetStaticPaths, InferGetStaticPropsType } from 'astro';
import Layout from '@/layouts/Layout.astro';
import { Docs } from '@/components/docs';
import { i18n } from '@/lib/i18n';
import { source } from '@/lib/source';

export const prerender = true;

export const getStaticPaths = (() => {
  const paths = [];
  for (const { language, pages } of source.getLanguages()) {
    for (const page of pages) {
      paths.push({
        params: {
          lang: language === i18n.defaultLanguage ? undefined : language,
          slug: decodeURI(page.slugs.join('/')) || undefined,
        },
        props: { lang: language, page },
      });
    }
  }
  return paths;
}) satisfies GetStaticPaths;

type Props = InferGetStaticPropsType<typeof getStaticPaths>;

const { lang, page } = Astro.props;
const { body: Content, toc } = await page.data.load();
const pageTree = await source.serializePageTree(source.getPageTree(lang));
const provider = { lang, pathname: Astro.url.pathname, params: Astro.params };
---

<Layout lang={lang} title={page.data.title} description={page.data.description}>
  <Docs
    client:load
    transition:persist="docs"
    {...provider}
    tree={pageTree}
    title={page.data.title}
    description={page.data.description}
    toc={toc}
  >
    <Content {...provider} />
  </Docs>
</Layout>
```

```astro tab="ホームページ" title="src/pages/[...lang]/index.astro"
---
import type { GetStaticPaths, InferGetStaticPropsType } from 'astro';
import Layout from '@/layouts/Layout.astro';
import { i18n } from '@/lib/i18n';

export const prerender = true;

export const getStaticPaths = (() => {
  return i18n.languages.map((lang) => ({
    params: { lang: lang === i18n.defaultLanguage ? undefined : lang },
    props: { lang },
  }));
}) satisfies GetStaticPaths;

type Props = InferGetStaticPropsType<typeof getStaticPaths>;

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

<Layout lang={lang} title="My App">
  <a href={lang === i18n.defaultLanguage ? '/docs' : `/${lang}/docs`}>
    Open Docs
  </a>
</Layout>
```

### 検索 [#search]

使用している検索ソリューションで i18n を設定してください。

* **組み込み検索:** [国際化](/docs/headless/search/orama#internationalization)を参照してください。
* **クラウドソリューション(Algolia など):** 多くの場合、多言語対応が公式にサポートされています。

## ドキュメントの作成 [#writing-documents]

特定のロケール向けのページを作成する方法については、[i18n ルーティング](/docs/astro/page-conventions#i18n-routing)を参照してください。

### ナビゲーション [#navigation]

Fumadocs がナビゲーションを処理するのは、Fumadocs 自身のレイアウト(サイドバーなど)のみです。

それ以外の場所では、`Astro.params` またはパスからロケールを取り出し、リンクの先頭に付加してください。

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

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

[`fumadocs-core/dynamic-link`](/docs/headless/components/link#dynamic-hrefs) コンポーネントは動的な href に対応しているため、Markdown/MDX コンテンツ内でロケールのプレフィックスを付加する際に利用できます。

MDX コンテンツはコンポーネントを独自のアイランド内でレンダリングするため、MDX コンポーネントに追加してください。

```tsx title="src/components/mdx.tsx"
import { DynamicLink } from 'fumadocs-core/dynamic-link'; // [!code ++]

export function getMDXComponents(components?: MDXComponents) {
  return {
    ...defaultMdxComponents,
    DynamicLink, // [!code ++]
    ...components,
  } satisfies MDXComponents;
}
```

```mdx title="content.mdx"
<DynamicLink href="/[lang]/another-page">This is a link</DynamicLink>
```