Skip to content

Commit 28963a8

Browse files
Merge pull request brettdottech#240 from flattermann/i18n
Added Translation support (I18n)
2 parents c5b42b3 + 2a5198f commit 28963a8

File tree

15 files changed

+456
-82
lines changed

15 files changed

+456
-82
lines changed

firmware/src/core/i18n/I18n.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include "I18n.h"
2+
3+
String I18n::s_allLanguages[] = ALL_LANGUAGES;
4+
int I18n::s_languageId = DEFAULT_LANGUAGE;
5+
6+
void I18n::setLanguageId(const int langId) {
7+
s_languageId = langId;
8+
}
9+
10+
String I18n::getLanguageString(const int langId) {
11+
if (langId >= 0 && langId < LANG_NUM) {
12+
return s_allLanguages[langId];
13+
}
14+
return "invalid";
15+
}
16+
17+
String *I18n::getAllLanguages() {
18+
return s_allLanguages;
19+
}

firmware/src/core/i18n/I18n.h

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#ifndef I18N_H
2+
#define I18N_H
3+
4+
#include "config_helper.h"
5+
#include <Arduino.h>
6+
7+
#define ALL_LANGUAGES {"en", "de", "fr"}
8+
9+
enum Language {
10+
LANG_EN = 0,
11+
LANG_DE,
12+
LANG_FR,
13+
LANG_NUM // keep this as last item
14+
};
15+
16+
#define DEFAULT_LANGUAGE LANG_EN
17+
18+
class I18n {
19+
public:
20+
static void setLanguageId(int langId);
21+
static String getLanguageString(int langId);
22+
static String *getAllLanguages();
23+
24+
template <size_t N>
25+
static const char *get(const char *const (&translations)[N]) {
26+
const char *text = translations[s_languageId];
27+
if (text == nullptr) {
28+
text = translations[DEFAULT_LANGUAGE];
29+
}
30+
return text ? text : "@missingTranslation@";
31+
}
32+
33+
template <size_t X, size_t Y>
34+
static const char *get(const char *const (&translations)[X][Y], size_t index) {
35+
if (index >= X) {
36+
return "@invalidIndex@";
37+
}
38+
39+
const char *text = translations[index][s_languageId];
40+
if (text == nullptr) {
41+
text = translations[index][DEFAULT_LANGUAGE];
42+
}
43+
return text ? text : "@missingTranslation@";
44+
}
45+
46+
private:
47+
static String s_allLanguages[];
48+
static int s_languageId;
49+
};
50+
51+
// I18n helper
52+
template <size_t N>
53+
static const char *i18n(const char *const (&translations)[N]) {
54+
return I18n::get(translations);
55+
}
56+
57+
// I18n helper (with index)
58+
template <size_t X, size_t Y>
59+
static const char *i18n(const char *const (&translations)[X][Y], size_t index) {
60+
return I18n::get(translations, index);
61+
}
62+
63+
#endif // I18N_H
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#include "Translations.h"
2+
3+
// Languages are defined in I18n.h:
4+
// EN, DE, FR
5+
6+
// THIS FILE IS ONLY FOR GENERAL TRANSLATIONS
7+
// Widget translations should be placed in a separate .h/.cpp in the Widget directory
8+
9+
// All translation variables should start with "t_"
10+
11+
constexpr const char *const t_welcome[LANG_NUM] = {
12+
"Welcome", // EN
13+
"Willkommen", // DE
14+
"Bienvenue" // FR
15+
};
16+
17+
constexpr const char *const t_infoOrbs[LANG_NUM] = {
18+
"InfoOrbs", // EN
19+
"InfoOrbs", // DE
20+
"InfoOrbs" // FR
21+
};
22+
23+
constexpr const char *const t_by[LANG_NUM] = {
24+
"by", // EN
25+
"von", // DE
26+
"par" // FR
27+
};
28+
29+
constexpr const char *const t_brettTech[LANG_NUM] = {
30+
"brett.tech", // EN
31+
"brett.tech", // DE
32+
"brett.tech" // FR
33+
};
34+
35+
constexpr const char *const t_version[LANG_NUM] = {
36+
"Version", // EN
37+
"Version", // DE
38+
"Version" // FR
39+
};
40+
41+
constexpr const char *const t_loadingData[LANG_NUM] = {
42+
"Loading data:", // EN
43+
"Lade Daten:", // DE
44+
"Chargement:" // FR
45+
};
46+
47+
constexpr const char *const t_enableWidget[LANG_NUM] = {
48+
"Enable Widget", // EN
49+
"Widget aktivieren", // DE
50+
"Activer le widget" // FR
51+
};
52+
53+
constexpr const char *const t_timezoneLoc[LANG_NUM] = {
54+
"Timezone, use one from <a href='https://timezonedb.com/time-zones' target='blank'>this list</a>", // EN
55+
"Zeitzone, verwenden Sie eine aus <a href='https://timezonedb.com/time-zones' target='blank'>dieser Liste</a>", // DE
56+
"Fuseau horaire, utilisez-en un de <a href='https://timezonedb.com/time-zones' target='blank'>cette liste</a>" // FR
57+
};
58+
59+
constexpr const char *const t_language[LANG_NUM] = {
60+
"Language", // EN
61+
"Sprache", // DE
62+
"Langue" // FR
63+
};
64+
65+
constexpr const char *const t_widgetCycleDelay[LANG_NUM] = {
66+
"Automatically cycle widgets every X seconds, set to 0 to disable", // EN
67+
"Wechseln Sie die Widgets automatisch alle X Sekunden, auf 0 setzen, um zu deaktivieren", // DE
68+
"Faites défiler les widgets automatiquement toutes les X secondes, définissez sur 0 pour désactiver" // FR
69+
};
70+
71+
constexpr const char *const t_ntpServer[LANG_NUM] = {
72+
"NTP server", // EN
73+
"NTP-Server", // DE
74+
"Serveur NTP" // FR
75+
};
76+
77+
constexpr const char *const t_orbRotation[LANG_NUM] = {
78+
"Orb rotation", // EN
79+
"Orb-Drehung", // DE
80+
"Rotation des Orbes" // FR
81+
};
82+
83+
constexpr const char *const t_orbRot[4][LANG_NUM] = {
84+
{
85+
"No rotation", // EN
86+
"Keine Drehung", // DE
87+
"Pas de rotation" // FR
88+
},
89+
{
90+
"Rotate 90°", // EN
91+
"90° gedreht", // DE
92+
"Tourné de 90°" // FR
93+
},
94+
{
95+
"Rotate 180°", // EN
96+
"180° gedreht", // DE
97+
"Tourné de 180°" // FR
98+
},
99+
{
100+
"Rotate 270°", // EN
101+
"270° gedreht", // DE
102+
"Tourné de 270°" // FR
103+
}};
104+
105+
constexpr const char *const t_nightmode[LANG_NUM] = {
106+
"Enable Nighttime mode", // EN
107+
"Nachtmodus aktivieren", // DE
108+
"Activer le mode nuit" // FR
109+
};
110+
111+
constexpr const char *const t_tftBrightness[LANG_NUM] = {
112+
"TFT Brightness [0-255]", // EN
113+
"TFT-Helligkeit [0-255]", // DE
114+
"Luminosité TFT [0-255]" // FR
115+
};
116+
117+
constexpr const char *const t_dimStartHour[LANG_NUM] = {
118+
"Nighttime Start [24h format]", // EN
119+
"Nachtmodus Start [24h-Format]", // DE
120+
"Début de la nuit [format 24h]" // FR
121+
};
122+
123+
constexpr const char *const t_dimEndHour[LANG_NUM] = {
124+
"Nighttime End [24h format]", // EN
125+
"Nachtmodus Ende [24h-Format]", // DE
126+
"Fin de la nuit [format 24h]" // FR
127+
};
128+
129+
constexpr const char *const t_dimBrightness[LANG_NUM] = {
130+
"Nighttime Brightness [0-255]", // EN
131+
"Nachtmodus Helligkeit [0-255]", // DE
132+
"Luminosité nocturne [0-255]" // FR
133+
};

