Rewrite display language handling to work with locale strings instead of magic numbers.

This is required to implement further i18n functions using the language packs
This commit is contained in:
Thomas Basler 2024-10-25 21:29:15 +02:00
parent 6113e0737b
commit d259042542
9 changed files with 73 additions and 39 deletions

View File

@ -5,7 +5,7 @@
#include <cstdint> #include <cstdint>
#define CONFIG_FILENAME "/config.json" #define CONFIG_FILENAME "/config.json"
#define CONFIG_VERSION 0x00011c00 // 0.1.28 // make sure to clean all after change #define CONFIG_VERSION 0x00011d00 // 0.1.29 // make sure to clean all after change
#define WIFI_MAX_SSID_STRLEN 32 #define WIFI_MAX_SSID_STRLEN 32
#define WIFI_MAX_PASSWORD_STRLEN 64 #define WIFI_MAX_PASSWORD_STRLEN 64
@ -30,6 +30,7 @@
#define CHAN_MAX_NAME_STRLEN 31 #define CHAN_MAX_NAME_STRLEN 31
#define DEV_MAX_MAPPING_NAME_STRLEN 63 #define DEV_MAX_MAPPING_NAME_STRLEN 63
#define LOCALE_STRLEN 2
struct CHANNEL_CONFIG_T { struct CHANNEL_CONFIG_T {
uint16_t MaxChannelPower; uint16_t MaxChannelPower;
@ -144,7 +145,7 @@ struct CONFIG_T {
bool ScreenSaver; bool ScreenSaver;
uint8_t Rotation; uint8_t Rotation;
uint8_t Contrast; uint8_t Contrast;
uint8_t Language; char Locale[LOCALE_STRLEN + 1];
struct { struct {
uint32_t Duration; uint32_t Duration;
uint8_t Mode; uint8_t Mode;

View File

@ -40,7 +40,7 @@ public:
void setContrast(const uint8_t contrast); void setContrast(const uint8_t contrast);
void setStatus(const bool turnOn); void setStatus(const bool turnOn);
void setOrientation(const uint8_t rotation = DISPLAY_ROTATION); void setOrientation(const uint8_t rotation = DISPLAY_ROTATION);
void setLanguage(const uint8_t language); void setLocale(const String& locale);
void setDiagramMode(DiagramMode_t mode); void setDiagramMode(DiagramMode_t mode);
void setStartupDisplay(); void setStartupDisplay();
@ -65,7 +65,7 @@ private:
DisplayType_t _display_type = DisplayType_t::None; DisplayType_t _display_type = DisplayType_t::None;
DiagramMode_t _diagram_mode = DiagramMode_t::Off; DiagramMode_t _diagram_mode = DiagramMode_t::Off;
uint8_t _display_language = DISPLAY_LANGUAGE; String _display_language = DISPLAY_LOCALE;
uint8_t _mExtra; uint8_t _mExtra;
const uint16_t _period = 1000; const uint16_t _period = 1000;
const uint16_t _interval = 60000; // interval at which to power save (milliseconds) const uint16_t _interval = 60000; // interval at which to power save (milliseconds)
@ -73,6 +73,15 @@ private:
char _fmtText[32]; char _fmtText[32];
bool _isLarge = false; bool _isLarge = false;
uint8_t _lineOffsets[5]; uint8_t _lineOffsets[5];
String _i18n_offline;
String _i18n_yield_today_kwh;
String _i18n_yield_today_wh;
String _i18n_date_format;
String _i18n_current_power_kw;
String _i18n_current_power_w;
String _i18n_yield_total_mwh;
String _i18n_yield_total_kwh;
}; };
extern DisplayGraphicClass Display; extern DisplayGraphicClass Display;

View File

@ -99,7 +99,7 @@
#define DISPLAY_SCREENSAVER true #define DISPLAY_SCREENSAVER true
#define DISPLAY_ROTATION 2U #define DISPLAY_ROTATION 2U
#define DISPLAY_CONTRAST 60U #define DISPLAY_CONTRAST 60U
#define DISPLAY_LANGUAGE 0U #define DISPLAY_LOCALE "en"
#define DISPLAY_DIAGRAM_DURATION (10UL * 60UL * 60UL) #define DISPLAY_DIAGRAM_DURATION (10UL * 60UL * 60UL)
#define DISPLAY_DIAGRAM_MODE 1U #define DISPLAY_DIAGRAM_MODE 1U

View File

@ -107,7 +107,7 @@ bool ConfigurationClass::write()
display["screensaver"] = config.Display.ScreenSaver; display["screensaver"] = config.Display.ScreenSaver;
display["rotation"] = config.Display.Rotation; display["rotation"] = config.Display.Rotation;
display["contrast"] = config.Display.Contrast; display["contrast"] = config.Display.Contrast;
display["language"] = config.Display.Language; display["locale"] = config.Display.Locale;
display["diagram_duration"] = config.Display.Diagram.Duration; display["diagram_duration"] = config.Display.Diagram.Duration;
display["diagram_mode"] = config.Display.Diagram.Mode; display["diagram_mode"] = config.Display.Diagram.Mode;
@ -282,7 +282,7 @@ bool ConfigurationClass::read()
config.Display.ScreenSaver = display["screensaver"] | DISPLAY_SCREENSAVER; config.Display.ScreenSaver = display["screensaver"] | DISPLAY_SCREENSAVER;
config.Display.Rotation = display["rotation"] | DISPLAY_ROTATION; config.Display.Rotation = display["rotation"] | DISPLAY_ROTATION;
config.Display.Contrast = display["contrast"] | DISPLAY_CONTRAST; config.Display.Contrast = display["contrast"] | DISPLAY_CONTRAST;
config.Display.Language = display["language"] | DISPLAY_LANGUAGE; strlcpy(config.Display.Locale, display["locale"] | DISPLAY_LOCALE, sizeof(config.Display.Locale));
config.Display.Diagram.Duration = display["diagram_duration"] | DISPLAY_DIAGRAM_DURATION; config.Display.Diagram.Duration = display["diagram_duration"] | DISPLAY_DIAGRAM_DURATION;
config.Display.Diagram.Mode = display["diagram_mode"] | DISPLAY_DIAGRAM_MODE; config.Display.Diagram.Mode = display["diagram_mode"] | DISPLAY_DIAGRAM_MODE;
@ -383,6 +383,22 @@ void ConfigurationClass::migrate()
} }
} }
if (config.Cfg.Version < 0x00011d00) {
JsonObject device = doc["device"];
JsonObject display = device["display"];
switch (display["language"] | 0U) {
case 0U:
strlcpy(config.Display.Locale, "en", sizeof(config.Display.Locale));
break;
case 1U:
strlcpy(config.Display.Locale, "de", sizeof(config.Display.Locale));
break;
case 2U:
strlcpy(config.Display.Locale, "fr", sizeof(config.Display.Locale));
break;
}
}
f.close(); f.close();
config.Cfg.Version = CONFIG_VERSION; config.Cfg.Version = CONFIG_VERSION;

