# 管理员本地化

> Source: https://strapi.nodejs.cn/cms/plugins-development/admin-localization

🌐 Admin Panel API: Localization

使用 `registerTrads` 注册翻译文件，使用你的插件 ID 作为键前缀以避免冲突，并在组件中使用 `react-intl` 的 `useIntl` 钩子。Strapi 会自动将插件翻译与核心翻译合并。

🌐 Register translation files with `registerTrads`, prefix keys with your plugin ID to avoid conflicts, and use `react-intl`'s `useIntl` hook in components. Strapi merges plugin translations with core translations automatically.

插件可以为多种语言提供翻译，使管理界面对全球用户可访问。Strapi 会自动加载并合并插件翻译与核心翻译，使它们在整个管理面板中可用。

🌐 Plugins can provide translations for multiple languages to make the admin interface accessible to users worldwide. Strapi automatically loads and merges plugin translations with core translations, making them available throughout the admin panel.

:::prerequisites

在深入了解本页的概念之前，请确保你已经：

🌐 Before diving deeper into the concepts on this page, please ensure you have:

- [创建了一个 Strapi 插件](/cms/plugins-development/create-a-plugin)，
- 已阅读并理解了 [管理面板 API](/cms/plugins-development/admin-panel-api) 的基础知识

:::

## 翻译文件结构 {#translation-file-structure}

🌐 Translation file structure

翻译文件存储在 `admin/src/translations/` 目录中，每个语言环境有一个 JSON 文件：

🌐 Translation files are stored in the `admin/src/translations/` directory, with 1 JSON file per locale:

```
admin/src/translations/
  ├── en.json
  ├── fr.json
  ├── de.json
  └── ...
```

每个翻译文件包含键值对，其中键是翻译标识符，值是翻译后的字符串：

🌐 Each translation file contains key-value pairs where keys are translation identifiers and values are the translated strings:

```json title="admin/src/translations/en.json"
{
  "plugin.name": "My Plugin",
  "plugin.description": "A custom Strapi plugin",
  "settings.title": "Settings",
  "settings.general": "General",
  "settings.advanced": "Advanced"
}
```

## `registerTrads` 函数 {#registertrads}

🌐 The `registerTrads` function 

在具有管理部分的 Strapi 插件中，`registerTrads()` 用于加载插件的 UI 标签和消息的翻译。如果你不需要本地化，你可以省略它，插件仍然可以运行。

🌐 In Strapi plugins that have an admin part, `registerTrads()` is used to load translations for your plugin's UI labels and messages. If you don't need localization, you can omit it and the plugin can still run.

`registerTrads()` 函数是一个异步函数，用于加载所有配置的语言环境的翻译文件。Strapi 在管理面板初始化期间调用此函数，以收集所有插件的翻译。

🌐 The `registerTrads()` function is an async function that loads translation files for all configured locales. Strapi calls this function during admin panel initialization to collect translations from all plugins.

### 基本实现 {#basic-implementation}

🌐 Basic implementation

```js title="admin/src/index.js"

  register(app) {
    app.registerPlugin({
      id: PLUGIN_ID,
      name: 'My Plugin',
    });
  },
  async registerTrads({ locales }) {
    const importedTranslations = await Promise.all(
      locales.map((locale) => {
        return import(`./translations/${locale}.json`)
          .then(({ default: data }) => {
            return {
              data: prefixPluginTranslations(data, PLUGIN_ID),
              locale,
            };
          })
          .catch(() => {
            return {
              data: {},
              locale,
            };
          });
      }),
    );

    return importedTranslations;
  },
};
```
```ts title="admin/src/index.ts"

  register(app: StrapiApp) {
    app.registerPlugin({
      id: PLUGIN_ID,
      name: 'My Plugin',
    });
  },
  async registerTrads({ locales }: { locales: string[] }) {
    const importedTranslations = await Promise.all(
      locales.map((locale) => {
        return import(`./translations/${locale}.json`)
          .then(({ default: data }) => {
            return {
              data: prefixPluginTranslations(data, PLUGIN_ID),
              locale,
            };
          })
          .catch(() => {
            return {
              data: {},
              locale,
            };
          });
      }),
    );

    return importedTranslations;
  },
};
```

### 函数参数 {#function-parameters}

🌐 Function parameters

`registerTrads` 函数接收一个具有以下属性的对象：

🌐 The `registerTrads` function receives an object with the following property:

| 参数 | 类型 | 描述 |
|---|---|---|
| `locales` | `string[]` | 在管理面板中配置的区域设置代码数组（例如，`['en', 'fr', 'de']`） |

### 返回值 {#return-value}

🌐 Return value

