diff --git a/platformio.ini b/platformio.ini
index 7d5f9b24c..e8d2b2227 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -62,6 +62,7 @@ build_flags = -Wno-missing-field-initializers
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
#-D OLED_PL=1
+ #-D OLED_ZH=1 ; uncomment for compile-time Chinese font (runtime switching works without this on TFT)
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
#-D DEBUG_LOOP_TIMING=1 ; uncomment to add main loop timing logs
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index bc65a08c8..29bfbec44 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -39,6 +39,7 @@ along with this program. If not, see .
#include "draw/NodeListRenderer.h"
#include "draw/NotificationRenderer.h"
#include "draw/UIRenderer.h"
+#include "graphics/fonts/OLEDDisplayFontsZH.h"
#include "graphics/TFTColorRegions.h"
#include "modules/CannedMessageModule.h"
#include "security/LockdownDisplay.h"
@@ -60,6 +61,7 @@ along with this program. If not, see .
#include "graphics/TFTPalette.h"
#include "graphics/emotes.h"
#include "graphics/images.h"
+#include "graphics/l10n/Strings.h"
#include "input/TouchScreenImpl1.h"
#include "main.h"
#include "mesh-pb-constants.h"
@@ -731,7 +733,10 @@ void Screen::setup()
// Enable display rendering
useDisplay = true;
- // Load saved brightness from UI config
+ // Set the runtime display language from persisted config
+ meshtastic::l10n::currentDisplayLanguage = uiconfig.language;
+
+ // Load brightness from UI config
// For OLED displays (SSD1306), default brightness is 255 if not set
if (uiconfig.screen_brightness == 0) {
#if defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
@@ -819,6 +824,9 @@ void Screen::setup()
// Enable UTF-8 to display mapping
dispdev->setFontTableLookupFunction(customFontTableLookup);
+ // Load CJK (Chinese) font if language requires it
+ loadCJKFontIfNeeded();
+
#ifdef USERPREFS_OEM_TEXT
logo_timeout *= 2; // Give more time for branded boot logos
#endif
@@ -985,6 +993,13 @@ int32_t Screen::runOnce()
return RUN_SAME;
}
+ // Detect language changes from remote config (phone app)
+ if (uiconfig.language != meshtastic::l10n::currentDisplayLanguage) {
+ meshtastic::l10n::currentDisplayLanguage = uiconfig.language;
+ loadCJKFontIfNeeded();
+ setFrames(FOCUS_PRESERVE);
+ }
+
if (displayHeight == 0) {
displayHeight = dispdev->getHeight();
}
@@ -2210,6 +2225,272 @@ bool Screen::isOverlayBannerShowing()
return NotificationRenderer::isOverlayBannerShowing();
}
+char Screen::customFontTableLookup(const uint8_t ch)
+{
+ static uint8_t LASTCHAR;
+ static bool SKIPREST;
+ static int8_t cjkRemaining = 0;
+
+ if (cjkRemaining > 0) {
+ cjkRemaining--;
+ LASTCHAR = ch;
+ return (uint8_t)0;
+ }
+
+ if (ch < 128) {
+ LASTCHAR = 0;
+ SKIPREST = false;
+ cjkRemaining = 0;
+ return ch;
+ }
+
+ uint8_t last = LASTCHAR;
+ LASTCHAR = ch;
+
+ switch (last) {
+ case 0xC2: {
+ SKIPREST = false;
+ return (uint8_t)ch;
+ }
+ case 0xC3: {
+ SKIPREST = false;
+ return (uint8_t)(ch | 0xC0);
+ }
+ }
+
+ if (ch == 0xC2 || ch == 0xC3)
+ return (uint8_t)0;
+
+#if defined(OLED_PL)
+ switch (last) {
+ case 0xC3: {
+ if (ch == 147)
+ return (uint8_t)(ch);
+ else if (ch == 179)
+ return (uint8_t)(148);
+ else
+ return (uint8_t)(ch | 0xC0);
+ break;
+ }
+ case 0xC4: {
+ SKIPREST = false;
+ return (uint8_t)(ch);
+ }
+ case 0xC5: {
+ SKIPREST = false;
+ if (ch == 132)
+ return (uint8_t)(136);
+ else if (ch == 186)
+ return (uint8_t)(137);
+ else
+ return (uint8_t)(ch);
+ break;
+ }
+ }
+ if (ch == 0xC2 || ch == 0xC3 || ch == 0xC4 || ch == 0xC5)
+ return (uint8_t)0;
+#endif
+
+#if defined(OLED_UA) || defined(OLED_RU)
+ switch (last) {
+ case 0xC3: {
+ SKIPREST = false;
+ return (uint8_t)(ch | 0xC0);
+ }
+ case 0xD0: {
+ SKIPREST = false;
+ if (ch == 132)
+ return (uint8_t)(170);
+ if (ch == 134)
+ return (uint8_t)(178);
+ if (ch == 135)
+ return (uint8_t)(175);
+ if (ch == 129)
+ return (uint8_t)(168);
+ if (ch > 143 && ch < 192)
+ return (uint8_t)(ch + 48);
+ break;
+ }
+ case 0xD1: {
+ SKIPREST = false;
+ if (ch == 148)
+ return (uint8_t)(186);
+ if (ch == 150)
+ return (uint8_t)(179);
+ if (ch == 151)
+ return (uint8_t)(191);
+ if (ch == 145)
+ return (uint8_t)(184);
+ if (ch > 127 && ch < 144)
+ return (uint8_t)(ch + 112);
+ break;
+ }
+ case 0xD2: {
+ SKIPREST = false;
+ if (ch == 144)
+ return (uint8_t)(165);
+ if (ch == 145)
+ return (uint8_t)(180);
+ break;
+ }
+ }
+ if (ch == 0xC2 || ch == 0xC3 || ch == 0x82 || ch == 0xD0 || ch == 0xD1)
+ return (uint8_t)0;
+#endif
+
+#if defined(OLED_CS)
+ switch (last) {
+ case 0xC2: {
+ SKIPREST = false;
+ return (uint8_t)ch;
+ }
+ case 0xC3: {
+ SKIPREST = false;
+ return (uint8_t)(ch | 0xC0);
+ }
+ case 0xC4: {
+ SKIPREST = false;
+ if (ch == 140)
+ return (uint8_t)(129);
+ if (ch == 141)
+ return (uint8_t)(138);
+ if (ch == 142)
+ return (uint8_t)(130);
+ if (ch == 143)
+ return (uint8_t)(139);
+ if (ch == 154)
+ return (uint8_t)(131);
+ if (ch == 155)
+ return (uint8_t)(140);
+ if (ch == 185)
+ return (uint8_t)(147);
+ if (ch == 186)
+ return (uint8_t)(148);
+ if (ch == 189)
+ return (uint8_t)(149);
+ if (ch == 190)
+ return (uint8_t)(150);
+ break;
+ }
+ case 0xC5: {
+ SKIPREST = false;
+ if (ch == 135)
+ return (uint8_t)(132);
+ if (ch == 136)
+ return (uint8_t)(141);
+ if (ch == 152)
+ return (uint8_t)(133);
+ if (ch == 153)
+ return (uint8_t)(142);
+ if (ch == 160)
+ return (uint8_t)(134);
+ if (ch == 161)
+ return (uint8_t)(143);
+ if (ch == 164)
+ return (uint8_t)(135);
+ if (ch == 165)
+ return (uint8_t)(144);
+ if (ch == 174)
+ return (uint8_t)(136);
+ if (ch == 175)
+ return (uint8_t)(145);
+ if (ch == 189)
+ return (uint8_t)(137);
+ if (ch == 190)
+ return (uint8_t)(146);
+ if (ch == 148)
+ return (uint8_t)(151);
+ if (ch == 149)
+ return (uint8_t)(152);
+ break;
+ }
+ }
+ if (ch == 0xC2 || ch == 0xC3 || ch == 0xC4 || ch == 0xC5)
+ return (uint8_t)0;
+#endif
+
+#if defined(OLED_GR)
+ switch (last) {
+ case 0xC3: {
+ SKIPREST = false;
+ return (uint8_t)(ch | 0xC0);
+ }
+ case 0xCE: {
+ SKIPREST = false;
+ if (ch >= 145 && ch <= 161)
+ return (uint8_t)(ch + 48);
+ else if (ch >= 163 && ch <= 169)
+ return (uint8_t)(ch + 48);
+ else if (ch >= 177 && ch <= 193)
+ return (uint8_t)(ch + 48);
+ break;
+ }
+ case 0xCF: {
+ SKIPREST = false;
+ if (ch >= 130 && ch <= 137)
+ return (uint8_t)(ch + 112);
+ break;
+ }
+ }
+ if (ch == 0xC2 || ch == 0xC3 || ch == 0xCE || ch == 0xCF)
+ return (uint8_t)0;
+#endif
+
+ if (ch >= 0xE2 && ch <= 0xEF) {
+ cjkRemaining = 2;
+ return (uint8_t)0;
+ }
+
+ if (SKIPREST)
+ return (uint8_t)0;
+ SKIPREST = true;
+
+ return (uint8_t)191;
+}
+
+void Screen::drawTextL10n(int16_t x, int16_t y, const char *text, OLEDDISPLAY_TEXT_ALIGNMENT alignment)
+{
+ if (!dispdev || !text)
+ return;
+
+#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
+ defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(RAK14014)
+ if (meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_SIMPLIFIED_CHINESE ||
+ meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_TRADITIONAL_CHINESE) {
+ TFTDisplay *tftDisp = static_cast(dispdev);
+ if (tftDisp->hasCJKFont()) {
+ tftDisp->drawStringCJK(x, y, text);
+ return;
+ }
+ }
+#endif
+
+ dispdev->setFont(FONT_SMALL);
+ dispdev->setTextAlignment(alignment);
+ dispdev->drawString(x, y, text);
+}
+
+void Screen::setFontL10n(const uint8_t *fontData)
+{
+ if (!dispdev)
+ return;
+ dispdev->setFont(fontData);
+}
+
+void Screen::loadCJKFontIfNeeded()
+{
+#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
+ defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(RAK14014)
+ if (meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_SIMPLIFIED_CHINESE ||
+ meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_TRADITIONAL_CHINESE) {
+ TFTDisplay *tftDisp = static_cast(dispdev);
+ if (!tftDisp->hasCJKFont()) {
+ tftDisp->loadCJKFont(FontZH_CN_16, FontZH_CN_16_size);
+ }
+ }
+#endif
+}
+
} // namespace graphics
#else
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index 37acfbc96..9df71fd94 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -366,255 +366,13 @@ class Screen : public concurrency::OSThread
enqueueCmd(ScreenCmd{.cmd = Cmd::NOOP});
}
- /// Overrides the default utf8 character conversion, to replace empty space with question marks
- static char customFontTableLookup(const uint8_t ch)
- {
- // UTF-8 to font table index converter
- // Code from http://playground.arduino.cc/Main/Utf8ascii
- static uint8_t LASTCHAR;
- static bool SKIPREST; // Only display a single unconvertable-character symbol per sequence of unconvertable characters
+ static char customFontTableLookup(const uint8_t ch);
- if (ch < 128) { // Standard ASCII-set 0..0x7F handling
- LASTCHAR = 0;
- SKIPREST = false;
- return ch;
- }
+ void drawTextL10n(int16_t x, int16_t y, const char *text, OLEDDISPLAY_TEXT_ALIGNMENT alignment = TEXT_ALIGN_LEFT);
+ void setFontL10n(const uint8_t *fontData);
- uint8_t last = LASTCHAR; // get last char
- LASTCHAR = ch;
-
- switch (last) {
- case 0xC2: {
- SKIPREST = false;
- return (uint8_t)ch;
- }
-
- case 0xC3: {
- SKIPREST = false;
- return (uint8_t)(ch | 0xC0);
- }
- }
-
- // We want to strip out prefix chars for two-byte char formats
- if (ch == 0xC2 || ch == 0xC3)
- return (uint8_t)0;
-
-#if defined(OLED_PL)
-
- switch (last) {
- case 0xC3: {
-
- if (ch == 147)
- return (uint8_t)(ch); // Ó
- else if (ch == 179)
- return (uint8_t)(148); // ó
- else
- return (uint8_t)(ch | 0xC0);
- break;
- }
-
- case 0xC4: {
- SKIPREST = false;
- return (uint8_t)(ch);
- }
-
- case 0xC5: {
- SKIPREST = false;
- if (ch == 132)
- return (uint8_t)(136); // ń
- else if (ch == 186)
- return (uint8_t)(137); // ź
- else
- return (uint8_t)(ch);
- break;
- }
- }
-
- // We want to strip out prefix chars for two-byte char formats
- if (ch == 0xC2 || ch == 0xC3 || ch == 0xC4 || ch == 0xC5)
- return (uint8_t)0;
-
-#endif
-
-#if defined(OLED_UA) || defined(OLED_RU)
-
- switch (last) {
- case 0xC3: {
- SKIPREST = false;
- return (uint8_t)(ch | 0xC0);
- }
- // map UTF-8 cyrillic chars to it Windows-1251 (CP-1251) ASCII codes
- // note: in this case we must use compatible font - provided ArialMT_Plain_10/16/24 by 'ThingPulse/esp8266-oled-ssd1306'
- // library have empty chars for non-latin ASCII symbols
- case 0xD0: {
- SKIPREST = false;
- if (ch == 132)
- return (uint8_t)(170); // Є
- if (ch == 134)
- return (uint8_t)(178); // І
- if (ch == 135)
- return (uint8_t)(175); // Ї
- if (ch == 129)
- return (uint8_t)(168); // Ё
- if (ch > 143 && ch < 192)
- return (uint8_t)(ch + 48);
- break;
- }
- case 0xD1: {
- SKIPREST = false;
- if (ch == 148)
- return (uint8_t)(186); // є
- if (ch == 150)
- return (uint8_t)(179); // і
- if (ch == 151)
- return (uint8_t)(191); // ї
- if (ch == 145)
- return (uint8_t)(184); // ё
- if (ch > 127 && ch < 144)
- return (uint8_t)(ch + 112);
- break;
- }
- case 0xD2: {
- SKIPREST = false;
- if (ch == 144)
- return (uint8_t)(165); // Ґ
- if (ch == 145)
- return (uint8_t)(180); // ґ
- break;
- }
- }
-
- // We want to strip out prefix chars for two-byte char formats
- if (ch == 0xC2 || ch == 0xC3 || ch == 0x82 || ch == 0xD0 || ch == 0xD1)
- return (uint8_t)0;
-
-#endif
-
-#if defined(OLED_CS)
-
- switch (last) {
- case 0xC2: {
- SKIPREST = false;
- return (uint8_t)ch;
- }
-
- case 0xC3: {
- SKIPREST = false;
- return (uint8_t)(ch | 0xC0);
- }
-
- case 0xC4: {
- SKIPREST = false;
- if (ch == 140)
- return (uint8_t)(129); // Č
- if (ch == 141)
- return (uint8_t)(138); // č
- if (ch == 142)
- return (uint8_t)(130); // Ď
- if (ch == 143)
- return (uint8_t)(139); // ď
- if (ch == 154)
- return (uint8_t)(131); // Ě
- if (ch == 155)
- return (uint8_t)(140); // ě
- // Slovak specific glyphs
- if (ch == 185)
- return (uint8_t)(147); // Ĺ
- if (ch == 186)
- return (uint8_t)(148); // ĺ
- if (ch == 189)
- return (uint8_t)(149); // Ľ
- if (ch == 190)
- return (uint8_t)(150); // ľ
- break;
- }
-
- case 0xC5: {
- SKIPREST = false;
- if (ch == 135)
- return (uint8_t)(132); // Ň
- if (ch == 136)
- return (uint8_t)(141); // ň
- if (ch == 152)
- return (uint8_t)(133); // Ř
- if (ch == 153)
- return (uint8_t)(142); // ř
- if (ch == 160)
- return (uint8_t)(134); // Š
- if (ch == 161)
- return (uint8_t)(143); // š
- if (ch == 164)
- return (uint8_t)(135); // Ť
- if (ch == 165)
- return (uint8_t)(144); // ť
- if (ch == 174)
- return (uint8_t)(136); // Ů
- if (ch == 175)
- return (uint8_t)(145); // ů
- if (ch == 189)
- return (uint8_t)(137); // Ž
- if (ch == 190)
- return (uint8_t)(146); // ž
- // Slovak specific glyphs
- if (ch == 148)
- return (uint8_t)(151); // Ŕ
- if (ch == 149)
- return (uint8_t)(152); // ŕ
- break;
- }
- }
-
- // We want to strip out prefix chars for two-byte char formats
- if (ch == 0xC2 || ch == 0xC3 || ch == 0xC4 || ch == 0xC5)
- return (uint8_t)0;
-
-#endif
-
-#if defined(OLED_GR)
-
- switch (last) {
- case 0xC3: {
- SKIPREST = false;
- return (uint8_t)(ch | 0xC0);
- }
- // Map UTF-8 Greek chars to Windows-1253 (CP-1253) ASCII codes
- case 0xCE: {
- SKIPREST = false;
- // Uppercase Greek: Α-Ρ (U+0391-U+03A1) -> CP-1253 193-209
- if (ch >= 145 && ch <= 161)
- return (uint8_t)(ch + 48);
- // Uppercase Greek: Σ-Ω (U+03A3-U+03A9) -> CP-1253 211-217
- else if (ch >= 163 && ch <= 169)
- return (uint8_t)(ch + 48);
- // Lowercase Greek: α-ρ (U+03B1-U+03C1) -> CP-1253 225-241
- else if (ch >= 177 && ch <= 193)
- return (uint8_t)(ch + 48);
- break;
- }
- case 0xCF: {
- SKIPREST = false;
- // Lowercase Greek: ς-ω (U+03C2-U+03C9) -> CP-1253 242-249
- if (ch >= 130 && ch <= 137)
- return (uint8_t)(ch + 112);
- break;
- }
- }
-
- // We want to strip out prefix chars for two-byte Greek char formats
- if (ch == 0xC2 || ch == 0xC3 || ch == 0xCE || ch == 0xCF)
- return (uint8_t)0;
-
-#endif
-
- // If we already returned an unconvertable-character symbol for this unconvertable-character sequence, return NULs for the
- // rest of it
- if (SKIPREST)
- return (uint8_t)0;
- SKIPREST = true;
-
- return (uint8_t)191; // otherwise: return ¿ if character can't be converted (note that the font map we're using doesn't
- // stick to standard EASCII codes)
- }
+ // Load CJK font if language requires it (called from setup & on language change)
+ void loadCJKFontIfNeeded();
/// Returns a handle to the DebugInfo screen.
//
diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h
index 624465f33..1769e3fb7 100644
--- a/src/graphics/ScreenFonts.h
+++ b/src/graphics/ScreenFonts.h
@@ -20,6 +20,10 @@
#include "graphics/fonts/OLEDDisplayFontsGR.h"
#endif
+#ifdef OLED_ZH
+#include "graphics/fonts/OLEDDisplayFontsZH.h"
+#endif
+
#if (defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(T5_S3_EPAPER_PRO)) && defined(USE_EINK)
#include "graphics/fonts/EinkDisplayFonts.h"
#endif
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index 4ef707efe..b1ce78e46 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -1636,4 +1636,35 @@ bool TFTDisplay::connect()
return true;
}
+bool TFTDisplay::loadCJKFont(const uint8_t *vlwData, uint32_t vlwDataLen)
+{
+ if (!tft || !vlwData || vlwDataLen == 0)
+ return false;
+ cjkFontLoaded = tft->loadFont(vlwData);
+ if (cjkFontLoaded) {
+ LOG_INFO("CJK font loaded (%u bytes)", vlwDataLen);
+ } else {
+ LOG_WARN("Failed to load CJK font");
+ }
+ return cjkFontLoaded;
+}
+
+void TFTDisplay::drawStringCJK(int16_t x, int16_t y, const char *text)
+{
+ if (!tft || !text || !cjkFontLoaded)
+ return;
+ concurrency::LockGuard g(spiLock);
+ uint16_t fgColor = getThemeDefaultOnColor();
+ tft->setTextColor(fgColor, getThemeDefaultOffColor());
+ tft->setCursor(x, y);
+ tft->print(text);
+}
+
+int16_t TFTDisplay::getStringWidthCJK(const char *text)
+{
+ if (!tft || !text || !cjkFontLoaded)
+ return 0;
+ return tft->textWidth(text);
+}
+
#endif // USE_TFTDISPLAY
diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h
index 2c86f05d2..ea955383e 100644
--- a/src/graphics/TFTDisplay.h
+++ b/src/graphics/TFTDisplay.h
@@ -38,6 +38,12 @@ class TFTDisplay : public OLEDDisplay
// Functions for changing display brightness
void setDisplayBrightness(uint8_t);
+ // CJK (Chinese/Japanese/Korean) text rendering via LovyanGFX
+ bool loadCJKFont(const uint8_t *vlwData, uint32_t vlwDataLen);
+ void drawStringCJK(int16_t x, int16_t y, const char *text);
+ int16_t getStringWidthCJK(const char *text);
+ bool hasCJKFont() const { return cjkFontLoaded; }
+
/**
* shim to make the abstraction happy
*
@@ -64,4 +70,5 @@ class TFTDisplay : public OLEDDisplay
uint16_t *linePixelBuffer = nullptr;
uint16_t *repaintChunkBuffer = nullptr;
+ bool cjkFontLoaded = false;
};
diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index 0f4d8d713..e13287f3f 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -13,6 +13,7 @@
#include "graphics/Screen.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/TFTColorRegions.h"
+#include "graphics/l10n/Strings.h"
#include "graphics/draw/MessageRenderer.h"
#include "graphics/draw/UIRenderer.h"
#include "input/RotaryEncoderInterruptImpl1.h"
@@ -2377,9 +2378,9 @@ void menuHandler::screenOptionsMenu()
bool hasSupportBrightness = false;
#endif
- enum optionsNumbers { Back, Brightness, FrameToggles, DisplayUnits, MessageBubbles, Theme };
- static const char *optionsArray[7] = {"Back"};
- static int optionsEnumArray[7] = {Back};
+ enum optionsNumbers { Back, Brightness, FrameToggles, DisplayUnits, MessageBubbles, Theme, LangPicker };
+ static const char *optionsArray[8] = {"Back"};
+ static int optionsEnumArray[8] = {Back};
int options = 1;
// Only show brightness for B&W displays
@@ -2402,6 +2403,12 @@ void menuHandler::screenOptionsMenu()
optionsEnumArray[options++] = Theme;
#endif
+#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
+ defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(RAK14014)
+ optionsArray[options] = "Language";
+ optionsEnumArray[options++] = LangPicker;
+#endif
+
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Display Options";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -2423,6 +2430,9 @@ void menuHandler::screenOptionsMenu()
} else if (selected == Theme) {
menuHandler::menuQueue = menuHandler::ThemeMenu;
screen->runNow();
+ } else if (selected == LangPicker) {
+ menuHandler::menuQueue = menuHandler::LanguagePicker;
+ screen->runNow();
} else {
menuQueue = SystemBaseMenu;
screen->runNow();
@@ -2903,6 +2913,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case ThemeMenu:
themeMenu();
break;
+ case LanguagePicker:
+ languagePickerMenu();
+ break;
case HamModeConfirm:
hamModeConfirmMenu();
break;
@@ -2913,6 +2926,44 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
menuQueue = MenuNone;
}
+void menuHandler::languagePickerMenu()
+{
+ enum optionsNumbers { Back, English, Chinese };
+ static const char *optionsArray[3] = {"Back"};
+ static int optionsEnumArray[3] = {Back};
+ int options = 1;
+
+ optionsArray[options] = "English";
+ optionsEnumArray[options++] = English;
+
+ optionsArray[options] = "Chinese";
+ optionsEnumArray[options++] = Chinese;
+
+ BannerOverlayOptions bannerOptions;
+ bannerOptions.message = "Language";
+ bannerOptions.optionsArrayPtr = optionsArray;
+ bannerOptions.optionsCount = options;
+ bannerOptions.optionsEnumPtr = optionsEnumArray;
+ bannerOptions.bannerCallback = [](int selected) -> void {
+ if (selected == English) {
+ uiconfig.language = meshtastic_Language_ENGLISH;
+ meshtastic::l10n::currentDisplayLanguage = meshtastic_Language_ENGLISH;
+ saveUIConfig();
+ } else if (selected == Chinese) {
+ uiconfig.language = meshtastic_Language_SIMPLIFIED_CHINESE;
+ meshtastic::l10n::currentDisplayLanguage = meshtastic_Language_SIMPLIFIED_CHINESE;
+ saveUIConfig();
+#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
+ defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(RAK14014)
+ screen->loadCJKFontIfNeeded();
+#endif
+ }
+ menuQueue = ScreenOptionsMenu;
+ screen->runNow();
+ };
+ screen->showOverlayBanner(bannerOptions);
+}
+
void menuHandler::saveUIConfig()
{
nodeDB->saveProto("/prefs/uiconfig.proto", meshtastic_DeviceUIConfig_size, &meshtastic_DeviceUIConfig_msg, &uiconfig);
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index b924c9197..9b46160d7 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -56,6 +56,7 @@ class menuHandler
DisplayUnits,
MessageBubblesMenu,
ThemeMenu,
+ LanguagePicker,
HamModeConfirm,
LicensedToNormalConfirm
};
@@ -107,6 +108,7 @@ class menuHandler
static void wifiBaseMenu();
static void wifiToggleMenu();
static void screenOptionsMenu();
+ static void languagePickerMenu();
static void powerMenu();
static void nodeNameLengthMenu();
static void frameTogglesMenu();
diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp
index a42ef1e08..ec46a8e44 100644
--- a/src/graphics/draw/MessageRenderer.cpp
+++ b/src/graphics/draw/MessageRenderer.cpp
@@ -13,6 +13,7 @@
#include "graphics/SharedUIDisplay.h"
#include "graphics/TFTColorRegions.h"
#include "graphics/TFTPalette.h"
+#include "graphics/l10n/Strings.h"
#include "graphics/TimeFormatters.h"
#include "graphics/emotes.h"
#include "main.h"
@@ -75,6 +76,15 @@ void scrollDown()
void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount)
{
+#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
+ defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(RAK14014) || defined(USE_ST7789) || \
+ defined(USE_ST7796)
+ if (meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_SIMPLIFIED_CHINESE ||
+ meshtastic::l10n::currentDisplayLanguage == meshtastic_Language_TRADITIONAL_CHINESE) {
+ screen->drawTextL10n(x, y, line.c_str());
+ return;
+ }
+#endif
graphics::EmoteRenderer::drawStringWithEmotes(display, x, y, line, FONT_HEIGHT_SMALL, emotes, emoteCount);
}
diff --git a/src/graphics/fonts/OLEDDisplayFontsZH.cpp b/src/graphics/fonts/OLEDDisplayFontsZH.cpp
new file mode 100644
index 000000000..060b7054f
--- /dev/null
+++ b/src/graphics/fonts/OLEDDisplayFontsZH.cpp
@@ -0,0 +1,46 @@
+// Chinese font data (VLW format) for OLEDDisplay-based TFT rendering via LovyanGFX.
+//
+// HOW TO GENERATE FONT DATA:
+//
+// Option 1: Adafruit GFX fontconvert (simpler, fewer glyphs)
+// brew install adafruitnrf52-gfx-fontconvert # macOS
+// fontconvert NotoSansSC-Regular.ttf 16 > zh_cn_16.h
+// The output is a GFXfont struct. LovyanGFX can use this via ft_gfx type.
+//
+// Option 2: LovyanGFX VLW format (recommended, more features)
+// Use the LovyanGFX font converter script to generate a .vlw file from TTF
+// Then embed the .vlw binary as a byte array using xxd:
+// xxd -i zh_font_16.vlw > zh_font_16_data.h
+//
+// Option 3: Use BDF fonts (free, text-based)
+// WenQuanYi bitmap fonts: http://wenq.org/
+// Convert BDF to VLW using LovyanGFX tools
+//
+// FONT SIZE GUIDELINES:
+// - 16px: Minimum readable Chinese on 320x240 TFT (~20 chars per line)
+// - 20px: Better readability, ~16 chars per line
+// - 24px: Title/header text, ~13 chars per line
+//
+// CHARACTER SET RECOMMENDATIONS:
+// - GB2312 Level 1: 3755 commonly used simplified Chinese characters
+// - Plus ASCII 0x20-0x7E for punctuation and Latin characters
+//
+// PLACEHOLDER DATA - Replace with generated font binary:
+
+#include "OLEDDisplayFontsZH.h"
+
+// Placeholder font data.
+// Replace with actual generated VLW/Gfx font binary from a Chinese TTF file.
+// The array below is intentionally minimal - it won't render any characters
+// until real font data is provided.
+const uint8_t FontZH_CN_16[] = {
+ // VLW font file magic header would go here
+ // Format: 0x00 0x00 ...
+ // Replace with output from: xxd -i your_chinese_font.vlw
+};
+const uint32_t FontZH_CN_16_size = sizeof(FontZH_CN_16);
+
+const uint8_t FontZH_TW_16[] = {
+ // Traditional Chinese VLW font data placeholder
+};
+const uint32_t FontZH_TW_16_size = sizeof(FontZH_TW_16);
diff --git a/src/graphics/fonts/OLEDDisplayFontsZH.h b/src/graphics/fonts/OLEDDisplayFontsZH.h
new file mode 100644
index 000000000..ecfe2610b
--- /dev/null
+++ b/src/graphics/fonts/OLEDDisplayFontsZH.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include
+
+// Chinese Simplified font - VLW format (to be generated from TTF)
+//
+// HOW TO GENERATE:
+// 1. Download NotoSansSC-Regular.ttf (or any open-source Chinese font)
+// 2. Use Adafruit fontconvert tool:
+// ./fontconvert NotoSansSC-Regular.ttf 16 > zh_cn_16_pt.h
+// 3. Copy the resulting binary data (GFXfont struct bytes) into the array below
+//
+// For LovyanGFX VLW font format, use:
+// - The binary VLW file can be embedded as a const uint8_t[] array
+// - Loaded with tft->loadFont(zh_font_data_array)
+
+extern const uint8_t FontZH_CN_16[];
+extern const uint32_t FontZH_CN_16_size;
+
+// Chinese Traditional font (VLW format)
+extern const uint8_t FontZH_TW_16[];
+extern const uint32_t FontZH_TW_16_size;
diff --git a/src/graphics/l10n/Strings.cpp b/src/graphics/l10n/Strings.cpp
new file mode 100644
index 000000000..5a2adf41e
--- /dev/null
+++ b/src/graphics/l10n/Strings.cpp
@@ -0,0 +1,20 @@
+#include "Strings.h"
+#include "Strings_EN.h"
+#include "Strings_ZH.h"
+
+namespace meshtastic
+{
+namespace l10n
+{
+
+meshtastic_Language currentDisplayLanguage = meshtastic_Language_ENGLISH;
+
+const char *getString(StringKey key, meshtastic_Language lang)
+{
+ if (lang == meshtastic_Language_SIMPLIFIED_CHINESE || lang == meshtastic_Language_TRADITIONAL_CHINESE)
+ return getStringZH(key);
+ return getStringEN(key);
+}
+
+} // namespace l10n
+} // namespace meshtastic
diff --git a/src/graphics/l10n/Strings.h b/src/graphics/l10n/Strings.h
new file mode 100644
index 000000000..6464e3877
--- /dev/null
+++ b/src/graphics/l10n/Strings.h
@@ -0,0 +1,138 @@
+#pragma once
+
+#include "mesh/generated/meshtastic/device_ui.pb.h"
+
+namespace meshtastic
+{
+namespace l10n
+{
+
+enum class StringKey : uint8_t {
+ Back,
+ OK,
+ GotIt,
+ WelcomeToMeshtastic,
+ SwipeToNavigate,
+ ClickToNavigate,
+ UseSelectButton,
+ DeviceRole,
+ RadioPreset,
+ FrequencySlot,
+ LoRaRegion,
+ Client,
+ ClientMute,
+ LostAndFound,
+ Tracker,
+ Hour12,
+ Hour24,
+ ClockAction,
+ ClockFace,
+ TimeFormat,
+ Timezone,
+ MessageAction,
+ Reply,
+ ViewChats,
+ UnmuteChannel,
+ MuteChannel,
+ Delete,
+ ReadAloud,
+ Brightness,
+ FrameVisibility,
+ DisplayUnits,
+ MessageBubbles,
+ Theme,
+ Reboot,
+ Shutdown,
+ SwitchToMUI,
+ DisplayOptions,
+ Language,
+ Chinese,
+ English,
+ NoGPS,
+ GPSoff,
+ GPSnotPresent,
+ GPSisDisabled,
+ Node,
+ Hop,
+ USB,
+ BT_off,
+ WiFi,
+ WiFiNotConnected,
+ WiFiConnected,
+ SSID,
+ LoRaInfo,
+ LoRa,
+ Role,
+ Freq,
+ ChUtil,
+ System,
+ Heap,
+ PSRAM,
+ Flash,
+ SD,
+ Ver,
+ Up,
+ ClientApp,
+ NoConnected,
+ LOCKED,
+ ConnectToUnlock,
+ Battery,
+ Resuming,
+ LastHeard,
+ HopsSig,
+ HopsSignal,
+ Nodes,
+ Distance,
+ Bearings,
+ CreatingSSLCert,
+ PleaseWait,
+ CriticalFault,
+ ForHelpVisit,
+ Updating,
+ PleaseBePatient,
+ RebootInProgress,
+ PowerMenu,
+ SystemMenu,
+ ScreenOptions,
+ WifiToggle,
+ BluetoothToggle,
+ NodeDbReset,
+ ManageNode,
+ RemoveFavorite,
+ AddFavorite,
+ TestMenu,
+ TraceRoute,
+ ThrottleMessage,
+ MessageViewMode,
+ DeleteMessages,
+ NodeNameLength,
+ GPSMenu,
+ GPSToggle,
+ GPSFormat,
+ GPSSmartPosition,
+ GPSUpdateInterval,
+ GPSPositionBroadcast,
+ CompassNorth,
+ BuzzerMode,
+ HamModeConfirm,
+ LicensedToNormal,
+ KeyVerification,
+ FinalPrompt,
+ RegionUnset,
+ SelectRegionMessage,
+ Rebooting,
+ COUNT
+};
+
+extern meshtastic_Language currentDisplayLanguage;
+
+const char *getString(StringKey key, meshtastic_Language lang);
+
+inline const char *getString(StringKey key)
+{
+ extern meshtastic_Language currentDisplayLanguage;
+ return getString(key, currentDisplayLanguage);
+}
+
+} // namespace l10n
+} // namespace meshtastic
diff --git a/src/graphics/l10n/Strings_EN.h b/src/graphics/l10n/Strings_EN.h
new file mode 100644
index 000000000..c8ec3c12a
--- /dev/null
+++ b/src/graphics/l10n/Strings_EN.h
@@ -0,0 +1,360 @@
+#pragma once
+
+#include "Strings.h"
+
+namespace meshtastic
+{
+namespace l10n
+{
+
+// English string table
+// The strings are stored as const arrays to save RAM on constrained devices.
+// Each locale file defines the same set of strings in its own language.
+
+static const char STR_BACK_EN[] = "Back";
+static const char STR_OK_EN[] = "OK";
+static const char STR_GOT_IT_EN[] = "Got it!";
+static const char STR_WELCOME_TO_MESHTASTIC_EN[] = "Welcome to Meshtastic!";
+static const char STR_SWIPE_TO_NAVIGATE_EN[] = "Swipe to navigate.";
+static const char STR_CLICK_TO_NAVIGATE_EN[] = "Click to navigate.";
+static const char STR_USE_SELECT_BUTTON_EN[] = "Use the Select button.";
+static const char STR_DEVICE_ROLE_EN[] = "Device Role";
+static const char STR_RADIO_PRESET_EN[] = "Radio Preset";
+static const char STR_FREQUENCY_SLOT_EN[] = "Frequency Slot";
+static const char STR_LORA_REGION_EN[] = "LoRa Region";
+static const char STR_CLIENT_EN[] = "Client";
+static const char STR_CLIENT_MUTE_EN[] = "Client Mute";
+static const char STR_LOST_AND_FOUND_EN[] = "Lost and Found";
+static const char STR_TRACKER_EN[] = "Tracker";
+static const char STR_12HOUR_EN[] = "12-hour";
+static const char STR_24HOUR_EN[] = "24-hour";
+static const char STR_CLOCK_ACTION_EN[] = "Clock Action";
+static const char STR_CLOCK_FACE_EN[] = "Clock Face";
+static const char STR_TIME_FORMAT_EN[] = "Time Format";
+static const char STR_TIMEZONE_EN[] = "Timezone";
+static const char STR_MESSAGE_ACTION_EN[] = "Message Action";
+static const char STR_REPLY_EN[] = "Reply";
+static const char STR_VIEW_CHATS_EN[] = "View Chats";
+static const char STR_UNMUTE_CHANNEL_EN[] = "Unmute Channel";
+static const char STR_MUTE_CHANNEL_EN[] = "Mute Channel";
+static const char STR_DELETE_EN[] = "Delete";
+static const char STR_READ_ALOUD_EN[] = "Read Aloud";
+static const char STR_BRIGHTNESS_EN[] = "Brightness";
+static const char STR_FRAME_VISIBILITY_EN[] = "Frame Visibility";
+static const char STR_DISPLAY_UNITS_EN[] = "Display Units";
+static const char STR_MESSAGE_BUBBLES_EN[] = "Message Bubbles";
+static const char STR_THEME_EN[] = "Theme";
+static const char STR_REBOOT_EN[] = "Reboot";
+static const char STR_SHUTDOWN_EN[] = "Shutdown";
+static const char STR_SWITCH_TO_MUI_EN[] = "Switch to MUI";
+static const char STR_DISPLAY_OPTIONS_EN[] = "Display Options";
+static const char STR_LANGUAGE_EN[] = "Language";
+static const char STR_CHINESE_EN[] = "Chinese";
+static const char STR_ENGLISH_EN[] = "English";
+static const char STR_NO_GPS_EN[] = "No GPS";
+static const char STR_GPS_OFF_EN[] = "GPS off";
+static const char STR_GPS_NOT_PRESENT_EN[] = "GPS not present";
+static const char STR_GPS_IS_DISABLED_EN[] = "GPS is disabled";
+static const char STR_NODE_EN[] = "Node";
+static const char STR_HOP_EN[] = "Hop:";
+static const char STR_USB_EN[] = "USB";
+static const char STR_BT_OFF_EN[] = "BT off";
+static const char STR_WIFI_EN[] = "WiFi";
+static const char STR_WIFI_NOT_CONNECTED_EN[] = "WiFi: Not Connected";
+static const char STR_WIFI_CONNECTED_EN[] = "WiFi: Connected";
+static const char STR_SSID_EN[] = "SSID:";
+static const char STR_LORA_INFO_EN[] = "LoRa Info";
+static const char STR_LORA_EN[] = "LoRa";
+static const char STR_ROLE_EN[] = "Role:";
+static const char STR_FREQ_EN[] = "Freq:";
+static const char STR_CHUTIL_EN[] = "ChUtil";
+static const char STR_SYSTEM_EN[] = "System";
+static const char STR_HEAP_EN[] = "Heap:";
+static const char STR_PSRAM_EN[] = "PSRAM:";
+static const char STR_FLASH_EN[] = "Flash:";
+static const char STR_SD_EN[] = "SD:";
+static const char STR_VER_EN[] = "Ver:";
+static const char STR_UP_EN[] = "Up:";
+static const char STR_CLIENT_APP_EN[] = "Client";
+static const char STR_NO_CONNECTED_EN[] = "No Connected";
+static const char STR_LOCKED_EN[] = "LOCKED";
+static const char STR_CONNECT_TO_UNLOCK_EN[] = "Connect to unlock";
+static const char STR_BATTERY_EN[] = "Battery";
+static const char STR_RESUMING_EN[] = "Resuming...";
+static const char STR_LAST_HEARD_EN[] = "Last Heard";
+static const char STR_HOPS_SIG_EN[] = "Hops/Sig";
+static const char STR_HOPS_SIGNAL_EN[] = "Hops/Signal";
+static const char STR_NODES_EN[] = "Nodes";
+static const char STR_DISTANCE_EN[] = "Distance";
+static const char STR_BEARINGS_EN[] = "Bearings";
+static const char STR_CREATING_SSL_EN[] = "Creating SSL certificate";
+static const char STR_PLEASE_WAIT_EN[] = "Please wait . . .";
+static const char STR_CRITICAL_FAULT_EN[] = "Critical fault";
+static const char STR_FOR_HELP_VISIT_EN[] = "For help, please visit";
+static const char STR_UPDATING_EN[] = "Updating";
+static const char STR_PLEASE_BE_PATIENT_EN[] = "Please be patient and do not power off.";
+static const char STR_REBOOT_IN_PROGRESS_EN[] = "Reboot in progress...";
+static const char STR_POWER_MENU_EN[] = "Power";
+static const char STR_SYSTEM_MENU_EN[] = "System";
+static const char STR_SCREEN_OPTIONS_EN[] = "Screen";
+static const char STR_WIFI_TOGGLE_EN[] = "WiFi";
+static const char STR_BLUETOOTH_TOGGLE_EN[] = "Bluetooth";
+static const char STR_NODE_DB_RESET_EN[] = "Reset NodeDB";
+static const char STR_MANAGE_NODE_EN[] = "Manage Node";
+static const char STR_REMOVE_FAVORITE_EN[] = "Remove Favorite";
+static const char STR_ADD_FAVORITE_EN[] = "Add Favorite";
+static const char STR_TEST_MENU_EN[] = "Test";
+static const char STR_TRACE_ROUTE_EN[] = "Trace Route";
+static const char STR_THROTTLE_MSG_EN[] = "Message Throttled";
+static const char STR_MSG_VIEW_MODE_EN[] = "View Mode";
+static const char STR_DELETE_MESSAGES_EN[] = "Delete Messages";
+static const char STR_NODE_NAME_LENGTH_EN[] = "Name Length";
+static const char STR_GPS_MENU_EN[] = "GPS";
+static const char STR_GPS_TOGGLE_EN[] = "GPS Toggle";
+static const char STR_GPS_FORMAT_EN[] = "GPS Format";
+static const char STR_GPS_SMART_POS_EN[] = "Smart Position";
+static const char STR_GPS_UPDATE_INTERVAL_EN[] = "Update Interval";
+static const char STR_GPS_POS_BCAST_EN[] = "Position Broadcast";
+static const char STR_COMPASS_NORTH_EN[] = "Compass North";
+static const char STR_BUZZER_MODE_EN[] = "Buzzer";
+static const char STR_HAM_MODE_EN[] = "Ham Mode";
+static const char STR_LICENSED_NORMAL_EN[] = "Normal Mode";
+static const char STR_KEY_VERIFY_EN[] = "Verify Key";
+static const char STR_REGION_UNSET_EN[] = "Select a Region";
+static const char STR_SELECT_REGION_EN[] = "Please select your LoRa region:";
+static const char STR_REBOOTING_EN[] = "Rebooting...";
+
+inline const char *getStringEN(StringKey key)
+{
+ switch (key) {
+ case StringKey::Back:
+ return STR_BACK_EN;
+ case StringKey::OK:
+ return STR_OK_EN;
+ case StringKey::GotIt:
+ return STR_GOT_IT_EN;
+ case StringKey::WelcomeToMeshtastic:
+ return STR_WELCOME_TO_MESHTASTIC_EN;
+ case StringKey::SwipeToNavigate:
+ return STR_SWIPE_TO_NAVIGATE_EN;
+ case StringKey::ClickToNavigate:
+ return STR_CLICK_TO_NAVIGATE_EN;
+ case StringKey::UseSelectButton:
+ return STR_USE_SELECT_BUTTON_EN;
+ case StringKey::DeviceRole:
+ return STR_DEVICE_ROLE_EN;
+ case StringKey::RadioPreset:
+ return STR_RADIO_PRESET_EN;
+ case StringKey::FrequencySlot:
+ return STR_FREQUENCY_SLOT_EN;
+ case StringKey::LoRaRegion:
+ return STR_LORA_REGION_EN;
+ case StringKey::Client:
+ return STR_CLIENT_EN;
+ case StringKey::ClientMute:
+ return STR_CLIENT_MUTE_EN;
+ case StringKey::LostAndFound:
+ return STR_LOST_AND_FOUND_EN;
+ case StringKey::Tracker:
+ return STR_TRACKER_EN;
+ case StringKey::Hour12:
+ return STR_12HOUR_EN;
+ case StringKey::Hour24:
+ return STR_24HOUR_EN;
+ case StringKey::ClockAction:
+ return STR_CLOCK_ACTION_EN;
+ case StringKey::ClockFace:
+ return STR_CLOCK_FACE_EN;
+ case StringKey::TimeFormat:
+ return STR_TIME_FORMAT_EN;
+ case StringKey::Timezone:
+ return STR_TIMEZONE_EN;
+ case StringKey::MessageAction:
+ return STR_MESSAGE_ACTION_EN;
+ case StringKey::Reply:
+ return STR_REPLY_EN;
+ case StringKey::ViewChats:
+ return STR_VIEW_CHATS_EN;
+ case StringKey::UnmuteChannel:
+ return STR_UNMUTE_CHANNEL_EN;
+ case StringKey::MuteChannel:
+ return STR_MUTE_CHANNEL_EN;
+ case StringKey::Delete:
+ return STR_DELETE_EN;
+ case StringKey::ReadAloud:
+ return STR_READ_ALOUD_EN;
+ case StringKey::Brightness:
+ return STR_BRIGHTNESS_EN;
+ case StringKey::FrameVisibility:
+ return STR_FRAME_VISIBILITY_EN;
+ case StringKey::DisplayUnits:
+ return STR_DISPLAY_UNITS_EN;
+ case StringKey::MessageBubbles:
+ return STR_MESSAGE_BUBBLES_EN;
+ case StringKey::Theme:
+ return STR_THEME_EN;
+ case StringKey::Reboot:
+ return STR_REBOOT_EN;
+ case StringKey::Shutdown:
+ return STR_SHUTDOWN_EN;
+ case StringKey::SwitchToMUI:
+ return STR_SWITCH_TO_MUI_EN;
+ case StringKey::DisplayOptions:
+ return STR_DISPLAY_OPTIONS_EN;
+ case StringKey::Language:
+ return STR_LANGUAGE_EN;
+ case StringKey::Chinese:
+ return STR_CHINESE_EN;
+ case StringKey::English:
+ return STR_ENGLISH_EN;
+ case StringKey::NoGPS:
+ return STR_NO_GPS_EN;
+ case StringKey::GPSoff:
+ return STR_GPS_OFF_EN;
+ case StringKey::GPSnotPresent:
+ return STR_GPS_NOT_PRESENT_EN;
+ case StringKey::GPSisDisabled:
+ return STR_GPS_IS_DISABLED_EN;
+ case StringKey::Node:
+ return STR_NODE_EN;
+ case StringKey::Hop:
+ return STR_HOP_EN;
+ case StringKey::USB:
+ return STR_USB_EN;
+ case StringKey::BT_off:
+ return STR_BT_OFF_EN;
+ case StringKey::WiFi:
+ return STR_WIFI_EN;
+ case StringKey::WiFiNotConnected:
+ return STR_WIFI_NOT_CONNECTED_EN;
+ case StringKey::WiFiConnected:
+ return STR_WIFI_CONNECTED_EN;
+ case StringKey::SSID:
+ return STR_SSID_EN;
+ case StringKey::LoRaInfo:
+ return STR_LORA_INFO_EN;
+ case StringKey::LoRa:
+ return STR_LORA_EN;
+ case StringKey::Role:
+ return STR_ROLE_EN;
+ case StringKey::Freq:
+ return STR_FREQ_EN;
+ case StringKey::ChUtil:
+ return STR_CHUTIL_EN;
+ case StringKey::System:
+ return STR_SYSTEM_EN;
+ case StringKey::Heap:
+ return STR_HEAP_EN;
+ case StringKey::PSRAM:
+ return STR_PSRAM_EN;
+ case StringKey::Flash:
+ return STR_FLASH_EN;
+ case StringKey::SD:
+ return STR_SD_EN;
+ case StringKey::Ver:
+ return STR_VER_EN;
+ case StringKey::Up:
+ return STR_UP_EN;
+ case StringKey::ClientApp:
+ return STR_CLIENT_APP_EN;
+ case StringKey::NoConnected:
+ return STR_NO_CONNECTED_EN;
+ case StringKey::LOCKED:
+ return STR_LOCKED_EN;
+ case StringKey::ConnectToUnlock:
+ return STR_CONNECT_TO_UNLOCK_EN;
+ case StringKey::Battery:
+ return STR_BATTERY_EN;
+ case StringKey::Resuming:
+ return STR_RESUMING_EN;
+ case StringKey::LastHeard:
+ return STR_LAST_HEARD_EN;
+ case StringKey::HopsSig:
+ return STR_HOPS_SIG_EN;
+ case StringKey::HopsSignal:
+ return STR_HOPS_SIGNAL_EN;
+ case StringKey::Nodes:
+ return STR_NODES_EN;
+ case StringKey::Distance:
+ return STR_DISTANCE_EN;
+ case StringKey::Bearings:
+ return STR_BEARINGS_EN;
+ case StringKey::CreatingSSLCert:
+ return STR_CREATING_SSL_EN;
+ case StringKey::PleaseWait:
+ return STR_PLEASE_WAIT_EN;
+ case StringKey::CriticalFault:
+ return STR_CRITICAL_FAULT_EN;
+ case StringKey::ForHelpVisit:
+ return STR_FOR_HELP_VISIT_EN;
+ case StringKey::Updating:
+ return STR_UPDATING_EN;
+ case StringKey::PleaseBePatient:
+ return STR_PLEASE_BE_PATIENT_EN;
+ case StringKey::RebootInProgress:
+ return STR_REBOOT_IN_PROGRESS_EN;
+ case StringKey::PowerMenu:
+ return STR_POWER_MENU_EN;
+ case StringKey::SystemMenu:
+ return STR_SYSTEM_MENU_EN;
+ case StringKey::ScreenOptions:
+ return STR_SCREEN_OPTIONS_EN;
+ case StringKey::WifiToggle:
+ return STR_WIFI_TOGGLE_EN;
+ case StringKey::BluetoothToggle:
+ return STR_BLUETOOTH_TOGGLE_EN;
+ case StringKey::NodeDbReset:
+ return STR_NODE_DB_RESET_EN;
+ case StringKey::ManageNode:
+ return STR_MANAGE_NODE_EN;
+ case StringKey::RemoveFavorite:
+ return STR_REMOVE_FAVORITE_EN;
+ case StringKey::AddFavorite:
+ return STR_ADD_FAVORITE_EN;
+ case StringKey::TestMenu:
+ return STR_TEST_MENU_EN;
+ case StringKey::TraceRoute:
+ return STR_TRACE_ROUTE_EN;
+ case StringKey::ThrottleMessage:
+ return STR_THROTTLE_MSG_EN;
+ case StringKey::MessageViewMode:
+ return STR_MSG_VIEW_MODE_EN;
+ case StringKey::DeleteMessages:
+ return STR_DELETE_MESSAGES_EN;
+ case StringKey::NodeNameLength:
+ return STR_NODE_NAME_LENGTH_EN;
+ case StringKey::GPSMenu:
+ return STR_GPS_MENU_EN;
+ case StringKey::GPSToggle:
+ return STR_GPS_TOGGLE_EN;
+ case StringKey::GPSFormat:
+ return STR_GPS_FORMAT_EN;
+ case StringKey::GPSSmartPosition:
+ return STR_GPS_SMART_POS_EN;
+ case StringKey::GPSUpdateInterval:
+ return STR_GPS_UPDATE_INTERVAL_EN;
+ case StringKey::GPSPositionBroadcast:
+ return STR_GPS_POS_BCAST_EN;
+ case StringKey::CompassNorth:
+ return STR_COMPASS_NORTH_EN;
+ case StringKey::BuzzerMode:
+ return STR_BUZZER_MODE_EN;
+ case StringKey::HamModeConfirm:
+ return STR_HAM_MODE_EN;
+ case StringKey::LicensedToNormal:
+ return STR_LICENSED_NORMAL_EN;
+ case StringKey::KeyVerification:
+ return STR_KEY_VERIFY_EN;
+ case StringKey::RegionUnset:
+ return STR_REGION_UNSET_EN;
+ case StringKey::SelectRegionMessage:
+ return STR_SELECT_REGION_EN;
+ case StringKey::Rebooting:
+ return STR_REBOOTING_EN;
+ default:
+ return "?";
+ }
+}
+
+} // namespace l10n
+} // namespace meshtastic
diff --git a/src/graphics/l10n/Strings_ZH.h b/src/graphics/l10n/Strings_ZH.h
new file mode 100644
index 000000000..85c6cb30f
--- /dev/null
+++ b/src/graphics/l10n/Strings_ZH.h
@@ -0,0 +1,356 @@
+#pragma once
+
+#include "Strings.h"
+
+namespace meshtastic
+{
+namespace l10n
+{
+
+static const char STR_BACK_ZH[] = "\xe8\xbf\x94\xe5\x9b\x9e";
+static const char STR_OK_ZH[] = "\xe7\xa1\xae\xe5\xae\x9a";
+static const char STR_GOT_IT_ZH[] = "\xe7\x9f\xa5\xe9\x81\x93\xe4\xba\x86";
+static const char STR_WELCOME_TO_MESHTASTIC_ZH[] = "\xe6\xac\xa2\xe8\xbf\x8e\xe4\xbd\xbf\xe7\x94\xa8 Meshtastic\xef\xbc\x81";
+static const char STR_SWIPE_TO_NAVIGATE_ZH[] = "\xe6\xbb\x91\xe5\x8a\xa8\xe4\xbb\xa5\xe5\xaf\xbc\xe8\x88\xaa\xe3\x80\x82";
+static const char STR_CLICK_TO_NAVIGATE_ZH[] = "\xe7\x82\xb9\xe5\x87\xbb\xe4\xbb\xa5\xe5\xaf\xbc\xe8\x88\xaa\xe3\x80\x82";
+static const char STR_USE_SELECT_BUTTON_ZH[] = "\xe4\xbd\xbf\xe7\x94\xa8\xe9\x80\x89\xe6\x8b\xa9\xe6\x8c\x89\xe9\x92\xae\xe3\x80\x82";
+static const char STR_DEVICE_ROLE_ZH[] = "\xe8\xae\xbe\xe5\xa4\x87\xe8\xa7\x92\xe8\x89\xb2";
+static const char STR_RADIO_PRESET_ZH[] = "\xe6\x97\xa0\xe7\xba\xbf\xe9\xa2\x84\xe8\xae\xbe";
+static const char STR_FREQUENCY_SLOT_ZH[] = "\xe9\xa2\x91\xe7\x8e\x87\xe6\xa7\xbd\xe4\xbd\x8d";
+static const char STR_LORA_REGION_ZH[] = "LoRa \xe5\x9c\xb0\xe5\x8c\xba";
+static const char STR_CLIENT_ZH[] = "\xe5\xae\xa2\xe6\x88\xb7\xe7\xab\xaf";
+static const char STR_CLIENT_MUTE_ZH[] = "\xe9\x9d\x99\xe9\x9f\xb3\xe5\xae\xa2\xe6\x88\xb7\xe7\xab\xaf";
+static const char STR_LOST_AND_FOUND_ZH[] = "\xe5\xa4\xb1\xe7\x89\xa9\xe6\x8b\x9b\xe9\xa2\x86";
+static const char STR_TRACKER_ZH[] = "\xe8\xbf\xbd\xe8\xb8\xaa\xe5\x99\xa8";
+static const char STR_12HOUR_ZH[] = "12 \xe5\xb0\x8f\xe6\x97\xb6\xe5\x88\xb6";
+static const char STR_24HOUR_ZH[] = "24 \xe5\xb0\x8f\xe6\x97\xb6\xe5\x88\xb6";
+static const char STR_CLOCK_ACTION_ZH[] = "\xe6\x97\xb6\xe9\x92\x9f\xe6\x93\x8d\xe4\xbd\x9c";
+static const char STR_CLOCK_FACE_ZH[] = "\xe6\x97\xb6\xe9\x92\x9f\xe6\xa0\xb7\xe5\xbc\x8f";
+static const char STR_TIME_FORMAT_ZH[] = "\xe6\x97\xb6\xe9\x97\xb4\xe6\xa0\xbc\xe5\xbc\x8f";
+static const char STR_TIMEZONE_ZH[] = "\xe6\x97\xb6\xe5\x8c\xba";
+static const char STR_MESSAGE_ACTION_ZH[] = "\xe6\xb6\x88\xe6\x81\xaf\xe6\x93\x8d\xe4\xbd\x9c";
+static const char STR_REPLY_ZH[] = "\xe5\x9b\x9e\xe5\xa4\x8d";
+static const char STR_VIEW_CHATS_ZH[] = "\xe6\x9f\xa5\xe7\x9c\x8b\xe8\x81\x8a\xe5\xa4\xa9";
+static const char STR_UNMUTE_CHANNEL_ZH[] = "\xe5\x8f\x96\xe6\xb6\x88\xe9\x9d\x99\xe9\x9f\xb3";
+static const char STR_MUTE_CHANNEL_ZH[] = "\xe9\x9d\x99\xe9\x9f\xb3\xe9\xa2\x91\xe9\x81\x93";
+static const char STR_DELETE_ZH[] = "\xe5\x88\xa0\xe9\x99\xa4";
+static const char STR_READ_ALOUD_ZH[] = "\xe5\xa4\xa7\xe5\xa3\xb0\xe6\x9c\x97\xe8\xaf\xbb";
+static const char STR_BRIGHTNESS_ZH[] = "\xe4\xba\xae\xe5\xba\xa6";
+static const char STR_FRAME_VISIBILITY_ZH[] = "\xe9\xa1\xb5\xe9\x9d\xa2\xe5\x8f\xaf\xe8\xa7\x81\xe6\x80\xa7";
+static const char STR_DISPLAY_UNITS_ZH[] = "\xe6\x98\xbe\xe7\xa4\xba\xe5\x8d\x95\xe4\xbd\x8d";
+static const char STR_MESSAGE_BUBBLES_ZH[] = "\xe6\xb6\x88\xe6\x81\xaf\xe6\xb0\x94\xe6\xb3\xa1";
+static const char STR_THEME_ZH[] = "\xe4\xb8\xbb\xe9\xa2\x98";
+static const char STR_REBOOT_ZH[] = "\xe9\x87\x8d\xe5\x90\xaf";
+static const char STR_SHUTDOWN_ZH[] = "\xe5\x85\xb3\xe6\x9c\xba";
+static const char STR_SWITCH_TO_MUI_ZH[] = "\xe5\x88\x87\xe6\x8d\xa2\xe5\x88\xb0 MUI";
+static const char STR_DISPLAY_OPTIONS_ZH[] = "\xe6\x98\xbe\xe7\xa4\xba\xe9\x80\x89\xe9\xa1\xb9";
+static const char STR_LANGUAGE_ZH[] = "\xe8\xaf\xad\xe8\xa8\x80";
+static const char STR_CHINESE_ZH[] = "\xe4\xb8\xad\xe6\x96\x87";
+static const char STR_ENGLISH_ZH[] = "\xe8\x8b\xb1\xe6\x96\x87";
+static const char STR_NO_GPS_ZH[] = "\xe6\x97\xa0 GPS";
+static const char STR_GPS_OFF_ZH[] = "GPS \xe5\xb7\xb2\xe5\x85\xb3\xe9\x97\xad";
+static const char STR_GPS_NOT_PRESENT_ZH[] = "GPS \xe4\xb8\x8d\xe5\xad\x98\xe5\x9c\xa8";
+static const char STR_GPS_IS_DISABLED_ZH[] = "GPS \xe5\xb7\xb2\xe7\xa6\x81\xe7\x94\xa8";
+static const char STR_NODE_ZH[] = "\xe8\x8a\x82\xe7\x82\xb9";
+static const char STR_HOP_ZH[] = "\xe8\xb7\xb3\xe6\x95\xb0:";
+static const char STR_USB_ZH[] = "USB";
+static const char STR_BT_OFF_ZH[] = "\xe8\x93\x9d\xe7\x89\x99\xe5\xb7\xb2\xe5\x85\xb3";
+static const char STR_WIFI_ZH[] = "WiFi";
+static const char STR_WIFI_NOT_CONNECTED_ZH[] = "WiFi: \xe6\x9c\xaa\xe8\xbf\x9e\xe6\x8e\xa5";
+static const char STR_WIFI_CONNECTED_ZH[] = "WiFi: \xe5\xb7\xb2\xe8\xbf\x9e\xe6\x8e\xa5";
+static const char STR_SSID_ZH[] = "SSID:";
+static const char STR_LORA_INFO_ZH[] = "LoRa \xe4\xbf\xa1\xe6\x81\xaf";
+static const char STR_LORA_ZH[] = "LoRa";
+static const char STR_ROLE_ZH[] = "\xe8\xa7\x92\xe8\x89\xb2:";
+static const char STR_FREQ_ZH[] = "\xe9\xa2\x91\xe7\x8e\x87:";
+static const char STR_CHUTIL_ZH[] = "\xe4\xbf\xa1\xe9\x81\x93\xe5\x88\xa9\xe7\x94\xa8";
+static const char STR_SYSTEM_ZH[] = "\xe7\xb3\xbb\xe7\xbb\x9f";
+static const char STR_HEAP_ZH[] = "\xe5\xa0\x86:";
+static const char STR_PSRAM_ZH[] = "PSRAM:";
+static const char STR_FLASH_ZH[] = "Flash:";
+static const char STR_SD_ZH[] = "SD:";
+static const char STR_VER_ZH[] = "\xe7\x89\x88\xe6\x9c\xac:";
+static const char STR_UP_ZH[] = "\xe8\xbf\x90\xe8\xa1\x8c:";
+static const char STR_CLIENT_APP_ZH[] = "\xe5\xae\xa2\xe6\x88\xb7\xe7\xab\xaf";
+static const char STR_NO_CONNECTED_ZH[] = "\xe6\x9c\xaa\xe8\xbf\x9e\xe6\x8e\xa5";
+static const char STR_LOCKED_ZH[] = "\xe5\xb7\xb2\xe9\x94\x81\xe5\xae\x9a";
+static const char STR_CONNECT_TO_UNLOCK_ZH[] = "\xe8\xbf\x9e\xe6\x8e\xa5\xe4\xbb\xa5\xe8\xa7\xa3\xe9\x94\x81";
+static const char STR_BATTERY_ZH[] = "\xe7\x94\xb5\xe6\xb1\xa0";
+static const char STR_RESUMING_ZH[] = "\xe6\xad\xa3\xe5\x9c\xa8\xe6\x81\xa2\xe5\xa4\x8d...";
+static const char STR_LAST_HEARD_ZH[] = "\xe6\x9c\x80\xe5\x90\x8e\xe5\x90\xac\xe5\x88\xb0";
+static const char STR_HOPS_SIG_ZH[] = "\xe8\xb7\xb3\xe6\x95\xb0/\xe4\xbf\xa1\xe5\x8f\xb7";
+static const char STR_HOPS_SIGNAL_ZH[] = "\xe8\xb7\xb3\xe6\x95\xb0/\xe4\xbf\xa1\xe5\x8f\xb7";
+static const char STR_NODES_ZH[] = "\xe8\x8a\x82\xe7\x82\xb9";
+static const char STR_DISTANCE_ZH[] = "\xe8\xb7\x9d\xe7\xa6\xbb";
+static const char STR_BEARINGS_ZH[] = "\xe6\x96\xb9\xe4\xbd\x8d";
+static const char STR_CREATING_SSL_ZH[] = "\xe6\xad\xa3\xe5\x9c\xa8\xe5\x88\x9b\xe5\xbb\xba SSL \xe8\xaf\x81\xe4\xb9\xa6";
+static const char STR_PLEASE_WAIT_ZH[] = "\xe8\xaf\xb7\xe7\xa8\x8d\xe5\x80\x99 . . .";
+static const char STR_CRITICAL_FAULT_ZH[] = "\xe4\xb8\xa5\xe9\x87\x8d\xe6\x95\x85\xe9\x9a\x9c";
+static const char STR_FOR_HELP_VISIT_ZH[] = "\xe8\xaf\xb7\xe8\xae\xbf\xe9\x97\xae\x4ee5\xb8\xae\xe5\x8a\xa9";
+static const char STR_UPDATING_ZH[] = "\xe6\xad\xa3\xe5\x9c\xa8\xe6\x9b\xb4\xe6\x96\xb0";
+static const char STR_PLEASE_BE_PATIENT_ZH[] = "\xe8\xaf\xb7\xe8\x80\x90\xe5\xbf\x83\xe7\xad\x89\xe5\xbe\x85\xef\xbc\x8c\xe4\xb8\x8d\xe8\xa6\x81\xe5\x85\xb3\xe9\x97\xad\xe7\x94\xb5\xe6\xba\x90\xe3\x80\x82";
+static const char STR_REBOOT_IN_PROGRESS_ZH[] = "\xe6\xad\xa3\xe5\x9c\xa8\xe9\x87\x8d\xe5\x90\xaf...";
+static const char STR_POWER_MENU_ZH[] = "\xe7\x94\xb5\xe6\xba\x90";
+static const char STR_SYSTEM_MENU_ZH[] = "\xe7\xb3\xbb\xe7\xbb\x9f";
+static const char STR_SCREEN_OPTIONS_ZH[] = "\xe5\xb1\x8f\xe5\xb9\x95";
+static const char STR_WIFI_TOGGLE_ZH[] = "WiFi";
+static const char STR_BLUETOOTH_TOGGLE_ZH[] = "\xe8\x93\x9d\xe7\x89\x99";
+static const char STR_NODE_DB_RESET_ZH[] = "\xe9\x87\x8d\xe7\xbd\xae\xe8\x8a\x82\xe7\x82\xb9\xe5\xba\x93";
+static const char STR_MANAGE_NODE_ZH[] = "\xe7\xae\xa1\xe7\x90\x86\xe8\x8a\x82\xe7\x82\xb9";
+static const char STR_REMOVE_FAVORITE_ZH[] = "\xe5\x8f\x96\xe6\xb6\x88\xe6\x94\xb6\xe8\x97\x8f";
+static const char STR_ADD_FAVORITE_ZH[] = "\xe6\xb7\xbb\xe5\x8a\xa0\xe6\x94\xb6\xe8\x97\x8f";
+static const char STR_TEST_MENU_ZH[] = "\xe6\xb5\x8b\xe8\xaf\x95";
+static const char STR_TRACE_ROUTE_ZH[] = "\xe8\xb7\xaf\xe7\x94\xb1\xe8\xbf\xbd\xe8\xb8\xaa";
+static const char STR_THROTTLE_MSG_ZH[] = "\xe6\xb6\x88\xe6\x81\xaf\xe5\xb7\xb2\xe9\x99\x90\xe6\xb5\x81";
+static const char STR_MSG_VIEW_MODE_ZH[] = "\xe6\x9f\xa5\xe7\x9c\x8b\xe6\xa8\xa1\xe5\xbc\x8f";
+static const char STR_DELETE_MESSAGES_ZH[] = "\xe5\x88\xa0\xe9\x99\xa4\xe6\xb6\x88\xe6\x81\xaf";
+static const char STR_NODE_NAME_LENGTH_ZH[] = "\xe5\x90\x8d\xe7\xa7\xb0\xe9\x95\xbf\xe5\xba\xa6";
+static const char STR_GPS_MENU_ZH[] = "GPS";
+static const char STR_GPS_TOGGLE_ZH[] = "GPS \xe5\xbc\x80\xe5\x85\xb3";
+static const char STR_GPS_FORMAT_ZH[] = "GPS \xe6\xa0\xbc\xe5\xbc\x8f";
+static const char STR_GPS_SMART_POS_ZH[] = "\xe6\x99\xba\xe8\x83\xbd\xe5\xae\x9a\xe4\xbd\x8d";
+static const char STR_GPS_UPDATE_INTERVAL_ZH[] = "\xe6\x9b\xb4\xe6\x96\xb0\xe9\x97\xb4\xe9\x9a\x94";
+static const char STR_GPS_POS_BCAST_ZH[] = "\xe4\xbd\x8d\xe7\xbd\xae\xe5\xb9\xbf\xe6\x92\xad";
+static const char STR_COMPASS_NORTH_ZH[] = "\xe6\x8c\x87\xe5\x8d\x97\xe9\x92\x88";
+static const char STR_BUZZER_MODE_ZH[] = "\xe8\x9c\x82\xe9\xb8\xa3\xe5\x99\xa8";
+static const char STR_HAM_MODE_ZH[] = "Ham \xe6\xa8\xa1\xe5\xbc\x8f";
+static const char STR_LICENSED_NORMAL_ZH[] = "\xe6\x99\xae\xe9\x80\x9a\xe6\xa8\xa1\xe5\xbc\x8f";
+static const char STR_KEY_VERIFY_ZH[] = "\xe9\xaa\x8c\xe8\xaf\x81\xe5\xaf\x86\xe9\x92\xa5";
+static const char STR_REGION_UNSET_ZH[] = "\xe8\xaf\xb7\xe9\x80\x89\xe6\x8b\xa9\xe5\x9c\xb0\xe5\x8c\xba";
+static const char STR_SELECT_REGION_ZH[] = "\xe8\xaf\xb7\xe9\x80\x89\xe6\x8b\xa9\xe6\x82\xa8\xe7\x9a\x84 LoRa \xe5\x8c\xba\xe5\x9f\x9f:";
+static const char STR_REBOOTING_ZH[] = "\xe6\xad\xa3\xe5\x9c\xa8\xe9\x87\x8d\xe5\x90\xaf...";
+
+inline const char *getStringZH(StringKey key)
+{
+ switch (key) {
+ case StringKey::Back:
+ return STR_BACK_ZH;
+ case StringKey::OK:
+ return STR_OK_ZH;
+ case StringKey::GotIt:
+ return STR_GOT_IT_ZH;
+ case StringKey::WelcomeToMeshtastic:
+ return STR_WELCOME_TO_MESHTASTIC_ZH;
+ case StringKey::SwipeToNavigate:
+ return STR_SWIPE_TO_NAVIGATE_ZH;
+ case StringKey::ClickToNavigate:
+ return STR_CLICK_TO_NAVIGATE_ZH;
+ case StringKey::UseSelectButton:
+ return STR_USE_SELECT_BUTTON_ZH;
+ case StringKey::DeviceRole:
+ return STR_DEVICE_ROLE_ZH;
+ case StringKey::RadioPreset:
+ return STR_RADIO_PRESET_ZH;
+ case StringKey::FrequencySlot:
+ return STR_FREQUENCY_SLOT_ZH;
+ case StringKey::LoRaRegion:
+ return STR_LORA_REGION_ZH;
+ case StringKey::Client:
+ return STR_CLIENT_ZH;
+ case StringKey::ClientMute:
+ return STR_CLIENT_MUTE_ZH;
+ case StringKey::LostAndFound:
+ return STR_LOST_AND_FOUND_ZH;
+ case StringKey::Tracker:
+ return STR_TRACKER_ZH;
+ case StringKey::Hour12:
+ return STR_12HOUR_ZH;
+ case StringKey::Hour24:
+ return STR_24HOUR_ZH;
+ case StringKey::ClockAction:
+ return STR_CLOCK_ACTION_ZH;
+ case StringKey::ClockFace:
+ return STR_CLOCK_FACE_ZH;
+ case StringKey::TimeFormat:
+ return STR_TIME_FORMAT_ZH;
+ case StringKey::Timezone:
+ return STR_TIMEZONE_ZH;
+ case StringKey::MessageAction:
+ return STR_MESSAGE_ACTION_ZH;
+ case StringKey::Reply:
+ return STR_REPLY_ZH;
+ case StringKey::ViewChats:
+ return STR_VIEW_CHATS_ZH;
+ case StringKey::UnmuteChannel:
+ return STR_UNMUTE_CHANNEL_ZH;
+ case StringKey::MuteChannel:
+ return STR_MUTE_CHANNEL_ZH;
+ case StringKey::Delete:
+ return STR_DELETE_ZH;
+ case StringKey::ReadAloud:
+ return STR_READ_ALOUD_ZH;
+ case StringKey::Brightness:
+ return STR_BRIGHTNESS_ZH;
+ case StringKey::FrameVisibility:
+ return STR_FRAME_VISIBILITY_ZH;
+ case StringKey::DisplayUnits:
+ return STR_DISPLAY_UNITS_ZH;
+ case StringKey::MessageBubbles:
+ return STR_MESSAGE_BUBBLES_ZH;
+ case StringKey::Theme:
+ return STR_THEME_ZH;
+ case StringKey::Reboot:
+ return STR_REBOOT_ZH;
+ case StringKey::Shutdown:
+ return STR_SHUTDOWN_ZH;
+ case StringKey::SwitchToMUI:
+ return STR_SWITCH_TO_MUI_ZH;
+ case StringKey::DisplayOptions:
+ return STR_DISPLAY_OPTIONS_ZH;
+ case StringKey::Language:
+ return STR_LANGUAGE_ZH;
+ case StringKey::Chinese:
+ return STR_CHINESE_ZH;
+ case StringKey::English:
+ return STR_ENGLISH_ZH;
+ case StringKey::NoGPS:
+ return STR_NO_GPS_ZH;
+ case StringKey::GPSoff:
+ return STR_GPS_OFF_ZH;
+ case StringKey::GPSnotPresent:
+ return STR_GPS_NOT_PRESENT_ZH;
+ case StringKey::GPSisDisabled:
+ return STR_GPS_IS_DISABLED_ZH;
+ case StringKey::Node:
+ return STR_NODE_ZH;
+ case StringKey::Hop:
+ return STR_HOP_ZH;
+ case StringKey::USB:
+ return STR_USB_ZH;
+ case StringKey::BT_off:
+ return STR_BT_OFF_ZH;
+ case StringKey::WiFi:
+ return STR_WIFI_ZH;
+ case StringKey::WiFiNotConnected:
+ return STR_WIFI_NOT_CONNECTED_ZH;
+ case StringKey::WiFiConnected:
+ return STR_WIFI_CONNECTED_ZH;
+ case StringKey::SSID:
+ return STR_SSID_ZH;
+ case StringKey::LoRaInfo:
+ return STR_LORA_INFO_ZH;
+ case StringKey::LoRa:
+ return STR_LORA_ZH;
+ case StringKey::Role:
+ return STR_ROLE_ZH;
+ case StringKey::Freq:
+ return STR_FREQ_ZH;
+ case StringKey::ChUtil:
+ return STR_CHUTIL_ZH;
+ case StringKey::System:
+ return STR_SYSTEM_ZH;
+ case StringKey::Heap:
+ return STR_HEAP_ZH;
+ case StringKey::PSRAM:
+ return STR_PSRAM_ZH;
+ case StringKey::Flash:
+ return STR_FLASH_ZH;
+ case StringKey::SD:
+ return STR_SD_ZH;
+ case StringKey::Ver:
+ return STR_VER_ZH;
+ case StringKey::Up:
+ return STR_UP_ZH;
+ case StringKey::ClientApp:
+ return STR_CLIENT_APP_ZH;
+ case StringKey::NoConnected:
+ return STR_NO_CONNECTED_ZH;
+ case StringKey::LOCKED:
+ return STR_LOCKED_ZH;
+ case StringKey::ConnectToUnlock:
+ return STR_CONNECT_TO_UNLOCK_ZH;
+ case StringKey::Battery:
+ return STR_BATTERY_ZH;
+ case StringKey::Resuming:
+ return STR_RESUMING_ZH;
+ case StringKey::LastHeard:
+ return STR_LAST_HEARD_ZH;
+ case StringKey::HopsSig:
+ return STR_HOPS_SIG_ZH;
+ case StringKey::HopsSignal:
+ return STR_HOPS_SIGNAL_ZH;
+ case StringKey::Nodes:
+ return STR_NODES_ZH;
+ case StringKey::Distance:
+ return STR_DISTANCE_ZH;
+ case StringKey::Bearings:
+ return STR_BEARINGS_ZH;
+ case StringKey::CreatingSSLCert:
+ return STR_CREATING_SSL_ZH;
+ case StringKey::PleaseWait:
+ return STR_PLEASE_WAIT_ZH;
+ case StringKey::CriticalFault:
+ return STR_CRITICAL_FAULT_ZH;
+ case StringKey::ForHelpVisit:
+ return STR_FOR_HELP_VISIT_ZH;
+ case StringKey::Updating:
+ return STR_UPDATING_ZH;
+ case StringKey::PleaseBePatient:
+ return STR_PLEASE_BE_PATIENT_ZH;
+ case StringKey::RebootInProgress:
+ return STR_REBOOT_IN_PROGRESS_ZH;
+ case StringKey::PowerMenu:
+ return STR_POWER_MENU_ZH;
+ case StringKey::SystemMenu:
+ return STR_SYSTEM_MENU_ZH;
+ case StringKey::ScreenOptions:
+ return STR_SCREEN_OPTIONS_ZH;
+ case StringKey::WifiToggle:
+ return STR_WIFI_TOGGLE_ZH;
+ case StringKey::BluetoothToggle:
+ return STR_BLUETOOTH_TOGGLE_ZH;
+ case StringKey::NodeDbReset:
+ return STR_NODE_DB_RESET_ZH;
+ case StringKey::ManageNode:
+ return STR_MANAGE_NODE_ZH;
+ case StringKey::RemoveFavorite:
+ return STR_REMOVE_FAVORITE_ZH;
+ case StringKey::AddFavorite:
+ return STR_ADD_FAVORITE_ZH;
+ case StringKey::TestMenu:
+ return STR_TEST_MENU_ZH;
+ case StringKey::TraceRoute:
+ return STR_TRACE_ROUTE_ZH;
+ case StringKey::ThrottleMessage:
+ return STR_THROTTLE_MSG_ZH;
+ case StringKey::MessageViewMode:
+ return STR_MSG_VIEW_MODE_ZH;
+ case StringKey::DeleteMessages:
+ return STR_DELETE_MESSAGES_ZH;
+ case StringKey::NodeNameLength:
+ return STR_NODE_NAME_LENGTH_ZH;
+ case StringKey::GPSMenu:
+ return STR_GPS_MENU_ZH;
+ case StringKey::GPSToggle:
+ return STR_GPS_TOGGLE_ZH;
+ case StringKey::GPSFormat:
+ return STR_GPS_FORMAT_ZH;
+ case StringKey::GPSSmartPosition:
+ return STR_GPS_SMART_POS_ZH;
+ case StringKey::GPSUpdateInterval:
+ return STR_GPS_UPDATE_INTERVAL_ZH;
+ case StringKey::GPSPositionBroadcast:
+ return STR_GPS_POS_BCAST_ZH;
+ case StringKey::CompassNorth:
+ return STR_COMPASS_NORTH_ZH;
+ case StringKey::BuzzerMode:
+ return STR_BUZZER_MODE_ZH;
+ case StringKey::HamModeConfirm:
+ return STR_HAM_MODE_ZH;
+ case StringKey::LicensedToNormal:
+ return STR_LICENSED_NORMAL_ZH;
+ case StringKey::KeyVerification:
+ return STR_KEY_VERIFY_ZH;
+ case StringKey::RegionUnset:
+ return STR_REGION_UNSET_ZH;
+ case StringKey::SelectRegionMessage:
+ return STR_SELECT_REGION_ZH;
+ case StringKey::Rebooting:
+ return STR_REBOOTING_ZH;
+ default:
+ return "?";
+ }
+}
+
+} // namespace l10n
+} // namespace meshtastic