MetalGlot
Buy MetalGlot
Analytics cookies
MetalGlot can use Google Analytics to understand which pages help visitors evaluate the product. We do not send analytics until you explicitly accept.

By clicking "Accept", you allow MetalGlot to store your consent choice and submit privacy-conscious analytics events for the pages you visit.

Learn more
Analytics cookies
MetalGlot can use Google Analytics to understand which pages help visitors evaluate the product. We do not send analytics until you explicitly accept.

By clicking "Accept", you allow MetalGlot to store your consent choice and submit privacy-conscious analytics events for the pages you visit.

Learn more
← Back to blog
Localize an Electron App with i18next on Windows: Files, Menus, and QA article cover

Localize an Electron App with i18next on Windows: Files, Menus, and QA

A practical guide to Electron localization with i18next, covering namespaces, placeholder safety, nested JSON, main-process strings, menus, and Windows QA.

··MetalGlot Team

If you are localizing an Electron app with i18next, the main challenge is not just translating strings. It is keeping files, menus, placeholders, and Windows QA aligned with the desktop app you actually ship.

If you have ever tried to localize a large Electron app by copy-pasting nested JSON into a generic translator, you already know the failure mode: the sentences may improve while the app gets harder to trust.

Electron localization is not just about wording. It is about keeping renderer strings, main-process menus, nested JSON structure, placeholders, and review flow aligned with the desktop app you actually ship.

That is why the safest workflow is not a generic text box. It is a format-aware process that works against the real files, preserves structure, and includes QA inside the running Windows build.

Quick answer ✅

The safest way to localize an Electron app is to treat locale files as part of the codebase. Keep namespace files small, protect placeholders, cover both renderer and main-process strings, and validate the real Windows build before release.

Local-first translation becomes attractive when source strings are sensitive, reruns are frequent, or the team wants review to happen against the same JSON resources the app actually ships.

Workflow map

Use this view to keep locale files, the shared i18next runtime, renderer text, main-process menus, and Windows QA in one connected loop.

Electron localization workflow with i18next A diagram showing locale JSON files feeding both the Electron renderer and main process through a shared i18next runtime, with local translation and QA in the Windows build. Locale files common.json menu.json settings.json Renderer React + react-i18next views, dialogs, components Main process menus, tray, shell UI rebuild on locale change shared i18next runtime Local translation protect placeholders preserve JSON shape review actual diffs Windows QA run the build check fit and fallbacks inspect real menus
The core mistake in Electron localization is translating only the renderer surface. The real workflow has to cover locale files, main-process UI, and final QA in the actual Windows build.

Why desktop teams look harder at local workflows 🖥️

Before diving into the code, it helps to understand why some desktop teams prefer a local translation workflow over a browser-based TMS.

FeatureCloud-Based TMSLocal-First (MetalGlot)
Data pathStrings usually leave your machineTranslation can run on files you control after setup
WorkflowBrowser-based upload/downloadLocal file-based handling
Operational fitGood for centralized SaaS workflowsGood for teams that want local review and no per-use vendor fee
TradeoffLower setup burdenMore internal ownership

Why Electron localization is different from browser-only i18n

In a browser-only application, translation usually stays inside the frontend stack. In Electron, the text surface is wider:

  • renderer components and routed views
  • shared UI components
  • main-process menus and tray labels
  • installer text, updater prompts, and other shell-adjacent UI

For many Windows applications, especially in policy-sensitive environments, “sending strings to the cloud” is a workflow decision that deserves scrutiny. Source-code strings often reveal product logic, unreleased features, and internal terminology.

That is why a local workflow can be attractive. Once the runtime and model assets are installed, translation can happen on your own Windows machine or local Docker environment instead of requiring a third-party translation API for every pass.

Start with a persistent local runtime

The cleanest pattern for an Electron app is to treat localization as a core system service. Even if your app is not strictly offline-only, it is safer to avoid a design where basic UI translation depends on fetching resources from a remote CDN at runtime.