View File

@ -16,18 +16,11 @@ std::map<DisplayType_t, std::function<U8G2*(uint8_t, uint8_t, uint8_t, uint8_t)>
{ DisplayType_t::ST7567_GM12864I_59N, [](uint8_t reset, uint8_t clock, uint8_t data, uint8_t cs) { return new U8G2_ST7567_ENH_DG128064I_F_HW_I2C(U8G2_R0, reset, clock, data); } }, { DisplayType_t::ST7567_GM12864I_59N, [](uint8_t reset, uint8_t clock, uint8_t data, uint8_t cs) { return new U8G2_ST7567_ENH_DG128064I_F_HW_I2C(U8G2_R0, reset, clock, data); } },
}; };
// Language defintion, respect order in languages[] and translation lists // Language defintion, respect order in translation lists
#define I18N_LOCALE_EN 0 #define I18N_LOCALE_EN 0
#define I18N_LOCALE_DE 1 #define I18N_LOCALE_DE 1
#define I18N_LOCALE_FR 2 #define I18N_LOCALE_FR 2
// Languages supported. Note: the order is important and must match locale_translations.h
const uint8_t languages[] = {
I18N_LOCALE_EN,
I18N_LOCALE_DE,
I18N_LOCALE_FR
};
static const char* const i18n_offline[] = { "Offline", "Offline", "Offline" }; static const char* const i18n_offline[] = { "Offline", "Offline", "Offline" };
static const char* const i18n_current_power_w[] = { "%.0f W", "%.0f W", "%.0f W" }; static const char* const i18n_current_power_w[] = { "%.0f W", "%.0f W", "%.0f W" };
@ -166,9 +159,24 @@ void DisplayGraphicClass::setOrientation(const uint8_t rotation)
calcLineHeights(); calcLineHeights();
} }
void DisplayGraphicClass::setLanguage(const uint8_t language) void DisplayGraphicClass::setLocale(const String& locale)
{ {
_display_language = language < sizeof(languages) / sizeof(languages[0]) ? language : DISPLAY_LANGUAGE; _display_language = locale;
uint8_t idx = I18N_LOCALE_EN;
if (locale == "de") {
idx = I18N_LOCALE_DE;
} else if (locale == "fr") {
idx = I18N_LOCALE_FR;
}
_i18n_offline = i18n_offline[idx];
_i18n_yield_today_kwh = i18n_yield_today_kwh[idx];
_i18n_yield_today_wh = i18n_yield_today_wh[idx];
_i18n_date_format = i18n_date_format[idx];
_i18n_current_power_kw = i18n_current_power_kw[idx];
_i18n_current_power_w = i18n_current_power_w[idx];
_i18n_yield_total_mwh = i18n_yield_total_mwh[idx];
_i18n_yield_total_kwh = i18n_yield_total_kwh[idx];
} }
void DisplayGraphicClass::setDiagramMode(DiagramMode_t mode) void DisplayGraphicClass::setDiagramMode(DiagramMode_t mode)
@ -225,9 +233,9 @@ void DisplayGraphicClass::loop()
if (showText) { if (showText) {
const float watts = Datastore.getTotalAcPowerEnabled(); const float watts = Datastore.getTotalAcPowerEnabled();
if (watts > 999) { if (watts > 999) {
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_kw[_display_language], watts / 1000); snprintf(_fmtText, sizeof(_fmtText), _i18n_current_power_kw.c_str(), watts / 1000);
} else { } else {
snprintf(_fmtText, sizeof(_fmtText), i18n_current_power_w[_display_language], watts); snprintf(_fmtText, sizeof(_fmtText), _i18n_current_power_w.c_str(), watts);
} }
printText(_fmtText, 0); printText(_fmtText, 0);
} }
@ -237,7 +245,7 @@ void DisplayGraphicClass::loop()
//=====> Offline =========== //=====> Offline ===========
else { else {
printText(i18n_offline[_display_language], 0); printText(_i18n_offline.c_str(), 0);
// check if it's time to enter power saving mode // check if it's time to enter power saving mode
if (millis() - _previousMillis >= (_interval * 2)) { if (millis() - _previousMillis >= (_interval * 2)) {
displayPowerSave = enablePowerSafe; displayPowerSave = enablePowerSafe;
@ -249,16 +257,16 @@ void DisplayGraphicClass::loop()
// Daily production // Daily production
float wattsToday = Datastore.getTotalAcYieldDayEnabled(); float wattsToday = Datastore.getTotalAcYieldDayEnabled();
if (wattsToday >= 10000) { if (wattsToday >= 10000) {
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_kwh[_display_language], wattsToday / 1000); snprintf(_fmtText, sizeof(_fmtText), _i18n_yield_today_kwh.c_str(), wattsToday / 1000);
} else { } else {
snprintf(_fmtText, sizeof(_fmtText), i18n_yield_today_wh[_display_language], wattsToday); snprintf(_fmtText, sizeof(_fmtText), _i18n_yield_today_wh.c_str(), wattsToday);
} }
printText(_fmtText, 1); printText(_fmtText, 1);
// Total production // Total production
const float wattsTotal = Datastore.getTotalAcYieldTotalEnabled(); const float wattsTotal = Datastore.getTotalAcYieldTotalEnabled();
auto const format = (wattsTotal >= 1000) ? i18n_yield_total_mwh : i18n_yield_total_kwh; auto const format = (wattsTotal >= 1000) ? _i18n_yield_total_mwh : _i18n_yield_total_kwh;
snprintf(_fmtText, sizeof(_fmtText), format[_display_language], wattsTotal); snprintf(_fmtText, sizeof(_fmtText), format.c_str(), wattsTotal);
printText(_fmtText, 2); printText(_fmtText, 2);
//=====> IP or Date-Time ======== //=====> IP or Date-Time ========
@ -268,7 +276,7 @@ void DisplayGraphicClass::loop()
} else { } else {
// Get current time // Get current time
time_t now = time(nullptr); time_t now = time(nullptr);
strftime(_fmtText, sizeof(_fmtText), i18n_date_format[_display_language], localtime(&now)); strftime(_fmtText, sizeof(_fmtText), _i18n_date_format.c_str(), localtime(&now));
printText(_fmtText, 3); printText(_fmtText, 3);
} }
} }

