<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael M. Polowczuk</title>
    <description>The latest articles on DEV Community by Michael M. Polowczuk (@michaelpolowczuk).</description>
    <link>https://dev.to/michaelpolowczuk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4148255%2F17ab2711-ad77-4fdf-b0df-2b15f5818bef.jpg</url>
      <title>DEV Community: Michael M. Polowczuk</title>
      <link>https://dev.to/michaelpolowczuk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/michaelpolowczuk"/>
    <language>en</language>
    <item>
      <title>Localizing Electron Menus with i18next—Why I Built electron-menu-i18next</title>
      <dc:creator>Michael M. Polowczuk</dc:creator>
      <pubDate>Sat, 03 Oct 2026 22:16:17 +0000</pubDate>
      <link>https://dev.to/michaelpolowczuk/localizing-electron-menus-with-i18next-why-i-built-electron-menu-i18next-2p4i</link>
      <guid>https://dev.to/michaelpolowczuk/localizing-electron-menus-with-i18next-why-i-built-electron-menu-i18next-2p4i</guid>
      <description>&lt;p&gt;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.&lt;br&gt;
Native Electron menus are a slightly different story.&lt;br&gt;
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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File. Edit. Copy. Paste. Undo. Redo. Quit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;br&gt;
But I also wanted the native menu to follow the same language as the rest of the application.&lt;/p&gt;
&lt;h2&gt;
  
  
  Electron &amp;amp; i18next
&lt;/h2&gt;

&lt;p&gt;Electron lets you define native menu items using roles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;role:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"copy"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's great because Electron handles the actual native behavior.&lt;br&gt;
The problem comes when you want your application's i18next translations to determine what the user sees.&lt;br&gt;
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.&lt;br&gt;
None of that is particularly difficult. But it is repetitive.&lt;/p&gt;
&lt;h2&gt;
  
  
  Going open source
&lt;/h2&gt;

&lt;p&gt;What started as a small solution inside one application eventually became &lt;code&gt;@solisware/electron-menu-i18next—my&lt;/code&gt; first npm package released as part of SolisWare’s growing open-source suite.&lt;br&gt;
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.&lt;br&gt;
Instead of manually doing this throughout the menu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;role:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"copy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;t(&lt;/span&gt;&lt;span class="s2"&gt;"menu.roles.copy"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can keep the Electron template focused on Electron:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;role:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"copy"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and localize the template in one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;localizedTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;localizeMenuTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package recursively handles the role-based items and returns a localized copy of the template.&lt;br&gt;
Electron still handles the behavior. i18next still handles the translations. The package just connects them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Decisions I made along the way
&lt;/h2&gt;

&lt;p&gt;Once I started turning the code into something other developers could use, a few edge cases emerged.&lt;br&gt;
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.&lt;br&gt;
Another was respecting explicit labels. If you've deliberately written:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;role:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"copy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;label:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Copy to Clipboard"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the package shouldn't attempt to overwrite it.&lt;br&gt;
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.&lt;br&gt;
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.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small package for a specific problem
&lt;/h2&gt;

&lt;p&gt;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.&lt;br&gt;
If you're building with Electron and i18next, I'd be interested to hear how you're currently handling native menu localization.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>typescript</category>
      <category>i18n</category>
      <category>electron</category>
    </item>
  </channel>
</rss>
