Skip to content

Commit

Permalink
Merge pull request #240 from flattermann/i18n
Browse files Browse the repository at this point in the history
Added Translation support (I18n)
  • Loading branch information
brett-dot-tech authored Feb 25, 2025
2 parents c5b42b3 + 2a5198f commit 28963a8
Show file tree
Hide file tree
Showing 15 changed files with 456 additions and 82 deletions.
19 changes: 19 additions & 0 deletions firmware/src/core/i18n/I18n.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "I18n.h"

String I18n::s_allLanguages[] = ALL_LANGUAGES;
int I18n::s_languageId = DEFAULT_LANGUAGE;

void I18n::setLanguageId(const int langId) {
s_languageId = langId;
}

String I18n::getLanguageString(const int langId) {
if (langId >= 0 && langId < LANG_NUM) {
return s_allLanguages[langId];
}
return "invalid";
}

String *I18n::getAllLanguages() {
return s_allLanguages;
}
63 changes: 63 additions & 0 deletions firmware/src/core/i18n/I18n.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#ifndef I18N_H
#define I18N_H

#include "config_helper.h"
#include <Arduino.h>

#define ALL_LANGUAGES {"en", "de", "fr"}

enum Language {
LANG_EN = 0,
LANG_DE,
LANG_FR,
LANG_NUM // keep this as last item
};

#define DEFAULT_LANGUAGE LANG_EN

class I18n {
public:
static void setLanguageId(int langId);
static String getLanguageString(int langId);
static String *getAllLanguages();

template <size_t N>
static const char *get(const char *const (&translations)[N]) {
const char *text = translations[s_languageId];
if (text == nullptr) {
text = translations[DEFAULT_LANGUAGE];
}
return text ? text : "@missingTranslation@";
}

template <size_t X, size_t Y>
static const char *get(const char *const (&translations)[X][Y], size_t index) {
if (index >= X) {
return "@invalidIndex@";
}

const char *text = translations[index][s_languageId];
if (text == nullptr) {
text = translations[index][DEFAULT_LANGUAGE];
}
return text ? text : "@missingTranslation@";
}

private:
static String s_allLanguages[];
static int s_languageId;
};

// I18n helper
template <size_t N>
static const char *i18n(const char *const (&translations)[N]) {
return I18n::get(translations);
}

// I18n helper (with index)
template <size_t X, size_t Y>
static const char *i18n(const char *const (&translations)[X][Y], size_t index) {
return I18n::get(translations, index);
}

#endif // I18N_H
133 changes: 133 additions & 0 deletions firmware/src/core/i18n/Translations.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#include "Translations.h"

// Languages are defined in I18n.h:
// EN, DE, FR

// THIS FILE IS ONLY FOR GENERAL TRANSLATIONS
// Widget translations should be placed in a separate .h/.cpp in the Widget directory

// All translation variables should start with "t_"

constexpr const char *const t_welcome[LANG_NUM] = {
"Welcome", // EN
"Willkommen", // DE
"Bienvenue" // FR
};

constexpr const char *const t_infoOrbs[LANG_NUM] = {
"InfoOrbs", // EN
"InfoOrbs", // DE
"InfoOrbs" // FR
};

constexpr const char *const t_by[LANG_NUM] = {
"by", // EN
"von", // DE
"par" // FR
};

constexpr const char *const t_brettTech[LANG_NUM] = {
"brett.tech", // EN
"brett.tech", // DE
"brett.tech" // FR
};

constexpr const char *const t_version[LANG_NUM] = {
"Version", // EN
"Version", // DE
"Version" // FR
};

constexpr const char *const t_loadingData[LANG_NUM] = {
"Loading data:", // EN
"Lade Daten:", // DE
"Chargement:" // FR
};

constexpr const char *const t_enableWidget[LANG_NUM] = {
"Enable Widget", // EN
"Widget aktivieren", // DE
"Activer le widget" // FR
};

constexpr const char *const t_timezoneLoc[LANG_NUM] = {
"Timezone, use one from <a href='https://timezonedb.com/time-zones' target='blank'>this list</a>", // EN
"Zeitzone, verwenden Sie eine aus <a href='https://timezonedb.com/time-zones' target='blank'>dieser Liste</a>", // DE
"Fuseau horaire, utilisez-en un de <a href='https://timezonedb.com/time-zones' target='blank'>cette liste</a>" // FR
};

