> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.

# API 参考

## useModernI18n

`useModernI18n` 是插件提供的 React Hook，用于在组件中访问国际化状态和操作。

### 返回值

| 字段                    | 类型                                | 说明                                      |
| --------------------- | --------------------------------- | --------------------------------------- |
| `language`            | `string`                          | 当前语言代码                                  |
| `changeLanguage`      | `(lang: string) => Promise<void>` | 切换语言                                    |
| `supportedLanguages`  | `string[]`                        | 支持的语言列表（来自 `localeDetection.languages`） |
| `isLanguageSupported` | `(lang: string) => boolean`       | 检查语言是否在支持列表中                            |
| `isResourcesReady`    | `boolean`                         | 当前语言的翻译资源是否已加载完成                        |
| `i18nInstance`        | `I18nInstance`                    | i18next 实例（用于高级场景）                      |

### 基本用法

```tsx
import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
import { useTranslation } from 'react-i18next';

function LanguageSwitcher() {
  const { language, changeLanguage, supportedLanguages } = useModernI18n();
  const { t } = useTranslation();

  return (
    <div>
      <p>{t('welcome')}</p>
      {supportedLanguages.map(lang => (
        <button
          key={lang}
          onClick={() => changeLanguage(lang)}
          disabled={lang === language}
        >
          {lang}
        </button>
      ))}
    </div>
  );
}
```

### changeLanguage

切换语言时会依次执行：

1. 更新 i18next 实例的语言
2. 更新浏览器缓存（Cookie / LocalStorage，取决于 `caches` 配置）
3. 更新 URL 路径前缀（如果启用了 `localePathRedirect`）

`changeLanguage` 是异步函数，使用时需要 `await`：

```ts
await changeLanguage('zh');
```

### isResourcesReady

在自定义后端场景中，翻译资源需要异步加载，可以用 `isResourcesReady` 防止在资源未就绪时渲染：

```tsx
function MyComponent() {
  const { isResourcesReady } = useModernI18n();
  const { t } = useTranslation();

  if (!isResourcesReady) {
    return <div>加载翻译中...</div>;
  }

  return <div>{t('content')}</div>;
}
```

`isResourcesReady` 会检查：i18n 实例是否已初始化、当前语言是否有资源正在加载、所有必需命名空间是否已加载完成。

## I18nLink 组件

带语言前缀的路由链接组件。

### Props

| Prop          | 类型                | 必填 | 说明                                          |
| ------------- | ----------------- | -- | ------------------------------------------- |
| `to`          | `string`          | 是  | 目标路径，不需要包含语言前缀                              |
| `children`    | `React.ReactNode` | 是  | 链接内容                                        |
| `replace`     | `boolean`         | 否  | 使用 `history.replace` 而非 `push`              |
| `state`       | `any`             | 否  | 传递给目标路由的状态                                  |
| 其他 Link props | —                 | 否  | 继承自 `@modern-js/runtime/router` 的 `Link` 组件 |

### 用法

```tsx
import { I18nLink } from '@modern-js/plugin-i18n/runtime';

<I18nLink to="/about">关于</I18nLink>
<I18nLink to="/contact" replace>联系</I18nLink>
<I18nLink to="/profile" state={{ from: 'home' }}>个人中心</I18nLink>
```

## Runtime 插件 API

i18n 插件会在 `onBeforeRender` 钩子的 context 上挂载 `changeLanguage` 和 `i18nInstance`，供其他 Runtime 插件使用：

```ts
import type { RuntimePlugin } from '@modern-js/runtime';

const myPlugin = (): RuntimePlugin => ({
  name: 'my-plugin',
  setup: api => {
    api.onBeforeRender(async context => {
      if (!context.changeLanguage) return; // 确保 i18n 插件已加载

      const lang = detectLangFromRequest(context);
      const supported = context.i18nInstance?.options?.supportedLngs ?? [];

      if (supported.includes(lang)) {
        try {
          await context.changeLanguage(lang);
        } catch (e) {
          console.error('Language change failed:', e);
        }
      }
    });
  },
});
```

**注意事项：**

- 确保 i18n 插件在使用 `context.changeLanguage` 的插件之前注册
- `changeLanguage` 在服务端不会更新 URL 路径，需要配合路由插件或手动处理

## 类型定义

### I18nInstance

插件使用的 i18next 实例类型（i18next `i18n` 类型的子集，仅列出插件实际使用的字段）：

```ts
interface I18nInstance {
  language: string;
  isInitialized: boolean;
  init: (options?: I18nInitOptions) => void | Promise<void>;
  changeLanguage: (lang: string) => void | Promise<void>;
  use: (plugin: any) => void;
  createInstance: (options?: I18nInitOptions) => I18nInstance;
  options?: {
    backend?: BackendOptions;
    supportedLngs?: string[];
    [key: string]: any;
  };
}
```

### I18nSdkLoader

自定义后端加载函数的类型：

```ts
type I18nSdkLoader = (options: I18nSdkLoadOptions) => Promise<Resources>;

interface I18nSdkLoadOptions {
  lng?: string; // 单个语言代码
  ns?: string; // 单个命名空间
  lngs?: string[]; // 多个语言代码
  nss?: string[]; // 多个命名空间
  all?: boolean; // 加载所有资源
}

type Resources = {
  [lng: string]: {
    [ns: string]: Record<string, any>;
  };
};
```

### LanguageDetectorOptions

```ts
interface LanguageDetectorOptions {
  order?: string[];
  lookupQuerystring?: string; // 默认 'lng'
  lookupCookie?: string; // 默认 'i18next'
  lookupLocalStorage?: string; // 默认 'i18nextLng'
  lookupSession?: string;
  lookupHeader?: string; // 默认 'accept-language'
  lookupFromPathIndex?: number; // 默认 0
  caches?: false | string[];
  cookieMinutes?: number; // 默认 525600（1年）
  cookieExpirationDate?: Date;
  cookieDomain?: string;
}
```

## 与 react-i18next 集成

插件完全兼容 react-i18next，两套 API 可以混用：

- **`useTranslation`**（react-i18next）：获取 `t()` 函数做翻译，这是最常用的 Hook
- **`useModernI18n`**（插件提供）：获取语言切换、支持列表、资源加载状态等插件层面的状态

大多数组件只需要 `useTranslation`，只有涉及语言切换或需要检查加载状态时才需要 `useModernI18n`：

```tsx
import { useTranslation } from 'react-i18next';
import { useModernI18n } from '@modern-js/plugin-i18n/runtime';

function App() {
  const { t } = useTranslation(); // 翻译文本
  const { language, changeLanguage } = useModernI18n(); // 语言切换

  return (
    <div>
      <h1>{t('welcome')}</h1>
      <button onClick={() => changeLanguage('zh')}>中文</button>
      <button onClick={() => changeLanguage('en')}>English</button>
    </div>
  );
}
```

也可以通过 `useModernI18n` 获取 i18next 实例后直接调用 react-i18next 之外的能力：

```tsx
const { i18nInstance } = useModernI18n();

// 动态添加翻译资源
i18nInstance.addResourceBundle('zh', 'translation', { key: '值' }, true, true);
```
