管理面板 API:本地化(Localization)

页面摘要: 使用 registerTrads 注册翻译文件,用你的插件 ID 作为键前缀以避免冲突,并在组件中使用 react-intl 的 useIntl hook。Strapi 会自动将插件翻译与核心翻译合并。

插件可以为多种语言提供翻译,使全球用户都能使用管理界面。Strapi 会自动加载插件翻译并与核心翻译合并,使其在管理面板各处都可用。

WARNING

在深入阅读本页概念之前,请确保你已经:

翻译文件结构

翻译文件存放在 admin/src/translations/ 目录中,每个语言区域对应 1 个 JSON 文件:

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

每个翻译文件包含键值对,其中键是翻译标识符,值是已翻译的字符串:

{
  "plugin.name": "My Plugin",
  "plugin.description": "A custom Strapi plugin",
  "settings.title": "Settings",
  "settings.general": "General",
  "settings.advanced": "Advanced"
}

registerTrads 函数 {#registertrads}

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

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

基础实现

JavaScript

import { prefixPluginTranslations } from './utils/prefixPluginTranslations';
import { PLUGIN_ID } from './pluginId';

export default {
  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;
  },
};

TypeScript

import { prefixPluginTranslations } from './utils/prefixPluginTranslations';
import { PLUGIN_ID } from './pluginId';
import type { StrapiApp } from '@strapi/admin/strapi-admin';

export default {
  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;
  },
};

函数参数

registerTrads 函数接收一个包含以下属性的对象:

ParameterTypeDescription
localesstring[]管理面板中配置的语言区域代码数组(例如 ['en', 'fr', 'de'])

返回值

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

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

翻译键前缀

WARNING

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

使用 prefixPluginTranslations 工具函数可以自动为所有键添加前缀:

JavaScript

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

export { prefixPluginTranslations };

TypeScript

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

export { prefixPluginTranslations };

例如,如果你的翻译文件包含:

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

在以插件 ID my-plugin 添加前缀后,它们变为:

  • my-plugin.plugin.name
  • my-plugin.settings.title

缺失的翻译文件

registerTrads 函数应通过返回该语言区域的空对象,来优雅处理缺失的翻译文件。上面示例中的 .catch() 处理器确保如果某个翻译文件不存在,插件仍会返回一个有效的翻译对象:

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

这样,插件只需为部分语言区域(例如仅英文)提供翻译,而不会导致其他语言区域的管理面板崩溃。

组件中的翻译

要在你的 React 组件中使用翻译,请使用 react-intl 中的 useIntl hook:

JavaScript

import { useIntl } from 'react-intl';
import { PLUGIN_ID } from '../pluginId';

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

export default HomePage;

TypeScript

import { useIntl } from 'react-intl';
import { PLUGIN_ID } from '../pluginId';

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

export default HomePage;

翻译键辅助函数

为了避免重复编写插件 ID 前缀,可以创建一个辅助函数:

JavaScript

import { PLUGIN_ID } from '../pluginId';

export const getTranslation = (id) => `${PLUGIN_ID}.${id}`;

TypeScript

import { PLUGIN_ID } from '../pluginId';

export const getTranslation = (id: string) => `${PLUGIN_ID}.${id}`;

然后在组件中使用它:

JavaScript

import { useIntl } from 'react-intl';
import { getTranslation } from '../utils/getTranslation';

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

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

TypeScript

import { useIntl } from 'react-intl';
import { getTranslation } from '../utils/getTranslation';

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

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

配置中的翻译

在配置菜单链接、设置板块以及其他管理面板元素时,也会用到翻译键:

JavaScript

export default {
  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'),
    });
  },
};

TypeScript

import type { StrapiApp } from '@strapi/admin/strapi-admin';

export default {
  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'),
    });
  },
};

插件翻译生命周期

Strapi 的管理面板会自动:

  1. 在初始化期间为所有已注册插件调用 registerTrads
  2. 将来自所有插件的翻译与核心 Strapi 翻译合并
  3. 应用来自管理面板配置的自定义翻译(如果有)
  4. 通过 react-intl 在管理面板各处提供翻译

实际上,先加载核心管理面板翻译,再将插件翻译合并到其上;而 config.translations 中的项目级覆盖则允许你自定义管理面板中显示的标签。

最佳实践

  • 始终为翻译键添加前缀。 使用 prefixPluginTranslations 或手动为键添加插件 ID 前缀,以避免冲突。
  • 提供默认消息。 使用 formatMessage 时始终包含 defaultMessage,作为翻译缺失时的兜底。
  • 优雅处理缺失的翻译。 registerTrads 函数应针对缺失的语言区域返回空对象,而不是抛出错误。
  • 使用具有描述性的键名。 选择清晰、层级化的键名(例如 settings.general.title,而不是 title1)。
  • 至少支持英文。 提供英文翻译可确保你的插件开箱即用。
  • 使用多个语言区域验证行为。 测试当在管理面板中选择不同语言区域时,你的插件是否能正常工作。
NOTE

en 语言区域在 Strapi 中始终可用,并作为兜底语言区域。如果所选语言区域缺少翻译,Strapi 会使用英文翻译。

TIP

要查看你的 Strapi 实例中可用的语言区域,请检查 src/admin/app.ts 或 src/admin/app.js 文件中的 config.locales 数组。对于运行时的编程访问,请参阅 访问 Redux store(注意,内部 store 结构可能在不同版本间发生变化)。