View File

@ -86,7 +86,7 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request)
display["power_safe"] = config.Display.PowerSafe; display["power_safe"] = config.Display.PowerSafe;
display["screensaver"] = config.Display.ScreenSaver; display["screensaver"] = config.Display.ScreenSaver;
display["contrast"] = config.Display.Contrast; display["contrast"] = config.Display.Contrast;
display["language"] = config.Display.Language; display["locale"] = config.Display.Locale;
display["diagramduration"] = config.Display.Diagram.Duration; display["diagramduration"] = config.Display.Diagram.Duration;
display["diagrammode"] = config.Display.Diagram.Mode; display["diagrammode"] = config.Display.Diagram.Mode;
@ -137,7 +137,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
config.Display.PowerSafe = root["display"]["power_safe"].as<bool>(); config.Display.PowerSafe = root["display"]["power_safe"].as<bool>();
config.Display.ScreenSaver = root["display"]["screensaver"].as<bool>(); config.Display.ScreenSaver = root["display"]["screensaver"].as<bool>();
config.Display.Contrast = root["display"]["contrast"].as<uint8_t>(); config.Display.Contrast = root["display"]["contrast"].as<uint8_t>();
config.Display.Language = root["display"]["language"].as<uint8_t>(); strlcpy(config.Display.Locale, root["display"]["locale"].as<String>().c_str(), sizeof(config.Display.Locale));
config.Display.Diagram.Duration = root["display"]["diagramduration"].as<uint32_t>(); config.Display.Diagram.Duration = root["display"]["diagramduration"].as<uint32_t>();
config.Display.Diagram.Mode = root["display"]["diagrammode"].as<DiagramMode_t>(); config.Display.Diagram.Mode = root["display"]["diagrammode"].as<DiagramMode_t>();
@ -151,7 +151,7 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request)
Display.enablePowerSafe = config.Display.PowerSafe; Display.enablePowerSafe = config.Display.PowerSafe;
Display.enableScreensaver = config.Display.ScreenSaver; Display.enableScreensaver = config.Display.ScreenSaver;
Display.setContrast(config.Display.Contrast); Display.setContrast(config.Display.Contrast);
Display.setLanguage(config.Display.Language); Display.setLocale(config.Display.Locale);
Display.Diagram().updatePeriod(); Display.Diagram().updatePeriod();
WebApi.writeConfig(retMsg); WebApi.writeConfig(retMsg);

