Sechs Entscheidungen für eine wartbare i18n-Schicht in einer kleinen Electron-App
Ein praktisches Electron-i18n-Design für Spracheinstellungen, typisierte Kataloge, Fallbacks, Intl-Formatierung und die Synchronisierung vom Renderer zum Main-Prozess.

Als wir eine vorhandene Electron-App um Japanisch ergänzten, mussten wir Fragen beantworten, die ein Nachrichtenkatalog allein nicht löst. Welche Sprache gewinnt, wenn Betriebssystem und gespeicherte Einstellung voneinander abweichen? Was geschieht, bevor der Renderer dem Main-Prozess diese Wahl mitteilt? Und wie verhindert man, dass native Dialoge von der React-Oberfläche abweichen?
Wir hielten die erste Version bewusst klein. Sie unterstützt Englisch und Japanisch, verwendet Englisch als Fallback und setzt auf TypeScript sowie eine kurze Testsuite statt eines großen Internationalisierungs-Frameworks. Sechs frühe Entscheidungen machten diese kleine Schicht leichter verständlich.
1. Eine Einstellung speichern, nicht nur eine Sprache
Eine gespeicherte Sprache wie en oder ja kann nicht ausdrücken, dass jemand der macOS-Sprache folgen möchte. Die Systemsprache nur als anfängliche Vorgabe zu behandeln, hat dasselbe Problem: Sobald eine konkrete Sprache gespeichert ist, wirkt sich eine spätere Sprachänderung in macOS nicht mehr aus.
Wir modellierten die Nutzerwahl getrennt von der wirksamen Sprache:
type AppLocale = "en" | "ja";
type AppLocalePreference = "system" | AppLocale;
const resolveAppLocale = (preference: AppLocalePreference, languages: readonly string[]): AppLocale =>
preference === "system" ? detectSystemLocale(languages) : preference;
Nur die Einstellung system fragt navigator.languages ab. Eine manuelle Wahl gewinnt immer, auch wenn sie von macOS abweicht. Wir prüfen den gespeicherten Wert vor der Verwendung und fallen auf system zurück, wenn er fehlt, beschädigt ist oder aus einer nicht mehr unterstützten Version stammt.
Auch die Reihenfolge in navigator.languages ist wichtig. Wir wählen die erste unterstützte Sprache in der Präferenzliste, statt irgendwo nach Japanisch zu suchen und damit eine frühere englische Wahl zu überschreiben. Wird keiner der Einträge unterstützt, verwendet die App Englisch.
Durch die Trennung hat jeder Zustand genau eine Aufgabe. Die Einstellung hält den Nutzerwunsch fest, die Sprache bestimmt die aktuell gerenderte Oberfläche.
2. Sprachoptionen bleiben auch nach einer falschen Wahl lesbar
Im Sprachmenü stehen immer diese Bezeichnungen:
- English
- 日本語
Das sind Autonyme, also die Eigenbezeichnungen der Sprachen. Beide Bezeichnungen in die aktuelle Sprache der Oberfläche zu übersetzen, schafft ein vermeidbares Wiederherstellungsproblem. Wer versehentlich eine unlesbare Sprache auswählt, könnte auch die Bezeichnung verlieren, mit der sich die Auswahl zurücksetzen lässt.
System default übersetzen wir weiterhin, weil diese Option ein App-Verhalten beschreibt und keine Sprache benennt. Tests schreiben die beiden Autonyme in beiden Katalogen fest, damit eine gewöhnliche Übersetzungsänderung sie nicht später verändert.
3. Den englischen Katalog als TypeScript-Vertrag verwenden
Der englische Katalog war bereits die vollständigste Textübersicht der App. Deshalb machten wir seine Schlüssel zum Vertrag für jede unterstützte Sprache.
export const enMessages = {
"settings.language.title": "Language",
"settings.language.english": "English",
"settings.language.japanese": "日本語",
} as const;
export type MessageKey = keyof typeof enMessages;
const catalogs = {
en: enMessages,
ja: jaMessages,
} as const satisfies Record<AppLocale, Record<MessageKey, string>>;
Der Katalogvertrag fängt zwei typische Entwicklungsfehler ab. Eine Komponente kann keinen falsch geschriebenen oder nicht vorhandenen Schlüssel anfordern, und einer Sprache kann kein Schlüssel fehlen, der im Englischen vorkommt. Wird eine englische Nachricht ohne japanisches Gegenstück ergänzt, schlägt die Typprüfung in der Nähe der Katalogänderung fehl.
Typisierte Schlüssel beweisen nicht die Qualität einer Übersetzung. Sie machen strukturelle Vollständigkeit aber zu einer gewöhnlichen Compilerprüfung. Für eine App mit zwei Sprachen und einfacher Nachrichtenform reichte das aus.
4. Laufzeit-Fallback und Vollständigkeitsprüfung beibehalten
Vollständigkeit zur Kompilierzeit macht einen Fallback zur Laufzeit nicht überflüssig. Alte lokale Daten, unerwartete Unterschiede zwischen Builds oder ein künftiger dynamischer Katalog können weiterhin einen fehlenden Wert erzeugen. Die englische Nachricht zurückzugeben ist sicherer, als den umgebenden Bildschirm zu beschädigen.
Der Fallback darf unfertige Arbeit nicht verbergen. Die Typprüfung fordert in jedem Katalog dieselbe Schlüsselmenge, und Tests vergleichen repräsentative Nachrichten, Ersetzungen und stabile Bezeichnungen. Ausfallsicherheit zur Laufzeit und Feedback bei der Entwicklung lösen unterschiedliche Probleme; deshalb behält die Schicht beides.
Interpolation ist bewusst auf einfache benannte Werte wie {count} begrenzt. Pluralregeln, grammatisches Geschlecht und komplexe Nachrichtenauswahl würden ICU MessageFormat oder eine spezielle Bibliothek rechtfertigen. Solange die Texte das nicht benötigen, würde diese Technik das aktuelle System schwerer prüfbar machen, ohne ein vorhandenes Problem zu lösen.
5. Datum und Zahlen außerhalb des Nachrichtenkatalogs formatieren
Übersetzte Beschriftungen wirken nicht lokal, wenn Datum und Zahlen weiterhin den falschen Konventionen folgen. Komponenten erhalten die wirksame App-Sprache und übergeben das passende BCP-47-Tag an die Plattformformatierer.
const getIntlLocale = (locale: AppLocale) => (locale === "ja" ? "ja-JP" : "en-US");
const label = new Intl.DateTimeFormat(getIntlLocale(locale), {
year: "numeric",
month: "2-digit",
day: "2-digit",
}).format(date);
Intl.DateTimeFormat und Intl.NumberFormat außerhalb der Nachrichtenfunktion zu halten verhindert, dass der Katalog zu einer allgemeinen Formatierungsschicht wird. Die Abhängigkeit von der Sprache bleibt außerdem genau dort sichtbar, wo Datum, Uhrzeit, Dateigröße oder Anzahl gerendert werden.
Die Anzeigeformatierung muss von maschinenlesbaren Ausgaben getrennt bleiben. Ein lokalisiertes Datum kann Schrägstriche oder andere Zeichen enthalten, die nicht in einen Dateinamen gehören. Für exportierte Namen verwenden wir vorhersehbare, dateisichere Muster. Lokalisierte Formate bleiben Text vorbehalten, den Menschen sehen.
6. Die Renderer-Wahl mit dem Main-Prozess synchronisieren
Eine Electron-App hat mehr als eine Textoberfläche. React rendert den Großteil unserer Oberfläche. Native Dialoge, aufnahmebezogene Aufforderungen und andere Betriebssysteminteraktionen gehören jedoch dem Main-Prozess. Ein fertiger Renderer-Katalog bedeutete deshalb noch keine vollständig lokalisierte App.
Der Renderer sendet gespeicherte Einstellung und wirksame Sprache über einen typisierten IPC-Endpunkt:
type AppLocaleSyncRequest = {
preference: "system" | "en" | "ja";
locale: "en" | "ja";
};
await window.electron.appLocale.syncPreference({
preference,
locale,
});
Beide Werte zu senden bewahrt ihre jeweilige Bedeutung. Der Main-Prozess kann die wirksame Sprache sofort verwenden und weiß trotzdem, ob sie von macOS oder einer manuellen Überschreibung stammt. Vor der ersten Synchronisierung ermittelt er aus Electrons app.getLocale() eine vorsichtige Vorgabe und fällt auf Englisch zurück, falls diese Abfrage nicht verfügbar ist.
Wir synchronisieren beim Einhängen des Providers und erneut, wenn das Fenster den Fokus zurückerhält. Die zweite Synchronisierung hilft bei der Entwicklung: Ein Reload des Main-Prozesses kann seinen In-Memory-Kontext verlieren, während der Renderer bestehen bleibt. Ein IPC-Fehler blockiert den Renderer nie. Native Texte bleiben lediglich bis zur nächsten Synchronisierung beim sicheren Fallback.
Die Synchronisierung vom Renderer zum Main-Prozess veränderte auch unsere Definition von „fertig“. Wir prüfen React-Texte, Texte des Main-Prozesses und macOS-eigene Oberfläche getrennt. Ein lokalisiertes Einstellungsfenster kann keine englische Beenden-Bestätigung außerhalb des Renderers ausgleichen.
Tests für die Prozessgrenzen
Die erste Testgruppe konzentriert sich auf Zustands- und Prozessgrenzen statt auf Snapshots jedes übersetzten Bildschirms. Sie prüft:
ja-JPergibt Japanisch, wenn es die erste unterstützte Systempräferenz ist.- Eine manuelle Sprache überschreibt die Systemsprache.
- Fehlende oder nicht unterstützte gespeicherte Werte fallen sicher auf die Systemeinstellung zurück.
- Jede Sprache erfüllt die Schlüsselmenge des englischen Katalogs.
Englishund日本語bleiben in beiden Oberflächen unverändert.- Datum und Zahlen verwenden die wirksame App-Sprache.
- Der Main-Prozess erhält sowohl Einstellung als auch wirksame Sprache.
Komponententests decken weiterhin wichtige gerenderte Zustände ab. Diese kleineren Tests finden die meisten Regressionen jedoch nahe an den Funktionen, die das Verhalten bestimmen. Sie sind außerdem günstig genug, um bei jeder Änderung zu laufen.
Auch eine kleine i18n-Schicht braucht klare Grenzen
Nicht die Codemenge macht dieses Design nützlich. Entscheidend ist die Trennung zwischen Einstellung und Sprache, Wiederherstellung und Vollständigkeit, Nachrichten und Formatierung sowie Renderer und Main-Prozess.
Diese Grenzen lassen uns später einzelne Teile ersetzen. Eine dritte oder vierte Sprache könnte generierte Kataloge rechtfertigen. Komplexere Grammatik könnte ICU-Nachrichten rechtfertigen. Keine der beiden Änderungen verlangt, die Bedeutung der gespeicherten Einstellung oder die Zuständigkeit für native Texte neu zu definieren.
Wir haben diese i18n-Schicht beim Hinzufügen von Japanisch zum Action-Capture-Ablauf von Shotomatic gebaut. Shotomatic ist eine von uns entwickelte Mac-App. Die Beispiele oben sind gegenüber der ausgelieferten Anwendung vereinfacht, Zustandsmodell und Prozessgrenze entsprechen aber unserem Code.
Ähnliche Artikel
Weitere ArtikelWebsite-Screenshots mit Playwright automatisieren
Automatisiere Website-Screenshots mit Playwright und JavaScript. Nimm einzelne Seiten, Ganzseiten, mobile Ansichten und URL-Stapel mit Wiederholungen auf.

Website-Screenshots mit Puppeteer automatisieren
Automatisiere Website-Screenshots mit Puppeteer und JavaScript. Erfasse einzelne Seiten, Ganzseiten, URL-Stapel und mobile Ansichten mit einem wiederverwendbaren Skript.

Screenshots auf dem Mac über die Kommandozeile automatisieren
Nutze macOS-screencapture-Schleifen für Vollbild, feste Bereiche und Seitenwechsel, mit sicheren Dateinamen und Fehlerbehebung.

Vor dem Kauf eines digitalen Lehrbuchs fürs Offlinelernen prüfen
Prüfe vor dem Kauf Offlinegeräte, portable Dateien, Drucklimits, Zugriffsdauer, Notizenexport, Barrierefreiheit und Rückgaberegeln.

Verwandle deine Klicks in eine klare Schritt-für-Schritt-Anleitung
Action Capture zeichnet jeden Klick als geordneten Schritt auf. Anschließend bearbeitest und exportierst du die Anleitung auf deinem Mac.