constexpr const char *const t_language[LANG_NUM] = {
"Language", // EN
"Sprache", // DE
"Langue" // FR
};

constexpr const char *const t_widgetCycleDelay[LANG_NUM] = {
"Automatically cycle widgets every X seconds, set to 0 to disable", // EN
"Wechseln Sie die Widgets automatisch alle X Sekunden, auf 0 setzen, um zu deaktivieren", // DE
"Faites défiler les widgets automatiquement toutes les X secondes, définissez sur 0 pour désactiver" // FR
};

constexpr const char *const t_ntpServer[LANG_NUM] = {
"NTP server", // EN
"NTP-Server", // DE
"Serveur NTP" // FR
};

constexpr const char *const t_orbRotation[LANG_NUM] = {
"Orb rotation", // EN
"Orb-Drehung", // DE
"Rotation des Orbes" // FR
};

constexpr const char *const t_orbRot[4][LANG_NUM] = {
{
"No rotation", // EN
"Keine Drehung", // DE
"Pas de rotation" // FR
},
{
"Rotate 90°", // EN
"90° gedreht", // DE
"Tourné de 90°" // FR
},
{
"Rotate 180°", // EN
"180° gedreht", // DE
"Tourné de 180°" // FR
},
{
"Rotate 270°", // EN
"270° gedreht", // DE
"Tourné de 270°" // FR
}};

constexpr const char *const t_nightmode[LANG_NUM] = {
"Enable Nighttime mode", // EN
"Nachtmodus aktivieren", // DE
"Activer le mode nuit" // FR
};

constexpr const char *const t_tftBrightness[LANG_NUM] = {
"TFT Brightness [0-255]", // EN
"TFT-Helligkeit [0-255]", // DE
"Luminosité TFT [0-255]" // FR
};

constexpr const char *const t_dimStartHour[LANG_NUM] = {
"Nighttime Start [24h format]", // EN
"Nachtmodus Start [24h-Format]", // DE
"Début de la nuit [format 24h]" // FR
};

constexpr const char *const t_dimEndHour[LANG_NUM] = {
"Nighttime End [24h format]", // EN
"Nachtmodus Ende [24h-Format]", // DE
"Fin de la nuit [format 24h]" // FR
};

constexpr const char *const t_dimBrightness[LANG_NUM] = {
"Nighttime Brightness [0-255]", // EN
"Nachtmodus Helligkeit [0-255]", // DE
"Luminosité nocturne [0-255]" // FR
};
30 changes: 30 additions & 0 deletions firmware/src/core/i18n/Translations.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef TRANSLATIONS_H
#define TRANSLATIONS_H

#include "I18n.h"

// THIS FILE IS ONLY FOR GENERAL TRANSLATIONS
// Widget translations should be placed in a separate .h/.cpp in the Widget directory

// All translation variables should start with "t_"

extern const char *const t_welcome[LANG_NUM];
extern const char *const t_infoOrbs[LANG_NUM];
extern const char *const t_by[LANG_NUM];
extern const char *const t_brettTech[LANG_NUM];
extern const char *const t_version[LANG_NUM];
extern const char *const t_loadingData[LANG_NUM];
extern const char *const t_enableWidget[LANG_NUM];
extern const char *const t_timezoneLoc[LANG_NUM];
extern const char *const t_language[LANG_NUM];
extern const char *const t_widgetCycleDelay[LANG_NUM];
extern const char *const t_ntpServer[LANG_NUM];
extern const char *const t_orbRotation[LANG_NUM];
extern const char *const t_orbRot[4][LANG_NUM];
extern const char *const t_nightmode[LANG_NUM];
extern const char *const t_tftBrightness[LANG_NUM];
extern const char *const t_dimStartHour[LANG_NUM];
extern const char *const t_dimEndHour[LANG_NUM];
extern const char *const t_dimBrightness[LANG_NUM];

#endif // TRANSLATIONS_H
41 changes: 24 additions & 17 deletions firmware/src/core/utils/MainHelper.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "MainHelper.h"
#include "LittleFSHelper.h"
#include "Translations.h"
#include "config_helper.h"
#include "icons.h"
#include <ArduinoLog.h>
Expand All @@ -23,6 +24,7 @@ static bool s_nightMode = DIM_ENABLED;
static int s_dimStartHour = DIM_START_HOUR;
static int s_dimEndHour = DIM_END_HOUR;
static int s_dimBrightness = DIM_BRIGHTNESS;
static int s_languageId = DEFAULT_LANGUAGE;