firmware/src/core/i18n/Translations.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#ifndef TRANSLATIONS_H
2+
#define TRANSLATIONS_H
3+
4+
#include "I18n.h"
5+
6+
// THIS FILE IS ONLY FOR GENERAL TRANSLATIONS
7+
// Widget translations should be placed in a separate .h/.cpp in the Widget directory
8+
9+
// All translation variables should start with "t_"
10+
11+
extern const char *const t_welcome[LANG_NUM];
12+
extern const char *const t_infoOrbs[LANG_NUM];
13+
extern const char *const t_by[LANG_NUM];
14+
extern const char *const t_brettTech[LANG_NUM];
15+
extern const char *const t_version[LANG_NUM];
16+
extern const char *const t_loadingData[LANG_NUM];
17+
extern const char *const t_enableWidget[LANG_NUM];
18+
extern const char *const t_timezoneLoc[LANG_NUM];
19+
extern const char *const t_language[LANG_NUM];
20+
extern const char *const t_widgetCycleDelay[LANG_NUM];
21+
extern const char *const t_ntpServer[LANG_NUM];
22+
extern const char *const t_orbRotation[LANG_NUM];
23+
extern const char *const t_orbRot[4][LANG_NUM];
24+
extern const char *const t_nightmode[LANG_NUM];
25+
extern const char *const t_tftBrightness[LANG_NUM];
26+
extern const char *const t_dimStartHour[LANG_NUM];
27+
extern const char *const t_dimEndHour[LANG_NUM];
28+
extern const char *const t_dimBrightness[LANG_NUM];
29+
30+
#endif // TRANSLATIONS_H

