Skip to main content

Admin Panel API: Localization

Page summary:

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.

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:

Translation file structure

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:

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

The registerTrads function

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.

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

admin/src/index.js
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;
},
};

Function parameters

The registerTrads function receives an object with the following property:

ParameterTypeDescription
localesstring[]Array of locale codes configured in the admin panel (e.g., ['en', 'fr', 'de'])

Return value

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

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

Translation key prefixing

Caution

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.

Use the prefixPluginTranslations utility function to automatically prefix all keys:

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

export { prefixPluginTranslations };

For instance, if your translation file contains:

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

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

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

Missing translation files

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:

.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

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

admin/src/pages/HomePage.jsx
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;

Helper function for translation keys

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

admin/src/utils/getTranslation.js
import { PLUGIN_ID } from '../pluginId';

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

Then use it in components:

admin/src/pages/HomePage.jsx
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>
);
};

Translations in configuration

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

admin/src/index.js
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: async () => {
const { App } = await import('./pages/App');
return App;
},
});
},
};

Plugin translation lifecycle

Strapi's admin panel automatically:

  1. Calls registerTrads for all registered plugins during initialization
  2. Merges translations from all plugins with core Strapi translations
  3. Applies custom translations from the admin configuration (if any)
  4. Makes translations available via react-intl throughout the admin panel

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

  • Always prefix translation keys. Use prefixPluginTranslations or manually prefix keys with your plugin ID to avoid conflicts.
  • Provide default messages. Always include defaultMessage when using formatMessage as a fallback if translations are missing.
  • Handle missing translations gracefully. The registerTrads function should return empty objects for missing locales rather than throwing errors.
  • Use descriptive key names. Choose clear, hierarchical key names (e.g., settings.general.title rather than title1).
  • Support at least English. Providing English translations ensures your plugin works out of the box.
  • Verify behavior with multiple locales. Test that your plugin works correctly when different locales are selected in the admin panel.
Note

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

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 (note that internal store structure may change between versions).