Use libraries like i18next-fs-backend (or a custom local loader) to ensure all resources are available the moment the .exe launches. This avoids flash-of-untranslated-content issues and helps system tray menus appear localized even before the renderer process has fully hydrated.

  • one initialized i18next instance for the app
  • react-i18next hooks in the renderer
  • one shared helper for changing language
  • a persistent setting for the currently selected locale
  • lazy loading of namespace bundles instead of baking everything into each component

That structure matters because desktop apps often mix React surfaces with menus, dialogs, and other strings that are not neatly contained inside one page tree.

The desktop-specific trap: it is easy to localize React components well and still ship English-only menus, tray items, or shell-adjacent prompts. Electron crosses process boundaries, so your localization plan has to cross them too.

A clean i18next folder structure for Electron 🗂️

For most Electron apps, a namespace-based layout is easier to maintain than a single massive file.

src/
main/
preload/
renderer/
locales/
en/
common.json
menu.json
settings.json
onboarding.json
de/
common.json
menu.json
settings.json
onboarding.json

This gives you three advantages immediately:

  1. Smaller review units: menu changes do not get buried inside unrelated strings.
  2. Cleaner ownership: teams can assign review by surface area.
  3. Safer translation passes: smaller files make it easier to spot placeholder or structural issues.

Try to name namespaces after product surfaces, not departments. menu, settings, and onboarding are better than team-a or release-2.

Step 1: standardize how keys are called

Before you think about translation automation, make the source predictable.

In many Electron apps, that means using explicit namespace-aware t() calls consistently.

import { useTranslation } from 'react-i18next';
export function SettingsHeader() {
const { t } = useTranslation('settings');
return (
<header>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</header>
);
}

Some teams prefer fully qualified keys such as t('settings:title') or t('settings:dialog.quit.title') even inside a namespaced hook because they are easier to grep, review, and move across mixed desktop surfaces. Either approach can work. The important thing is consistency.

In shared logic or non-React modules, keep keys explicit instead of building them dynamically whenever possible.

Bad:

const key = `status.${state}`;

Better:

switch (state) {
case 'syncing':
return t('status.syncing');
case 'idle':
return t('status.idle');
default:
return t('status.unknown');
}

Static keys are easier to audit, translate, and review.

Step 2: keep locale files as the real source of truth

If your Electron app already uses i18next, there are two common ways to maintain the English source catalog:

  • generate or update it with extraction tooling
  • maintain it intentionally as the canonical locale resource the rest of the app reads

Both approaches can work. What matters more is that the translation workflow operates on the actual nested JSON files your app will ship.

That is especially important for Windows desktop apps because the QA loop usually depends on reviewing real file diffs, not isolated strings in a browser form.

If your team does use parser-based extraction, keep the step simple and review the English diff before translation. If your team maintains the English catalog manually, enforce naming discipline and keep namespaces small.

Step 3: design the JSON so translators can succeed

An i18next file is not just data. It is part of the translation interface.

Messy source JSON creates messy translations.

Prefer readable nesting

{
"dialog": {
"quit": {
"title": "Quit the app?",
"message": "Your current import will stop immediately.",
"confirm": "Quit now",
"cancel": "Stay"
}
}
}

That is much easier to review than a flat list of opaque keys.

Keep placeholders explicit

{
"syncComplete": "{{count}} files synced successfully.",
"welcome": "Welcome back, {{firstName}}."
}

If the placeholder contract is visible, it is easier to verify later.

Keep linked keys and plural groups obvious

Nested JSON works well for i18next, but only if the relationships remain readable.

{
"fileCount_one": "{{count}} file",
"fileCount_other": "{{count}} files",
"cta": "$t(common:buttons.continue)"
}

Plural keys, interpolation placeholders, and linked references should be easy to spot before translation begins.

Step 4: translate the nested JSON tree, not a flattened text dump 🌳

Writing or maintaining a recursive parser just to translate your app safely is often not where a product team wants to spend engineering time. General-purpose translators also tend to ignore the JSON structure itself, which is how broken commas, renamed keys, and damaged placeholders slip into the app.

This is where format-aware handling matters. A safer workflow walks the JSON object, translates only the string leaves, and keeps the surrounding schema intact.