firmware/src/core/utils/MainHelper.cpp

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "MainHelper.h"
22
#include "LittleFSHelper.h"
3+
#include "Translations.h"
34
#include "config_helper.h"
45
#include "icons.h"
56
#include <ArduinoLog.h>
@@ -23,6 +24,7 @@ static bool s_nightMode = DIM_ENABLED;
2324
static int s_dimStartHour = DIM_START_HOUR;
2425
static int s_dimEndHour = DIM_END_HOUR;
2526
static int s_dimBrightness = DIM_BRIGHTNESS;
27+
static int s_languageId = DEFAULT_LANGUAGE;
2628

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

6264
void MainHelper::setupConfig() {
63-
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>");
64-
s_configManager->addConfigInt("General", "widgetCycDelay", &s_widgetCycleDelay, "Automatically cycle widgets every X seconds, set to 0 to disable");
65-
s_configManager->addConfigString("General", "ntpServer", &s_ntpServer, 30, "NTP server", true);
66-
67-
String optRotation[] = {"No rotation", "Rotate 90° clockwise", "Rotate 180°", "Rotate 270° clockwise"};
68-
s_configManager->addConfigComboBox("TFT Settings", "orbRotation", &s_orbRotation, optRotation, 4, "Orb rotation");
69-
s_configManager->addConfigBool("TFT Settings", "nightmode", &s_nightMode, "Enable Nighttime mode");
70-
s_configManager->addConfigInt("TFT Settings", "tftBrightness", &s_tftBrightness, "TFT Brightness [0-255]", true);
65+
// Set language here to get i18n strings for the configuration
66+
I18n::setLanguageId(s_configManager->getConfigInt("lang", DEFAULT_LANGUAGE));
67+
s_configManager->addConfigString("General", "timezoneLoc", &s_timezoneLocation, 30, i18n(t_timezoneLoc));
68+
String *optLang = I18n::getAllLanguages();
69+
s_configManager->addConfigComboBox("General", "lang", &s_languageId, optLang, LANG_NUM, i18n(t_language));
70+
s_configManager->addConfigInt("General", "widgetCycDelay", &s_widgetCycleDelay, i18n(t_widgetCycleDelay));
71+
s_configManager->addConfigString("General", "ntpServer", &s_ntpServer, 30, i18n(t_ntpServer), true);
72+
String optRotation[] = {i18n(t_orbRot, 0), i18n(t_orbRot, 1), i18n(t_orbRot, 2), i18n(t_orbRot, 3)};
73+
s_configManager->addConfigComboBox("TFT Settings", "orbRotation", &s_orbRotation, optRotation, 4, i18n(t_orbRotation));
74+
s_configManager->addConfigBool("TFT Settings", "nightmode", &s_nightMode, i18n(t_nightmode));
75+
s_configManager->addConfigInt("TFT Settings", "tftBrightness", &s_tftBrightness, i18n(t_tftBrightness), true);
7176
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",
7277
"12:00", "13:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00"};
73-
s_configManager->addConfigComboBox("TFT Settings", "dimStartHour", &s_dimStartHour, optHours, 24, "Nighttime Start [24h format]", true);
74-
s_configManager->addConfigComboBox("TFT Settings", "dimEndHour", &s_dimEndHour, optHours, 24, "Nighttime End [24h format]", true);
75-
s_configManager->addConfigInt("TFT Settings", "dimBrightness", &s_dimBrightness, "Nighttime Brightness [0-255]", true);
78+
s_configManager->addConfigComboBox("TFT Settings", "dimStartHour", &s_dimStartHour, optHours, 24, i18n(t_dimStartHour), true);
79+
s_configManager->addConfigComboBox("TFT Settings", "dimEndHour", &s_dimEndHour, optHours, 24, i18n(t_dimEndHour), true);
80+
s_configManager->addConfigInt("TFT Settings", "dimBrightness", &s_dimBrightness, i18n(t_dimBrightness), true);
7681
}
7782

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

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