View File

@ -143,7 +143,7 @@ void setup()
Display.enablePowerSafe = config.Display.PowerSafe; Display.enablePowerSafe = config.Display.PowerSafe;
Display.enableScreensaver = config.Display.ScreenSaver; Display.enableScreensaver = config.Display.ScreenSaver;
Display.setContrast(config.Display.Contrast); Display.setContrast(config.Display.Contrast);
Display.setLanguage(config.Display.Language); Display.setLocale(config.Display.Locale);
Display.setStartupDisplay(); Display.setStartupDisplay();
MessageOutput.println("done"); MessageOutput.println("done");

View File

@ -5,7 +5,7 @@ export interface Display {
power_safe: boolean; power_safe: boolean;
screensaver: boolean; screensaver: boolean;
contrast: number; contrast: number;
language: number; locale: string;
diagramduration: number; diagramduration: number;
diagrammode: number; diagrammode: number;
} }

View File

@ -160,13 +160,13 @@
{{ $t('deviceadmin.DisplayLanguage') }} {{ $t('deviceadmin.DisplayLanguage') }}
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<select class="form-select" v-model="deviceConfigList.display.language"> <select class="form-select" v-model="deviceConfigList.display.locale">
<option <option
v-for="language in displayLanguageList" v-for="locale in displayLocaleList"
:key="language.key" :key="locale.key"
:value="language.key" :value="locale.key"
> >
{{ $t(`deviceadmin.` + language.value) }} {{ $t(`deviceadmin.` + locale.value) }}
</option> </option>
</select> </select>
</div> </div>
@ -289,10 +289,10 @@ export default defineComponent({
{ key: 2, value: 'rot180' }, { key: 2, value: 'rot180' },
{ key: 3, value: 'rot270' }, { key: 3, value: 'rot270' },
], ],
displayLanguageList: [ displayLocaleList: [
{ key: 0, value: 'en' }, { key: 'en', value: 'en' },
{ key: 1, value: 'de' }, { key: 'de', value: 'de' },
{ key: 2, value: 'fr' }, { key: 'fr', value: 'fr' },
], ],
diagramModeList: [ diagramModeList: [
{ key: 0, value: 'off' }, { key: 0, value: 'off' },