void MainHelper::init(WiFiManager *wm, ConfigManager *cm, ScreenManager *sm, WidgetSet *ws) {
s_wifiManager = wm;
Expand Down Expand Up @@ -60,19 +62,22 @@ void MainHelper::setupButtons() {
}

void MainHelper::setupConfig() {
s_configManager->addConfigString("General", "timezoneLoc", &s_timezoneLocation, 30, "Timezone Location, use one from <a href='https://timezonedb.com/time-zones' target='blank'>this list</a>");
s_configManager->addConfigInt("General", "widgetCycDelay", &s_widgetCycleDelay, "Automatically cycle widgets every X seconds, set to 0 to disable");
s_configManager->addConfigString("General", "ntpServer", &s_ntpServer, 30, "NTP server", true);

String optRotation[] = {"No rotation", "Rotate 90° clockwise", "Rotate 180°", "Rotate 270° clockwise"};
s_configManager->addConfigComboBox("TFT Settings", "orbRotation", &s_orbRotation, optRotation, 4, "Orb rotation");
s_configManager->addConfigBool("TFT Settings", "nightmode", &s_nightMode, "Enable Nighttime mode");
s_configManager->addConfigInt("TFT Settings", "tftBrightness", &s_tftBrightness, "TFT Brightness [0-255]", true);
// Set language here to get i18n strings for the configuration
I18n::setLanguageId(s_configManager->getConfigInt("lang", DEFAULT_LANGUAGE));
s_configManager->addConfigString("General", "timezoneLoc", &s_timezoneLocation, 30, i18n(t_timezoneLoc));
String *optLang = I18n::getAllLanguages();
s_configManager->addConfigComboBox("General", "lang", &s_languageId, optLang, LANG_NUM, i18n(t_language));
s_configManager->addConfigInt("General", "widgetCycDelay", &s_widgetCycleDelay, i18n(t_widgetCycleDelay));
s_configManager->addConfigString("General", "ntpServer", &s_ntpServer, 30, i18n(t_ntpServer), true);
String optRotation[] = {i18n(t_orbRot, 0), i18n(t_orbRot, 1), i18n(t_orbRot, 2), i18n(t_orbRot, 3)};
s_configManager->addConfigComboBox("TFT Settings", "orbRotation", &s_orbRotation, optRotation, 4, i18n(t_orbRotation));
s_configManager->addConfigBool("TFT Settings", "nightmode", &s_nightMode, i18n(t_nightmode));
s_configManager->addConfigInt("TFT Settings", "tftBrightness", &s_tftBrightness, i18n(t_tftBrightness), true);
String optHours[] = {"0:00", "1:00", "2:00", "3:00", "4:00", "5:00", "6:00", "7:00", "8:00", "9:00", "10:00", "11:00",
"12:00", "13:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00"};
s_configManager->addConfigComboBox("TFT Settings", "dimStartHour", &s_dimStartHour, optHours, 24, "Nighttime Start [24h format]", true);
s_configManager->addConfigComboBox("TFT Settings", "dimEndHour", &s_dimEndHour, optHours, 24, "Nighttime End [24h format]", true);
s_configManager->addConfigInt("TFT Settings", "dimBrightness", &s_dimBrightness, "Nighttime Brightness [0-255]", true);
s_configManager->addConfigComboBox("TFT Settings", "dimStartHour", &s_dimStartHour, optHours, 24, i18n(t_dimStartHour), true);
s_configManager->addConfigComboBox("TFT Settings", "dimEndHour", &s_dimEndHour, optHours, 24, i18n(t_dimEndHour), true);
s_configManager->addConfigInt("TFT Settings", "dimBrightness", &s_dimBrightness, i18n(t_dimBrightness), true);
}

void MainHelper::buttonPressed(uint8_t buttonId, ButtonState state) {
Expand Down Expand Up @@ -419,7 +424,7 @@ void MainHelper::showWelcome() {
s_screenManager->setFontColor(TFT_WHITE);

s_screenManager->selectScreen(0);
s_screenManager->drawCentreString("Welcome", ScreenCenterX, ScreenCenterY, 29);
s_screenManager->drawCentreString(i18n(t_welcome), ScreenCenterX, ScreenCenterY, 29);
if (GIT_BRANCH != "main" && GIT_BRANCH != "unknown" && GIT_BRANCH != "HEAD") {
s_screenManager->setFontColor(TFT_RED);
s_screenManager->drawCentreString(GIT_BRANCH, ScreenCenterX, ScreenCenterY - 40, 15);
Expand All @@ -428,11 +433,13 @@ void MainHelper::showWelcome() {
}

s_screenManager->selectScreen(1);
s_screenManager->drawCentreString("Info Orbs", ScreenCenterX, ScreenCenterY - 50, 22);
s_screenManager->drawCentreString("by", ScreenCenterX, ScreenCenterY - 5, 22);
s_screenManager->drawCentreString("brett.tech", ScreenCenterX, ScreenCenterY + 30, 22);
s_screenManager->drawCentreString(i18n(t_infoOrbs), ScreenCenterX, ScreenCenterY - 50, 22);
s_screenManager->drawCentreString(i18n(t_by), ScreenCenterX, ScreenCenterY - 5, 22);
s_screenManager->drawCentreString(i18n(t_brettTech), ScreenCenterX, ScreenCenterY + 30, 22);
s_screenManager->setFontColor(TFT_RED);
s_screenManager->drawCentreString("version: 1.1.0", ScreenCenterX, ScreenCenterY + 65, 15);
// VERSION is defined in MainHelper.h
const auto version = String(i18n(t_version)) + " " + String(VERSION);
s_screenManager->drawCentreString(version, ScreenCenterX, ScreenCenterY + 65, 15);

s_screenManager->selectScreen(2);
s_screenManager->drawJpg(0, 0, logo_start, logo_end - logo_start);
Expand Down Expand Up @@ -503,4 +510,4 @@ void MainHelper::watchdogInit() {

void MainHelper::watchdogReset() {
esp_task_wdt_reset();
}
}
2 changes: 2 additions & 0 deletions firmware/src/core/utils/MainHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include "git_info.h"
#include <Arduino.h>

#define VERSION "1.2beta"

// Set defaults if not set in config.h
#ifndef TFT_BRIGHTNESS
#define TFT_BRIGHTNESS 255
Expand Down
1 change: 1 addition & 0 deletions firmware/src/core/widget/Widget.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "Button.h"
#include "ConfigManager.h"
#include "ScreenManager.h"
#include "Translations.h" // include for use by all Widgets
#include "config_helper.h"

class Widget {
Expand Down
9 changes: 5 additions & 4 deletions firmware/src/core/widget/WidgetSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ void WidgetSet::next() {
}

void WidgetSet::prev() {
m_currentWidget--;
if (m_currentWidget < 0) {
if (m_currentWidget == 0) {
m_currentWidget = m_widgetCount - 1;
} else {
m_currentWidget--;
}
if (!getCurrent()->isEnabled()) {
// Recursive call to next()
Expand All @@ -82,11 +83,11 @@ void WidgetSet::showCenteredLine(int screen, const String &text) {
}

void WidgetSet::showLoading() {
showCenteredLine(3, "Loading data:");
showCenteredLine(3, I18n::get(t_loadingData));
}

void WidgetSet::updateAll() {
for (int8_t i; i < m_widgetCount; i++) {
for (uint8_t i = 0; i < m_widgetCount; i++) {
if (m_widgets[i]->isEnabled()) {
Serial.printf("updating widget %s\n", m_widgets[i]->getName().c_str());
showCenteredLine(4, m_widgets[i]->getName());
Expand Down
4 changes: 2 additions & 2 deletions firmware/src/core/widget/WidgetSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class WidgetSet {
ScreenManager *m_screenManager;
bool m_clearScreensOnDrawCurrent = true;
Widget *m_widgets[MAX_WIDGETS];
int8_t m_widgetCount = 0;
int8_t m_currentWidget = 0;
uint8_t m_widgetCount = 0;
uint8_t m_currentWidget = 0;

bool m_initialized = false;

Expand Down
Loading

0 comments on commit 28963a8

Please sign in to comment.