That keeps en.json and de.json aligned structurally. If a key disappears or the shape changes unexpectedly, the app may fail to find strings at runtime.

Step 5: Protect placeholders with AST and regex

Generic translation tools often break i18next variables like {{count}} or $t(common:btn). An expert-level workflow uses Abstract Syntax Tree (AST) concepts or strict Regex to identify “protected tokens” before the translation begins.

By identifying these tokens, you can instruct the local model to treat them as constants. This prevents the common failure mode where {{name}} becomes {{nombre}}, breaking the functionality of your React components.

Step 6: centralize locale switching

In Electron apps, locale switching often becomes messy because different surfaces update language in different ways.

The safer pattern is:

  1. persist the selected locale in one app-level setting
  2. load the locale resources for that language
  3. add them to the shared i18next instance
  4. call one central language-change function
  5. let the UI rerender from the shared runtime

That avoids the common desktop anti-pattern where one part of the app changes language immediately and another only updates after a full reload.

Step 7: translate locally on Windows against the real files

A local workflow for desktop app internationalization nodejs teams usually looks like this:

  1. prepare or update the English namespace files
  2. run the local translation pass on target locale JSON
  3. review placeholder and formatting issues at the string level
  4. rebuild the nested JSON in its original structure
  5. open the Windows build and inspect the real UI

The important part is that translation happens against the actual JSON resources, not against copy-pasted strings in a generic text box.

That lets the team keep namespace structure, reuse review diffs, and validate the same files that will ship.

Step 8: do not forget main-process and menu strings

A common Electron mistake is to localize the renderer well and leave the main process behind.

Menus, tray labels, confirmation dialogs, and shell-adjacent UI often live outside the React surface.

Treat them as first-class translation assets.

For example:

{
"file": "File",
"edit": "Edit",
"view": "View",
"quit": "Quit"
}

If those strings live in a menu.json namespace, they can follow the same translation and QA process as the renderer.

Step 9: run structural QA before release

Good localize electron app workflows always include release QA.

At minimum, verify:

1. JSON validity

No malformed JSON, accidental renames, or broken nesting.

2. Placeholder integrity

All {{variables}}, linked keys, plural suffixes, and markup fragments still match source expectations.

3. Suspicious formatting changes

Unexpected newlines, extra parentheses, and odd formatting drift are common local-translation failure modes and should be flagged automatically where possible.

4. Layout fit

Check narrow buttons, modal titles, settings panes, and menus on real Windows screens. Desktop UI breaks faster than teams expect.

5. Fallback behavior

Confirm the app handles missing translations gracefully and falls back to the configured default locale correctly.

Common failure modes in Electron localization

Most broken Electron localization workflows fail in a few predictable ways:

  1. renderer strings get localized but menu and main-process strings are missed
  2. placeholders or linked keys are changed during translation
  3. plural groups become incomplete
  4. suspicious formatting drift is not reviewed before release
  5. the team never opens the real Windows build to inspect UI fit in context

One technical detail matters here: react-i18next hooks do not run in the main process. If your menus and tray items must react to language changes, keep the shared i18next instance available outside the renderer and trigger menu rebuilds deliberately when the locale changes.

Final take

The safest way to localize an Electron app is to treat translation files as part of the codebase, not as side documents.

That means a shared i18next runtime, structured namespace files, local translation against real nested JSON, strict placeholder protection, and QA inside the running Windows build.

Electron apps are not harder to localize because the strings are special. They are harder because desktop workflows cross boundaries that browser-only guides tend to ignore. Once those boundaries are explicit, the workflow gets much easier to manage.

If your next question is about the underlying file model rather than the Electron shell, continue with i18next JSON v4. If you are still deciding whether private local translation is worth the operational effort, read When Local-First Translation Software Is Worth the Overhead. If you need the broader workflow-ownership view beyond one app, the companion piece is Self-Hosted Translation Management System: Evaluation Checklist for Software Teams.

Own your localization stack today

Join teams translating without cloud lock-in. Download once, use forever.