Admin Panel API: Localization
Page summary:Register translation files with
registerTrads, prefix keys with your plugin ID to avoid conflicts, and usereact-intl'suseIntlhook 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.
Before diving deeper into the concepts on this page, please ensure you have:
- created a Strapi plugin,
- read and understood the basics of the Admin Panel API
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:
{
"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
- JavaScript
- TypeScript
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;
},
};
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;
},
};
Function parameters
The registerTrads function receives an object with the following property:
| Parameter | Type | Description |
|---|---|---|
locales | string[] | 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
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:
- JavaScript
- TypeScript
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 };
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 };
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.namemy-plugin.settings.title