i18n: English/German from Switch system language

- Add I18n module: German when system language is DE, else English (ENUS/ENGB
  or any other language).
- Translate UI strings (tabs, settings, about, file browser, notifications,
  OC row titles, toggles Ein/Aus).
- Keep toggle/frequency/voltage list rows without subtitle descriptions
  like before.

Uses setGetSystemLanguage + setMakeLanguage; call I18n::init before
Application::init.

Made-with: Cursor
This commit is contained in:
Niklas Friesen
2026-03-30 20:09:38 +02:00
parent b5ba2d71df
commit b5296d6686
9 changed files with 361 additions and 47 deletions

264
src/i18n.cpp Normal file
View File

@@ -0,0 +1,264 @@
/*
SWR INI Tool - Switchroot INI Configuration Editor
Copyright (C) 2026 Switchroot
*/
#include "i18n.h"
#ifdef __SWITCH__
#include <switch.h>
#endif
namespace
{
bool gGerman = false;
#ifdef __SWITCH__
void detectSystemLanguage()
{
gGerman = false;
Result rc = setInitialize();
if (!R_SUCCEEDED(rc))
return;
u64 code = 0;
SetLanguage lang = SetLanguage_ENUS;
if (R_SUCCEEDED(setGetSystemLanguage(&code)) && R_SUCCEEDED(setMakeLanguage(code, &lang)))
{
if (lang == SetLanguage_DE)
gGerman = true;
}
setExit();
}
#endif
struct Triple
{
const char* key;
const char* en;
const char* de;
};
static const Triple OC_BOOL_LABEL[] = {
{"oc", "Overclocking", "Übertaktung"},
{"dvfsb", "CPU DVFS Boost", "CPU-DVFS-Boost"},
{"gpu_dvfsc", "GPU DVFS Scaling", "GPU-DVFS-Skalierung"},
{"usb3force", "Force USB 3.0", "USB 3.0 erzwingen"},
{"ddr200_enable", "DDR200 Enable", "DDR200 aktivieren"},
};
static const Triple OC_FREQ_LABEL[] = {
{"max_cpu_freq", "Max CPU Frequency", "Max. CPU-Taktfrequenz"},
{"max_gpu_freq", "Max GPU Frequency", "Max. GPU-Taktfrequenz"},
{"ram_oc", "RAM Frequency", "RAM-Taktfrequenz"},
};
static const Triple OC_VOLT_LABEL[] = {
{"ram_oc_vdd2", "RAM VDD2 Voltage", "RAM VDD2-Spannung"},
{"ram_oc_vddq", "RAM VDDQ Voltage", "RAM VDDQ-Spannung"},
};
const char* lookup(const Triple* table, size_t n, const std::string& key, const char* fallback)
{
for (size_t i = 0; i < n; i++)
{
if (key == table[i].key)
return gGerman ? table[i].de : table[i].en;
}
return fallback;
}
} // namespace
namespace I18n
{
void init()
{
#ifdef __SWITCH__
detectSystemLanguage();
#else
gGerman = false;
#endif
}
bool isGerman()
{
return gGerman;
}
const char* appTitle()
{
return gGerman ? "SWR INI-Tool" : "SWR INI Tool";
}
const char* tabSettings()
{
return gGerman ? "Einstellungen" : "Settings";
}
const char* tabAbout()
{
return gGerman ? "Über" : "About";
}
const char* wordOn()
{
return gGerman ? "Ein" : "ON";
}
const char* wordOff()
{
return gGerman ? "Aus" : "OFF";
}
std::string sectionEditLine(const std::string& section, const std::string& path)
{
if (gGerman)
return "Abschnitt [" + section + "] aus " + path;
return "Editing section [" + section + "] from " + path;
}
const char* headerToggleOptions()
{
return gGerman ? "Schalter" : "Toggle Options";
}
const char* headerFrequencySettings()
{
return gGerman ? "Frequenz" : "Frequency Settings";
}
const char* headerVoltageSettings()
{
return gGerman ? "Spannung" : "Voltage Settings";
}
const char* headerOther()
{
return gGerman ? "Sonstiges" : "Other";
}
const char* headerIniFilePaths()
{
return gGerman ? "INI-Dateipfade" : "INI File Paths";
}
const char* headerLinksAndResources()
{
return gGerman ? "Links und Infos" : "Links and Resources";
}
const char* errorListItemTitleIni()
{
return gGerman ? "\uE150 INI-Problem" : "\uE150 INI file not found";
}
std::string iniLoadErrorBody(const std::string& path)
{
if (gGerman)
return "Die Datei konnte nicht geladen werden:\n" + path + "\n\n"
"Prüfen Sie den Pfad in den Einstellungen.";
return "Could not load " + path + "\n\n"
"Make sure the INI file exists at the expected path.\n"
"You can configure paths in the Settings tab.";
}
std::string iniSectionErrorBody(const std::string& path, const std::string& osName)
{
if (gGerman)
return "Kein OS-Abschnitt in " + path + "\n\n"
"Die INI sollte z. B. einen Abschnitt ["
+ osName + " OC] enthalten.";
return "No OS section found in " + path + "\n\n"
"The INI file should contain a section like\n"
"[" + osName + " OC] with your configuration.";
}
const char* notifyConfigSaved()
{
return gGerman ? "\uE14B Konfiguration gespeichert" : "\uE14B Configuration saved";
}
const char* notifyConfigSaveError()
{
return gGerman ? "\uE150 Fehler beim Speichern!" : "\uE150 Error saving configuration!";
}
const char* notifyPathUpdated()
{
return gGerman ? "\uE14B Pfad aktualisiert" : "\uE14B Path updated";
}
const char* settingsIntro()
{
return gGerman ? "Wählen Sie, welche INI-Datei jeder Reiter liest und schreibt.\n"
"Änderungen gelten sofort."
: "Select which INI file each OS tab reads and writes.\n"
"Changes take effect immediately.";
}
const char* pathListDescriptionLakka()
{
return gGerman ? "Tippen, um eine INI-Datei zu wählen" : "Tap to browse for an INI file";
}
const char* fileBrowserTitlePrefix()
{
return gGerman ? "INI wählen \u2014 " : "Select INI \u2014 ";
}
std::string fileBrowserNoIni(const std::string& dir)
{
if (gGerman)
return "Keine .ini-Dateien in " + dir;
return "No .ini files found in " + dir;
}
const char* aboutSubtitle()
{
return gGerman ? "Switchroot INI-Konfiguration\n"
"Linux-, Android- und Lakka-OC ohne PC bearbeiten!"
: "Switchroot INI Configuration Editor\n"
"Edit your Linux, Android and Lakka OC settings without a PC!";
}
const char* aboutCopyright()
{
return gGerman ? "Lizenziert unter GPL-3.0\n"
"UI: Borealis\n"
"Basierend auf der Arbeit von Switchroot\n"
"\u00A9 2026 NiklasCFW"
: "Licensed under GPL-3.0\n"
"Powered by Borealis UI framework\n"
"Based on the work of Switchroot\n"
"\u00A9 2026 NiklasCFW";
}
const char* aboutLinks()
{
return gGerman ? "\uE016 NiklasCFW-Dokumentation: Anleitungen und Infos\n"
"\uE016 NiklasCFW-Discord: Fragen und Community\n"
"\uE016 Quellcode auf GitHub (OmniNX)"
: "\uE016 NiklasCFW Docs for setup guides and documentation\n"
"\uE016 NiklasCFW's Discord server for support and community\n"
"\uE016 Source code available on the OmniNX GitHub";
}
const char* ocBoolLabel(const std::string& key)
{
return lookup(OC_BOOL_LABEL, sizeof(OC_BOOL_LABEL) / sizeof(OC_BOOL_LABEL[0]), key, key.c_str());
}
const char* ocFreqLabel(const std::string& key)
{
return lookup(OC_FREQ_LABEL, sizeof(OC_FREQ_LABEL) / sizeof(OC_FREQ_LABEL[0]), key, key.c_str());
}
const char* ocVoltageLabel(const std::string& key)
{
return lookup(OC_VOLT_LABEL, sizeof(OC_VOLT_LABEL) / sizeof(OC_VOLT_LABEL[0]), key, key.c_str());
}
} // namespace I18n