该函数必须返回一个 `Promise`，该 `Promise` 解析为翻译对象数组。每个对象具有以下结构：

🌐 The function must return a `Promise` that resolves to an array of translation objects. Each object has the following structure:

```ts
{
  data: Record<string, string>; // Translation key-value pairs
  locale: string; // Locale code (e.g., 'en', 'fr')
}
```

### 翻译键前缀 {#translation-key-prefixing}

🌐 Translation key prefixing

:::caution

翻译键必须以你的插件 ID 为前缀，以避免与其他插件和 Strapi 核心翻译的冲突。例如，如果你的插件 ID 是 `my-plugin`，那么像 `plugin.name` 这样的键应该变为 `my-plugin.plugin.name`。

🌐 Translation keys must be prefixed with your plugin ID to avoid conflicts with other plugins and core Strapi translations. For example, if your plugin ID is `my-plugin`, a key like `plugin.name` should become `my-plugin.plugin.name`.

:::

使用 `prefixPluginTranslations` 工具函数自动为所有键添加前缀：

🌐 Use the `prefixPluginTranslations` utility function to automatically prefix all keys:

```js title="admin/src/utils/prefixPluginTranslations.js"
const prefixPluginTranslations = (trad, pluginId) => {
  if (!pluginId) {
    throw new TypeError("pluginId can't be empty");
  }
  return Object.keys(trad).reduce((acc, current) => {
    acc[`${pluginId}.${current}`] = trad[current];
    return acc;
  }, {});
};

```
```ts title="admin/src/utils/prefixPluginTranslations.ts"
type TradOptions = Record<string, string>;

const prefixPluginTranslations = (
  trad: TradOptions,
  pluginId: string,
): TradOptions => {
  if (!pluginId) {
    throw new TypeError("pluginId can't be empty");
  }
  return Object.keys(trad).reduce((acc, current) => {
    acc[`${pluginId}.${current}`] = trad[current];
    return acc;
  }, {} as TradOptions);
};

```

例如，如果你的翻译文件包含：

🌐 For instance, if your translation file contains:

```json
{
  "plugin.name": "My Plugin",
  "settings.title": "Settings"
}
```

在前缀添加插件 ID `my-plugin` 后，这些变为：

🌐 After prefixing with plugin ID `my-plugin`, these become:

- `my-plugin.plugin.name`
- `my-plugin.settings.title`

### 缺少翻译文件 {#missing-translation-files}

🌐 Missing translation files

`registerTrads` 函数应该通过为该语言环境返回一个空对象来优雅地处理缺失的翻译文件。上面的示例中的 `.catch()` 处理程序确保即使翻译文件不存在，插件仍然返回一个有效的翻译对象：

🌐 The `registerTrads` function should gracefully handle missing translation files by returning an empty object for that locale. The `.catch()` handler in the example above ensures that if a translation file does not exist, the plugin still returns a valid translation object:

```js
.catch(() => {
  return {
    data: {},
    locale,
  };
});
```

这允许插件只为某些语言环境提供翻译（例如，仅英语），而不会破坏其他语言环境的管理面板。

🌐 This allows plugins to provide translations for only some locales (e.g., only English) without breaking the admin panel for other locales.

## 组件中的翻译 {#translations-in-components}

🌐 Translations in components

要在你的 React 组件中使用翻译，请使用来自 `react-intl` 的 `useIntl` 钩子：

🌐 To use translations in your React components, use the `useIntl` hook from `react-intl`:

<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">

```jsx title="admin/src/pages/HomePage.jsx"

const HomePage = () => {
  const { formatMessage } = useIntl();

  return (
    <div>
      <h1>
        {formatMessage({
          id: `${PLUGIN_ID}.plugin.name`,
          defaultMessage: 'My Plugin',
        })}
      </h1>
      <p>
        {formatMessage({
          id: `${PLUGIN_ID}.plugin.description`,
          defaultMessage: 'A custom Strapi plugin',
        })}
      </p>
    </div>
  );
};

```

<TabItem value="ts" label="TypeScript">

```tsx title="admin/src/pages/HomePage.tsx"

const HomePage = () => {
  const { formatMessage } = useIntl();

  return (
    <div>
      <h1>
        {formatMessage({
          id: `${PLUGIN_ID}.plugin.name`,
          defaultMessage: 'My Plugin',
        })}
      </h1>
      <p>
        {formatMessage({
          id: `${PLUGIN_ID}.plugin.description`,
          defaultMessage: 'A custom Strapi plugin',
        })}
      </p>
    </div>
  );
};

```

### 翻译键的辅助函数 {#helper-function-for-translation-keys}

🌐 Helper function for translation keys

