Internationalization in an Electron application can be relatively straightforward. Libraries like i18next work well for translating application interfaces, and integrating them into a React frontend is well documented.
Native Electron menus are a slightly different story.
While working with Electron and i18next, I ran into a small but frustrating gap—Electron's role-based native menu items don't integrate directly with an i18next instance running in the main process.
File. Edit. Copy. Paste. Undo. Redo. Quit.
Electron already knows how these actions should behave through its built-in menu roles. I didn't want to replace that behavior just because I wanted translated labels.
But I also wanted the native menu to follow the same language as the rest of the application.
Electron & i18next
Electron lets you define native menu items using roles:
{
role: "copy"
}
That's great because Electron handles the actual native behavior.
The problem comes when you want your application's i18next translations to determine what the user sees.
You can solve this inside your own application. Add labels, look up translations, handle fallbacks, repeat it across the menu, and remember to rebuild everything when the language changes.
None of that is particularly difficult. But it is repetitive.
Going open source
What started as a small solution inside one application eventually became @solisware/electron-menu-i18next—my first npm package released as part of SolisWare’s growing open-source suite.
The idea is deliberately simple. Give it an Electron menu template and your existing i18next translation function, and it localizes role-based menu items before Electron builds the native menu.
Instead of manually doing this throughout the menu:
{
role: "copy",
label: t("menu.roles.copy")
}
I can keep the Electron template focused on Electron:
{
role: "copy"
}
and localize the template in one place:
const localizedTemplate = localizeMenuTemplate(template, {
t,
appName: app.name
});
The package recursively handles the role-based items and returns a localized copy of the template.
Electron still handles the behavior. i18next still handles the translations. The package just connects them.
Decisions I made along the way
Once I started turning the code into something other developers could use, a few edge cases emerged.
One was handling fallback. I didn't think somebody should need a perfectly complete translation file before using the package, so it includes built-in English labels for Electron roles.
Another was respecting explicit labels. If you've deliberately written:
{
role: "copy",
label: "Copy to Clipboard"
}
the package shouldn't attempt to overwrite it.
There were also platform differences to consider. macOS provides some native localization for menu roles, while Windows and Linux generally require those labels to be handled by the application. Labels such as Quit {{appName}} also need the application's name, which is why the helper supports normal i18next interpolation.
And because Electron menus aren't reactive UI components but rather OS-level controls, changing the application's language still means rebuilding the menu. I kept menu rebuilding as an application-level responsibility rather than making the package manage that lifecycle itself.
A small package for a specific problem
The finished package is intentionally small. It solves the specific integration problem I originally ran into—using i18next translations with Electron's native role-based menus without repeatedly writing the same boilerplate code.
If you're building with Electron and i18next, I'd be interested to hear how you're currently handling native menu localization.
Top comments (0)