430435
s_screenManager->selectScreen(1);
431-
s_screenManager->drawCentreString("Info Orbs", ScreenCenterX, ScreenCenterY - 50, 22);
432-
s_screenManager->drawCentreString("by", ScreenCenterX, ScreenCenterY - 5, 22);
433-
s_screenManager->drawCentreString("brett.tech", ScreenCenterX, ScreenCenterY + 30, 22);
436+
s_screenManager->drawCentreString(i18n(t_infoOrbs), ScreenCenterX, ScreenCenterY - 50, 22);
437+
s_screenManager->drawCentreString(i18n(t_by), ScreenCenterX, ScreenCenterY - 5, 22);
438+
s_screenManager->drawCentreString(i18n(t_brettTech), ScreenCenterX, ScreenCenterY + 30, 22);
434439
s_screenManager->setFontColor(TFT_RED);
435-
s_screenManager->drawCentreString("version: 1.1.0", ScreenCenterX, ScreenCenterY + 65, 15);
440+
// VERSION is defined in MainHelper.h
441+
const auto version = String(i18n(t_version)) + " " + String(VERSION);
442+
s_screenManager->drawCentreString(version, ScreenCenterX, ScreenCenterY + 65, 15);
436443

437444
s_screenManager->selectScreen(2);
438445
s_screenManager->drawJpg(0, 0, logo_start, logo_end - logo_start);
@@ -503,4 +510,4 @@ void MainHelper::watchdogInit() {
503510

504511
void MainHelper::watchdogReset() {
505512
esp_task_wdt_reset();
506-
}
513+
}

firmware/src/core/utils/MainHelper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "git_info.h"
1212
#include <Arduino.h>
1313

14+
#define VERSION "1.2beta"
15+
1416
// Set defaults if not set in config.h
1517
#ifndef TFT_BRIGHTNESS
1618
#define TFT_BRIGHTNESS 255

firmware/src/core/widget/Widget.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "Button.h"
55
#include "ConfigManager.h"
66
#include "ScreenManager.h"
7+
#include "Translations.h" // include for use by all Widgets
78
#include "config_helper.h"
89

910
class Widget {

firmware/src/core/widget/WidgetSet.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ void WidgetSet::next() {
5353
}
5454

5555
void WidgetSet::prev() {
56-
m_currentWidget--;
57-
if (m_currentWidget < 0) {
56+
if (m_currentWidget == 0) {
5857
m_currentWidget = m_widgetCount - 1;
58+
} else {
59+
m_currentWidget--;
5960
}
6061
if (!getCurrent()->isEnabled()) {
6162
// Recursive call to next()
@@ -82,11 +83,11 @@ void WidgetSet::showCenteredLine(int screen, const String &text) {
8283
}
8384

8485
void WidgetSet::showLoading() {
85-
showCenteredLine(3, "Loading data:");
86+
showCenteredLine(3, I18n::get(t_loadingData));
8687
}
8788

8889
void WidgetSet::updateAll() {
89-
for (int8_t i; i < m_widgetCount; i++) {
90+
for (uint8_t i = 0; i < m_widgetCount; i++) {
9091
if (m_widgets[i]->isEnabled()) {
9192
Serial.printf("updating widget %s\n", m_widgets[i]->getName().c_str());
9293
showCenteredLine(4, m_widgets[i]->getName());

firmware/src/core/widget/WidgetSet.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ class WidgetSet {
2828
ScreenManager *m_screenManager;
2929
bool m_clearScreensOnDrawCurrent = true;
3030
Widget *m_widgets[MAX_WIDGETS];
31-
int8_t m_widgetCount = 0;
32-
int8_t m_currentWidget = 0;
31+
uint8_t m_widgetCount = 0;
32+
uint8_t m_currentWidget = 0;
3333

3434
bool m_initialized = false;
3535

0 commit comments

Comments
 (0)