Added Irish language

Added Irish language.
Bug fixes: weather description random characters addressed.
This commit is contained in:
M-Factory
2025-08-19 15:48:42 +09:00
parent 03f0ef033b
commit 41a578cc72
10 changed files with 249 additions and 234 deletions

View File

@@ -68,7 +68,7 @@ int dimBrightness = 2; // Dimming level (0-15)
bool countdownEnabled = false;
time_t countdownTargetTimestamp = 0; // Unix timestamp
char countdownLabel[64] = ""; // Label for the countdown
bool isDramaticCountdown = true; // Default to the dramatic countdown mode
bool isDramaticCountdown = true; // Default to the dramatic countdown mode
// State management
bool weatherCycleStarted = false;
@@ -267,7 +267,7 @@ void loadConfig() {
countdownEnabled = countdownObj["enabled"] | false;
countdownTargetTimestamp = countdownObj["targetTimestamp"] | 0;
isDramaticCountdown = countdownObj["isDramaticCountdown"] | true;
isDramaticCountdown = countdownObj["isDramaticCountdown"] | true;
JsonVariant labelVariant = countdownObj["label"];
if (labelVariant.isNull() || !labelVariant.is<const char *>()) {
@@ -285,7 +285,7 @@ void loadConfig() {
countdownEnabled = false;
countdownTargetTimestamp = 0;
strcpy(countdownLabel, "");
isDramaticCountdown = true;
isDramaticCountdown = true;
Serial.println(F("[CONFIG] Countdown object not found, defaulting to disabled."));
countdownFinished = false;
}
@@ -430,7 +430,7 @@ void setupTime() {
}
if (serverOk) {
configTime(0, 0, ntpServer1, ntpServer2); // safe to call now
configTime(0, 0, ntpServer1, ntpServer2); // safe to call now
setenv("TZ", ianaToPosix(timeZone), 1);
tzset();
ntpState = NTP_SYNCING;
@@ -440,7 +440,7 @@ void setupTime() {
} else {
Serial.println(F("[TIME] NTP server lookup failed — skipping sync"));
ntpSyncSuccessful = false;
ntpState = NTP_IDLE; // or custom NTP_ERROR state
ntpState = NTP_IDLE; // or custom NTP_ERROR state
// Trigger your error display here if desired
}
}
@@ -927,30 +927,30 @@ void setupWebServer() {
});
server.on("/set_dramatic_countdown", HTTP_POST, [](AsyncWebServerRequest *request) {
bool enableDramaticNow = false;
if (request->hasParam("value", true)) {
String v = request->getParam("value", true)->value();
enableDramaticNow = (v == "1" || v == "true" || v == "on");
}
server.on("/set_dramatic_countdown", HTTP_POST, [](AsyncWebServerRequest *request) {
bool enableDramaticNow = false;
if (request->hasParam("value", true)) {
String v = request->getParam("value", true)->value();
enableDramaticNow = (v == "1" || v == "true" || v == "on");
}
// Check if the state has changed
if (isDramaticCountdown == enableDramaticNow) {
Serial.println(F("[WEBSERVER] Dramatic Countdown state unchanged, ignoring."));
// Check if the state has changed
if (isDramaticCountdown == enableDramaticNow) {
Serial.println(F("[WEBSERVER] Dramatic Countdown state unchanged, ignoring."));
request->send(200, "application/json", "{\"ok\":true}");
return;
}
// Update the global variable
isDramaticCountdown = enableDramaticNow;
// Call saveCountdownConfig with only the existing parameters.
// It will read the updated global variable 'isDramaticCountdown'.
saveCountdownConfig(countdownEnabled, countdownTargetTimestamp, countdownLabel);
Serial.printf("[WEBSERVER] Set Dramatic Countdown to %d\n", isDramaticCountdown);
request->send(200, "application/json", "{\"ok\":true}");
return;
}
// Update the global variable
isDramaticCountdown = enableDramaticNow;
// Call saveCountdownConfig with only the existing parameters.
// It will read the updated global variable 'isDramaticCountdown'.
saveCountdownConfig(countdownEnabled, countdownTargetTimestamp, countdownLabel);
Serial.printf("[WEBSERVER] Set Dramatic Countdown to %d\n", isDramaticCountdown);
request->send(200, "application/json", "{\"ok\":true}");
});
});
server.begin();
@@ -1143,7 +1143,7 @@ String buildWeatherURL() {
String langForAPI = String(language);
if (langForAPI == "eo" || langForAPI == "sw" || langForAPI == "ja") {
if (langForAPI == "eo" || langForAPI == "ga" || langForAPI == "sw" || langForAPI == "ja") {
langForAPI = "en";
}
base += "&lang=" + langForAPI;
@@ -1317,7 +1317,7 @@ void advanceDisplayMode() {
String ntpField = String(ntpServer2);
bool nightscoutConfigured = ntpField.startsWith("https://");
if (displayMode == 0) { // Clock
if (displayMode == 0) { // Clock
if (showDate) {
displayMode = 5; // Date mode right after Clock
Serial.println(F("[DISPLAY] Switching to display mode: DATE (from Clock)"));
@@ -1348,7 +1348,7 @@ void advanceDisplayMode() {
displayMode = 0;
Serial.println(F("[DISPLAY] Switching to display mode: CLOCK (from Date)"));
}
} else if (displayMode == 1) { // Weather
} else if (displayMode == 1) { // Weather
if (showWeatherDescription && weatherAvailable && weatherDescription.length() > 0) {
displayMode = 2;
Serial.println(F("[DISPLAY] Switching to display mode: DESCRIPTION (from Weather)"));
@@ -1362,7 +1362,7 @@ void advanceDisplayMode() {
displayMode = 0;
Serial.println(F("[DISPLAY] Switching to display mode: CLOCK (from Weather)"));
}
} else if (displayMode == 2) { // Weather Description
} else if (displayMode == 2) { // Weather Description
if (countdownEnabled && !countdownFinished && ntpSyncSuccessful) {
displayMode = 3;
Serial.println(F("[DISPLAY] Switching to display mode: COUNTDOWN (from Description)"));
@@ -1748,16 +1748,20 @@ void loop() {
// --- WEATHER DESCRIPTION Display Mode ---
if (displayMode == 2 && showWeatherDescription && weatherAvailable && weatherDescription.length() > 0) {
String desc = weatherDescription;
desc.toUpperCase();
// prepare safe buffer
static char descBuffer[128]; // large enough for OWM translations
desc.toCharArray(descBuffer, sizeof(descBuffer));
if (desc.length() > 8) {
if (!descScrolling) {
P.displayClear();
textEffect_t actualScrollDirection = getEffectiveScrollDirection(PA_SCROLL_LEFT, flipDisplay);
P.displayScroll(desc.c_str(), PA_CENTER, actualScrollDirection, GENERAL_SCROLL_SPEED);
P.displayScroll(descBuffer, PA_CENTER, actualScrollDirection, GENERAL_SCROLL_SPEED);
descScrolling = true;
descScrollEndTime = 0; // reset end time at start
}
@@ -1780,7 +1784,7 @@ void loop() {
if (descStartTime == 0) {
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(1);
P.print(desc.c_str());
P.print(descBuffer);
descStartTime = millis();
}
if (millis() - descStartTime > descriptionDuration) {
@@ -2022,7 +2026,7 @@ void loop() {
P.displayAnimate();
}
// --- NEW: SINGLE-LINE COUNTDOWN LOGIC ---
// --- NEW: SINGLE-LINE COUNTDOWN LOGIC ---
else {
long days = timeRemaining / (24 * 3600);
long hours = (timeRemaining % (24 * 3600)) / 3600;
@@ -2052,9 +2056,9 @@ void loop() {
} else {
sprintf(buf, "%s IN: %02ldH %02ldM %02ldS", label.c_str(), hours, minutes, seconds);
}
String fullString = String(buf);
// Display the full string and scroll it
P.displayClear();
P.setTextAlignment(PA_LEFT);
@@ -2074,14 +2078,14 @@ void loop() {
yield();
return;
}
}
}
// Keep alignment reset just in case
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(1);
yield();
return;
} // End of if (displayMode == 3 && ...)
// Keep alignment reset just in case
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(1);
yield();
return;
} // End of if (displayMode == 3 && ...)
// --- NIGHTSCOUT Display Mode ---
@@ -2197,6 +2201,7 @@ void loop() {
"et", // Estonian
"fi", // Finnish
"fr", // French
"ga", // Irish
"hr", // Croatian
"hu", // Hungarian
"it", // Italian

View File

@@ -447,6 +447,7 @@ textarea::placeholder {
<option value="de">German</option>
<option value="hu">Hungarian</option>
<option value="it">Italian</option>
<option value="ga">Irish</option>
<option value="ja">Japanese</option>
<option value="lv">Latvian</option>
<option value="lt">Lithuanian</option>

View File

@@ -17,6 +17,7 @@ const DaysOfWeekMapping days_mappings[] = {
{ "et", { "p&a", "e&s", "t&e", "k&o", "n&e", "r&e", "l&a" } },
{ "fi", { "s&u&n", "m&a&a", "t&i&s", "k&e&s", "t&o&r", "p&e&r", "l&a&u" } },
{ "fr", { "d&i&m", "l&u&n", "m&a&r", "m&e&r", "j&e&u", "v&e&n", "s&a&m" } },
{ "ga", { "d&o&m", "l&u&a", "m&a&i", "c&e&a", "d&e&a", "a&o&i", "s&a&t" } },
{ "hr", { "n&e&d", "p&o&n", "u&t&o", "s&r&i", "c&e&t", "p&e&t", "s&u&b" } },
{ "hu", { "v&a&s", "h&e&t", "k&e&d", "s&z&e", "c&s&u", "p&e&t", "s&z&o" } },
{ "it", { "d&o&m", "l&u&n", "m&a&r", "m&e&r", "g&i&o", "v&e&n", "s&a&b" } },

View File

@@ -43,7 +43,7 @@ MD_MAX72XX::fontType_t mFactory[] PROGMEM =
6, 66, 37, 18, 72, 164, 66, // 37 - '%'
1, 1, // 38 - '&'
1, 6, // 39 - ''
1, 0, // 40 - '('
13, 254, 17, 17, 254, 0, 126, 129, 65, 190, 0, 129, 255, 129, // 40 - '('
17, 130, 186, 198, 254, 134, 234, 134, 254, 250, 130, 250, 254, 134, 234, 134, 254, 124, // 41 - ')'
20, 250, 130, 250, 254, 130, 170, 186, 254, 130, 250, 226, 250, 134, 254, 130, 234, 234, 246, 254, 124, // 42 - '*'
1, 0, // 43 - '+'

View File

@@ -20,6 +20,7 @@ const MonthsMapping months_mappings[] = {
{ "hr", { "s&i&j", "v&e&l", "o&z&u", "t&r&a", "s&v&i", "l&i&p", "s&r&p", "k&o&l", "r&u&j", "l&i&s", "s&t&u", "p&r&o" } }, // Croatian
{ "hu", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&u&g", "s&z&e", "o&k&t", "n&o&v", "d&e&c" } }, // Hungarian
{ "it", { "g&e&n", "f&e&b", "m&a&r", "a&p&r", "m&a&g", "g&i&u", "l&u&g", "a&g&o", "s&e&t", "o&t&t", "n&o&v", "d&i&c" } }, // Italian
{ "ga", { "e&a&n", "f&e&a", "m&a&r", "a&i&b", "b&e&a", "m&e&i", "i&u&i", "l&u&n", "m&e&a", "d&e&i", "s&a&m", "n&o&l" } }, // Irish
{ "ja", { "1 ²", "2 ²", "3 ²", "4 ²", "5 ²", "6 ²", "7 ²", "8 ²", "9 ²", "10 ²", "11 ²", "12 ²" } }, // Japanese
{ "lt", { "s&a&u", "v&a&s", "k&o&v", "b&a&l", "g&e&g", "b&i&r", "l&i&e", "r&u&g", "s&w&e", "s&p&a", "l&a&p", "g&r&u" } }, // Lithuanian
{ "lv", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Latvian

View File

@@ -68,7 +68,7 @@ int dimBrightness = 2; // Dimming level (0-15)
bool countdownEnabled = false;
time_t countdownTargetTimestamp = 0; // Unix timestamp
char countdownLabel[64] = ""; // Label for the countdown
bool isDramaticCountdown = true; // Default to the dramatic countdown mode
bool isDramaticCountdown = true; // Default to the dramatic countdown mode
// State management
bool weatherCycleStarted = false;
@@ -267,8 +267,8 @@ void loadConfig() {
countdownEnabled = countdownObj["enabled"] | false;
countdownTargetTimestamp = countdownObj["targetTimestamp"] | 0;
isDramaticCountdown = countdownObj["isDramaticCountdown"] | true;
isDramaticCountdown = countdownObj["isDramaticCountdown"] | true;
JsonVariant labelVariant = countdownObj["label"];
if (labelVariant.isNull() || !labelVariant.is<const char *>()) {
@@ -286,7 +286,7 @@ void loadConfig() {
countdownEnabled = false;
countdownTargetTimestamp = 0;
strcpy(countdownLabel, "");
isDramaticCountdown = true;
isDramaticCountdown = true;
Serial.println(F("[CONFIG] Countdown object not found, defaulting to disabled."));
countdownFinished = false;
}
@@ -928,30 +928,30 @@ void setupWebServer() {
request->send(200, "application/json", "{\"ok\":true}");
});
server.on("/set_dramatic_countdown", HTTP_POST, [](AsyncWebServerRequest *request) {
bool enableDramaticNow = false;
if (request->hasParam("value", true)) {
String v = request->getParam("value", true)->value();
enableDramaticNow = (v == "1" || v == "true" || v == "on");
}
server.on("/set_dramatic_countdown", HTTP_POST, [](AsyncWebServerRequest *request) {
bool enableDramaticNow = false;
if (request->hasParam("value", true)) {
String v = request->getParam("value", true)->value();
enableDramaticNow = (v == "1" || v == "true" || v == "on");
}
// Check if the state has changed
if (isDramaticCountdown == enableDramaticNow) {
Serial.println(F("[WEBSERVER] Dramatic Countdown state unchanged, ignoring."));
// Check if the state has changed
if (isDramaticCountdown == enableDramaticNow) {
Serial.println(F("[WEBSERVER] Dramatic Countdown state unchanged, ignoring."));
request->send(200, "application/json", "{\"ok\":true}");
return;
}
// Update the global variable
isDramaticCountdown = enableDramaticNow;
// Call saveCountdownConfig with only the existing parameters.
// It will read the updated global variable 'isDramaticCountdown'.
saveCountdownConfig(countdownEnabled, countdownTargetTimestamp, countdownLabel);
Serial.printf("[WEBSERVER] Set Dramatic Countdown to %d\n", isDramaticCountdown);
request->send(200, "application/json", "{\"ok\":true}");
return;
}
// Update the global variable
isDramaticCountdown = enableDramaticNow;
// Call saveCountdownConfig with only the existing parameters.
// It will read the updated global variable 'isDramaticCountdown'.
saveCountdownConfig(countdownEnabled, countdownTargetTimestamp, countdownLabel);
Serial.printf("[WEBSERVER] Set Dramatic Countdown to %d\n", isDramaticCountdown);
request->send(200, "application/json", "{\"ok\":true}");
});
});
server.begin();
@@ -1142,7 +1142,7 @@ String buildWeatherURL() {
String langForAPI = String(language);
if (langForAPI == "eo" || langForAPI == "sw" || langForAPI == "ja") {
if (langForAPI == "eo" || langForAPI == "ga" || langForAPI == "sw" || langForAPI == "ja") {
langForAPI = "en";
}
base += "&lang=" + langForAPI;
@@ -1743,16 +1743,19 @@ void loop() {
}
// --- WEATHER DESCRIPTION Display Mode ---
if (displayMode == 2 && showWeatherDescription && weatherAvailable && weatherDescription.length() > 0) {
String desc = weatherDescription;
// prepare safe buffer
static char descBuffer[128]; // large enough for OWM translations
desc.toCharArray(descBuffer, sizeof(descBuffer));
if (desc.length() > 8) {
if (!descScrolling) {
P.displayClear();
textEffect_t actualScrollDirection = getEffectiveScrollDirection(PA_SCROLL_LEFT, flipDisplay);
P.displayScroll(desc.c_str(), PA_CENTER, actualScrollDirection, GENERAL_SCROLL_SPEED);
P.displayScroll(descBuffer, PA_CENTER, actualScrollDirection, GENERAL_SCROLL_SPEED);
descScrolling = true;
descScrollEndTime = 0; // reset end time at start
}
@@ -1775,7 +1778,7 @@ void loop() {
if (descStartTime == 0) {
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(1);
P.print(desc.c_str());
P.print(descBuffer);
descStartTime = millis();
}
if (millis() - descStartTime > descriptionDuration) {
@@ -2016,7 +2019,7 @@ void loop() {
P.displayAnimate();
}
// --- NEW: SINGLE-LINE COUNTDOWN LOGIC ---
// --- NEW: SINGLE-LINE COUNTDOWN LOGIC ---
else {
long days = timeRemaining / (24 * 3600);
long hours = (timeRemaining % (24 * 3600)) / 3600;
@@ -2046,9 +2049,9 @@ void loop() {
} else {
sprintf(buf, "%s IN: %02ldH %02ldM %02ldS", label.c_str(), hours, minutes, seconds);
}
String fullString = String(buf);
// Display the full string and scroll it
P.displayClear();
P.setTextAlignment(PA_LEFT);
@@ -2068,177 +2071,178 @@ void loop() {
yield();
return;
}
}
// Keep alignment reset just in case
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(1);
yield();
return;
} // End of if (displayMode == 3 && ...)
// --- NIGHTSCOUT Display Mode ---
if (displayMode == 4) {
String ntpField = String(ntpServer2);
// These static variables will retain their values between calls to this block
static unsigned long lastNightscoutFetchTime = 0;
const unsigned long NIGHTSCOUT_FETCH_INTERVAL = 150000; // 2.5 minutes
static int currentGlucose = -1;
static String currentDirection = "?";
// Check if it's time to fetch new data or if we have no data yet
if (currentGlucose == -1 || millis() - lastNightscoutFetchTime >= NIGHTSCOUT_FETCH_INTERVAL) {
WiFiClientSecure client;
client.setInsecure();
HTTPClient https;
https.begin(client, ntpField);
https.setTimeout(5000); // This sets both the connection and response timeout.
Serial.print("[HTTPS] Nightscout fetch initiated...\n");
int httpCode = https.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = https.getString();
StaticJsonDocument<1024> doc;
DeserializationError error = deserializeJson(doc, payload);
if (!error && doc.is<JsonArray>() && doc.size() > 0) {
JsonObject firstReading = doc[0].as<JsonObject>();
currentGlucose = firstReading["glucose"] | firstReading["sgv"] | -1;
currentDirection = firstReading["direction"] | "?";
Serial.printf("Nightscout data fetched: mg/dL %d %s\n", currentGlucose, currentDirection.c_str());
} else {
Serial.println("Failed to parse Nightscout JSON");
}
} else {
Serial.printf("[HTTPS] GET failed, error: %s\n", https.errorToString(httpCode).c_str());
}
https.end();
lastNightscoutFetchTime = millis(); // Update the timestamp
}
// Display the data we have, which is now stored in static variables
if (currentGlucose != -1) {
char arrow;
if (currentDirection == "Flat") arrow = 139;
else if (currentDirection == "SingleUp") arrow = 134;
else if (currentDirection == "DoubleUp") arrow = 135;
else if (currentDirection == "SingleDown") arrow = 136;
else if (currentDirection == "DoubleDown") arrow = 137;
else if (currentDirection == "FortyFiveUp") arrow = 138;
else if (currentDirection == "FortyFiveDown") arrow = 140;
else arrow = '?';
String displayText = String(currentGlucose) + String(arrow);
// Keep alignment reset just in case
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(1);
P.print(displayText.c_str());
delay(weatherDuration);
advanceDisplayMode();
yield();
return;
} else {
// If no data is available after the first fetch attempt, show an error and advance
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(0);
P.print(F("?)"));
delay(2000); // Wait 2 seconds before advancing
advanceDisplayMode();
return;
}
}
} // End of if (displayMode == 3 && ...)
//DATE Display Mode
else if (displayMode == 5 && showDate) {
// --- VALID DATE CHECK ---
if (timeinfo.tm_year < 120 || timeinfo.tm_mday <= 0 || timeinfo.tm_mon < 0 || timeinfo.tm_mon > 11) {
advanceDisplayMode();
return; // skip drawing
}
// -------------------------
String dateString;
// --- NIGHTSCOUT Display Mode ---
if (displayMode == 4) {
String ntpField = String(ntpServer2);
// Get localized month names
const char *const *months = getMonthsOfYear(language);
String monthAbbr = String(months[timeinfo.tm_mon]).substring(0, 5);
monthAbbr.toLowerCase();
// These static variables will retain their values between calls to this block
static unsigned long lastNightscoutFetchTime = 0;
const unsigned long NIGHTSCOUT_FETCH_INTERVAL = 150000; // 2.5 minutes
static int currentGlucose = -1;
static String currentDirection = "?";
// Add spaces between day digits
String dayString = String(timeinfo.tm_mday);
String spacedDay = "";
for (size_t i = 0; i < dayString.length(); i++) {
spacedDay += dayString[i];
if (i < dayString.length() - 1) spacedDay += " ";
// Check if it's time to fetch new data or if we have no data yet
if (currentGlucose == -1 || millis() - lastNightscoutFetchTime >= NIGHTSCOUT_FETCH_INTERVAL) {
WiFiClientSecure client;
client.setInsecure();
HTTPClient https;
https.begin(client, ntpField);
https.setTimeout(5000); // This sets both the connection and response timeout.
Serial.print("[HTTPS] Nightscout fetch initiated...\n");
int httpCode = https.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = https.getString();
StaticJsonDocument<1024> doc;
DeserializationError error = deserializeJson(doc, payload);
if (!error && doc.is<JsonArray>() && doc.size() > 0) {
JsonObject firstReading = doc[0].as<JsonObject>();
currentGlucose = firstReading["glucose"] | firstReading["sgv"] | -1;
currentDirection = firstReading["direction"] | "?";
Serial.printf("Nightscout data fetched: mg/dL %d %s\n", currentGlucose, currentDirection.c_str());
} else {
Serial.println("Failed to parse Nightscout JSON");
}
} else {
Serial.printf("[HTTPS] GET failed, error: %s\n", https.errorToString(httpCode).c_str());
}
https.end();
lastNightscoutFetchTime = millis(); // Update the timestamp
}
// Display the data we have, which is now stored in static variables
if (currentGlucose != -1) {
char arrow;
if (currentDirection == "Flat") arrow = 139;
else if (currentDirection == "SingleUp") arrow = 134;
else if (currentDirection == "DoubleUp") arrow = 135;
else if (currentDirection == "SingleDown") arrow = 136;
else if (currentDirection == "DoubleDown") arrow = 137;
else if (currentDirection == "FortyFiveUp") arrow = 138;
else if (currentDirection == "FortyFiveDown") arrow = 140;
else arrow = '?';
String displayText = String(currentGlucose) + String(arrow);
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(1);
P.print(displayText.c_str());
delay(weatherDuration);
advanceDisplayMode();
return;
} else {
// If no data is available after the first fetch attempt, show an error and advance
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(0);
P.print(F("?)"));
delay(2000); // Wait 2 seconds before advancing
advanceDisplayMode();
return;
}
}
// Function to check if day should come first for given language
auto isDayFirst = [](const String &lang) {
// Languages with DD-MM order
const char *dayFirstLangs[] = {
"af", // Afrikaans
"cs", // Czech
"da", // Danish
"de", // German
"eo", // Esperanto
"es", // Spanish
"et", // Estonian
"fi", // Finnish
"fr", // French
"hr", // Croatian
"hu", // Hungarian
"it", // Italian
"lt", // Lithuanian
"lv", // Latvian
"nl", // Dutch
"no", // Norwegian
"pl", // Polish
"pt", // Portuguese
"ro", // Romanian
"sk", // Slovak
"sl", // Slovenian
"sr", // Serbian
"sv", // Swedish
"sw", // Swahili
"tr" // Turkish
//DATE Display Mode
else if (displayMode == 5 && showDate) {
// --- VALID DATE CHECK ---
if (timeinfo.tm_year < 120 || timeinfo.tm_mday <= 0 || timeinfo.tm_mon < 0 || timeinfo.tm_mon > 11) {
advanceDisplayMode();
return; // skip drawing
}
// -------------------------
String dateString;
// Get localized month names
const char *const *months = getMonthsOfYear(language);
String monthAbbr = String(months[timeinfo.tm_mon]).substring(0, 5);
monthAbbr.toLowerCase();
// Add spaces between day digits
String dayString = String(timeinfo.tm_mday);
String spacedDay = "";
for (size_t i = 0; i < dayString.length(); i++) {
spacedDay += dayString[i];
if (i < dayString.length() - 1) spacedDay += " ";
}
// Function to check if day should come first for given language
auto isDayFirst = [](const String &lang) {
// Languages with DD-MM order
const char *dayFirstLangs[] = {
"af", // Afrikaans
"cs", // Czech
"da", // Danish
"de", // German
"eo", // Esperanto
"es", // Spanish
"et", // Estonian
"fi", // Finnish
"fr", // French
"ga", // Irish
"hr", // Croatian
"hu", // Hungarian
"it", // Italian
"lt", // Lithuanian
"lv", // Latvian
"nl", // Dutch
"no", // Norwegian
"pl", // Polish
"pt", // Portuguese
"ro", // Romanian
"sk", // Slovak
"sl", // Slovenian
"sr", // Serbian
"sv", // Swedish
"sw", // Swahili
"tr" // Turkish
};
for (auto lf : dayFirstLangs) {
if (lang.equalsIgnoreCase(lf)) {
return true;
}
}
return false;
};
for (auto lf : dayFirstLangs) {
if (lang.equalsIgnoreCase(lf)) {
return true;
String langForDate = String(language);
if (langForDate == "ja") {
// Japanese: month number (spaced digits) + day + symbol
String spacedMonth = "";
String monthNum = String(timeinfo.tm_mon + 1);
dateString = monthAbbr + " " + spacedDay + " ±";
} else {
if (isDayFirst(language)) {
dateString = spacedDay + " " + monthAbbr;
} else {
dateString = monthAbbr + " " + spacedDay;
}
}
return false;
};
String langForDate = String(language);
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(0);
P.print(dateString);
if (langForDate == "ja") {
// Japanese: month number (spaced digits) + day + symbol
String spacedMonth = "";
String monthNum = String(timeinfo.tm_mon + 1);
dateString = monthAbbr + " " + spacedDay + " ±";
} else {
if (isDayFirst(language)) {
dateString = spacedDay + " " + monthAbbr;
} else {
dateString = monthAbbr + " " + spacedDay;
if (millis() - lastSwitch > weatherDuration) {
advanceDisplayMode();
}
}
P.setTextAlignment(PA_CENTER);
P.setCharSpacing(0);
P.print(dateString);
if (millis() - lastSwitch > weatherDuration) {
advanceDisplayMode();
}
}
yield();
yield();
}

View File

@@ -447,6 +447,7 @@ textarea::placeholder {
<option value="de">German</option>
<option value="hu">Hungarian</option>
<option value="it">Italian</option>
<option value="ga">Irish</option>
<option value="ja">Japanese</option>
<option value="lv">Latvian</option>
<option value="lt">Lithuanian</option>

View File

@@ -17,6 +17,7 @@ const DaysOfWeekMapping days_mappings[] = {
{ "et", { "p&a", "e&s", "t&e", "k&o", "n&e", "r&e", "l&a" } },
{ "fi", { "s&u&n", "m&a&a", "t&i&s", "k&e&s", "t&o&r", "p&e&r", "l&a&u" } },
{ "fr", { "d&i&m", "l&u&n", "m&a&r", "m&e&r", "j&e&u", "v&e&n", "s&a&m" } },
{ "ga", { "d&o&m", "l&u&a", "m&a&i", "c&e&a", "d&e&a", "a&o&i", "s&a&t" } },
{ "hr", { "n&e&d", "p&o&n", "u&t&o", "s&r&i", "c&e&t", "p&e&t", "s&u&b" } },
{ "hu", { "v&a&s", "h&e&t", "k&e&d", "s&z&e", "c&s&u", "p&e&t", "s&z&o" } },
{ "it", { "d&o&m", "l&u&n", "m&a&r", "m&e&r", "g&i&o", "v&e&n", "s&a&b" } },

View File

@@ -43,7 +43,7 @@ MD_MAX72XX::fontType_t mFactory[] PROGMEM =
6, 66, 37, 18, 72, 164, 66, // 37 - '%'
1, 1, // 38 - '&'
1, 6, // 39 - ''
1, 0, // 40 - '('
13, 254, 17, 17, 254, 0, 126, 129, 65, 190, 0, 129, 255, 129, // 40 - '('
17, 130, 186, 198, 254, 134, 234, 134, 254, 250, 130, 250, 254, 134, 234, 134, 254, 124, // 41 - ')'
20, 250, 130, 250, 254, 130, 170, 186, 254, 130, 250, 226, 250, 134, 254, 130, 234, 234, 246, 254, 124, // 42 - '*'
1, 0, // 43 - '+'

View File

@@ -20,6 +20,7 @@ const MonthsMapping months_mappings[] = {
{ "hr", { "s&i&j", "v&e&l", "o&z&u", "t&r&a", "s&v&i", "l&i&p", "s&r&p", "k&o&l", "r&u&j", "l&i&s", "s&t&u", "p&r&o" } }, // Croatian
{ "hu", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&j", "j&u&n", "j&u&l", "a&u&g", "s&z&e", "o&k&t", "n&o&v", "d&e&c" } }, // Hungarian
{ "it", { "g&e&n", "f&e&b", "m&a&r", "a&p&r", "m&a&g", "g&i&u", "l&u&g", "a&g&o", "s&e&t", "o&t&t", "n&o&v", "d&i&c" } }, // Italian
{ "ga", { "e&a&n", "f&e&a", "m&a&r", "a&i&b", "b&e&a", "m&e&i", "i&u&i", "l&u&n", "m&e&a", "d&e&i", "s&a&m", "n&o&l" } }, // Irish
{ "ja", { "1 ²", "2 ²", "3 ²", "4 ²", "5 ²", "6 ²", "7 ²", "8 ²", "9 ²", "10 ²", "11 ²", "12 ²" } }, // Japanese
{ "lt", { "s&a&u", "v&a&s", "k&o&v", "b&a&l", "g&e&g", "b&i&r", "l&i&e", "r&u&g", "s&w&e", "s&p&a", "l&a&p", "g&r&u" } }, // Lithuanian
{ "lv", { "j&a&n", "f&e&b", "m&a&r", "a&p&r", "m&a&i", "j&u&n", "j&u&l", "a&u&g", "s&e&p", "o&k&t", "n&o&v", "d&e&c" } }, // Latvian