为了避免重复插件 ID 前缀，请创建一个辅助函数：

🌐 To avoid repeating the plugin ID prefix, create a helper function:

```js title="admin/src/utils/getTranslation.js"

```
```ts title="admin/src/utils/getTranslation.ts"

```

然后在组件中使用它：

🌐 Then use it in components:

<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">

```jsx title="admin/src/pages/HomePage.jsx"

const HomePage = () => {
  const { formatMessage } = useIntl();

  return (
    <div>
      <h1>
        {formatMessage({
          id: getTranslation('plugin.name'),
          defaultMessage: 'My Plugin',
        })}
      </h1>
    </div>
  );
};
```

<TabItem value="ts" label="TypeScript">

```tsx title="admin/src/pages/HomePage.tsx"

const HomePage = () => {
  const { formatMessage } = useIntl();

  return (
    <div>
      <h1>
        {formatMessage({
          id: getTranslation('plugin.name'),
          defaultMessage: 'My Plugin',
        })}
      </h1>
    </div>
  );
};
```

## 配置中的翻译 {#translations-in-configuration}

🌐 Translations in configuration

在配置菜单链接、设置部分以及其他管理面板元素时，也会使用翻译键：

🌐 Translation keys are also used when configuring menu links, settings sections, and other admin panel elements:

```js title="admin/src/index.js"

  register(app) {
    app.addMenuLink({
      to: '/plugins/my-plugin',
      icon: PluginIcon,
      intlLabel: {
        id: 'my-plugin.plugin.name', // Prefixed translation key
        defaultMessage: 'My Plugin', // Fallback if translation missing
      },
      Component: () => import('./pages/App'),
    });
  },
};
```
```ts title="admin/src/index.ts"

  register(app: StrapiApp) {
    app.addMenuLink({
      to: '/plugins/my-plugin',
      icon: PluginIcon,
      intlLabel: {
        id: 'my-plugin.plugin.name', // Prefixed translation key
        defaultMessage: 'My Plugin', // Fallback if translation missing
      },
      Component: () => import('./pages/App'),
    });
  },
};
```

## 插件翻译生命周期 {#plugin-translation-lifecycle}

🌐 Plugin translation lifecycle

Strapi 的管理面板自动:

🌐 Strapi's admin panel automatically:

1. 在初始化期间调用所有已注册插件的 `registerTrads`
2. 将所有插件的翻译与核心 Strapi 翻译合并
3. 应用管理员配置的自定义翻译（如果有的话）
4. 通过 `react-intl` 在整个管理面板中提供翻译

在实际操作中，核心管理翻译首先加载，插件翻译会在其上合并，而 `config.translations` 中的项目级覆盖允许你自定义在管理面板中显示的标签。

🌐 In practice, core admin translations are loaded first, plugin translations are merged on top, and project-level overrides in `config.translations` let you customize the labels displayed in the admin panel.

## 最佳实践 {#best-practices}

🌐 Best practices

- **始终为翻译键添加前缀。** 使用 `prefixPluginTranslations` 或手动为键添加你的插件 ID 作为前缀以避免冲突。
- **提供默认消息。** 如果翻译缺失，使用 `formatMessage` 作为回退时，请始终包含 `defaultMessage`。
- **优雅地处理缺失的翻译。** `registerTrads` 函数对于缺失的语言环境应返回空对象，而不是抛出错误。
- **使用描述性键名。** 选择清晰、有层次的键名（例如，`settings.general.title` 而不是 `title1`）。
- **至少支持英语。** 提供英文翻译可以确保你的插件开箱即可使用。
- **验证多种语言环境下的行为。** 测试当在管理面板中选择不同的语言环境时，你的插件是否能正常工作。

:::note

`en` 语言环境在 Strapi 中始终可用，并作为备用语言环境。如果所选语言环境缺少翻译，Strapi 会使用英文翻译。

🌐 The `en` locale is always available in Strapi and serves as the fallback locale. If a translation is missing for a selected locale, Strapi uses the English translation.

:::

:::tip

要查看你的 Strapi 实例中可用的语言环境，请检查 `src/admin/app.ts` 或 `src/admin/app.js` 文件中的 `config.locales` 数组。要在运行时以编程方式访问，请参阅 [访问 Redux 存储](/cms/plugins-development/admin-redux-store)（注意内部存储结构可能会随着版本变化而改变）。

🌐 To see which locales are available in your Strapi instance, check the `config.locales` array in your `src/admin/app.ts` or `src/admin/app.js` file. For programmatic access at runtime, see [Accessing the Redux store](/cms/plugins-development/admin-redux-store) (note that internal store structure may change between versions).

:::
