Add i18n dictionary and dynamic UI translations

This commit is contained in:
Richard Zink committed 2026-10-07 12:51:02 +02:00
1 parent f726b76b7d
commit d546124359
3 files changed
+89 -17

No files matched your search

+1
View File
@@ -4,6 +4,7 @@ COPY ash.css /usr/share/nginx/html/ash.css
COPY gruvbox.css /usr/share/nginx/html/gruvbox.css COPY gruvbox.css /usr/share/nginx/html/gruvbox.css
COPY nord.css /usr/share/nginx/html/nord.css COPY nord.css /usr/share/nginx/html/nord.css
COPY config.js /usr/share/nginx/html/config.js COPY config.js /usr/share/nginx/html/config.js
COPY i18n.js /usr/share/nginx/html/i18n.js
COPY index.html /usr/share/nginx/html/index.html COPY index.html /usr/share/nginx/html/index.html
COPY noise.png /usr/share/nginx/html/noise.png COPY noise.png /usr/share/nginx/html/noise.png
EXPOSE 58881 EXPOSE 58881
+49
View File
@@ -0,0 +1,49 @@
/* Internationalization dictionary
*
* Each key maps to an array of translations indexed by language code.
* To add a new language, add an entry with the language code as the key
* and translations for each existing key.
*
* Example:
* TRANSLATIONS.title = { de: 'Titel', en: 'Title' };
*
* The current language is taken from window.CONFIG.language.
*/
window.TRANSLATIONS = {
'Aufgang': { de: 'Aufgang', en: 'Sunrise' },
'Untergang': { de: 'Untergang', en: 'Sunset' },
'Zenit Sonne': { de: 'Zenit Sonne', en: 'Sun Zenith' },
'Mondphase': { de: 'Mondphase', en: 'Moon Phase' },
'Mond auf': { de: 'Mond auf', en: 'Moonrise' },
'Mond unter': { de: 'Mond unter', en: 'Moonset' },
'heute': { de: 'heute', en: 'today' },
'morgen': { de: 'morgen', en: 'tomorrow' },
'Neumond': { de: 'Neumond', en: 'New Moon' },
'Zunehm. Sichel': { de: 'Zunehm. Sichel', en: 'Waxing Crescent' },
'Erstes Viertel': { de: 'Erstes Viertel', en: 'First Quarter' },
'Zunehm. Gibbous': { de: 'Zunehm. Gibbous', en: 'Waxing Gibbous' },
'Vollmond': { de: 'Vollmond', en: 'Full Moon' },
'Abnehm. Gibbous': { de: 'Abnehm. Gibbous', en: 'Waning Gibbous' },
'Letztes Viertel': { de: 'Letztes Viertel', en: 'Last Quarter' },
'Abnehm. Sichel': { de: 'Abnehm. Sichel', en: 'Waning Crescent' },
'Mo.': { de: 'Mo.', en: 'Mo.' },
'Di.': { de: 'Di.', en: 'Tu.' },
'Mi.': { de: 'Mi.', en: 'We.' },
'Do.': { de: 'Do.', en: 'Th.' },
'Fr.': { de: 'Fr.', en: 'Fr.' },
'Sa.': { de: 'Sa.', en: 'Sa.' },
'So.': { de: 'So.', en: 'Su.' }
};
window.getTranslation = function(key) {
var lang = (window.CONFIG && window.CONFIG.language) || 'de';
var entry = window.TRANSLATIONS[key];
if (!entry) return key;
return entry[lang] || entry.de || key;
};
window.t = window.getTranslation;
+39 -17
View File
@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vanta Clouds</title> <title>Vanta Clouds</title>
<script src="config.js"></script> <script src="config.js"></script>
<script src="i18n.js"></script>
<script> <script>
(function() { (function() {
var theme = (window.CONFIG && window.CONFIG.theme) || 'ash'; var theme = (window.CONFIG && window.CONFIG.theme) || 'ash';
@@ -411,37 +412,37 @@
<div class="info-icon"> <div class="info-icon">
<svg viewBox="0 0 24 24" fill="var(--orange)"><circle cx="12" cy="12" r="4"/><g stroke="var(--orange)" stroke-width="2" stroke-linecap="round"><line x1="12" y1="2" x2="12" y2="5"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="2" y1="12" x2="5" y2="12"/><line x1="19" y1="12" x2="22" y2="12"/><line x1="4.9" y1="4.9" x2="6.9" y2="6.9"/><line x1="17.1" y1="17.1" x2="19.1" y2="19.1"/><line x1="4.9" y1="19.1" x2="6.9" y2="17.1"/><line x1="17.1" y1="6.9" x2="19.1" y2="4.9"/></g></svg> <svg viewBox="0 0 24 24" fill="var(--orange)"><circle cx="12" cy="12" r="4"/><g stroke="var(--orange)" stroke-width="2" stroke-linecap="round"><line x1="12" y1="2" x2="12" y2="5"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="2" y1="12" x2="5" y2="12"/><line x1="19" y1="12" x2="22" y2="12"/><line x1="4.9" y1="4.9" x2="6.9" y2="6.9"/><line x1="17.1" y1="17.1" x2="19.1" y2="19.1"/><line x1="4.9" y1="19.1" x2="6.9" y2="17.1"/><line x1="17.1" y1="6.9" x2="19.1" y2="4.9"/></g></svg>
</div> </div>
<div class="info-content"><span class="info-label">Aufgang</span><span><span class="info-value" id="sunrise-value">--:--</span><span class="info-sub" id="sunrise-when"></span></span></div> <div class="info-content"><span class="info-label" data-i18n="Aufgang">Aufgang</span><span><span class="info-value" id="sunrise-value">--:--</span><span class="info-sub" id="sunrise-when"></span></span></div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="info-icon"> <div class="info-icon">
<svg viewBox="0 0 24 24" fill="var(--orange)"><circle cx="12" cy="12" r="4"/><line x1="12" y1="12" x2="12" y2="3" stroke="var(--orange)" stroke-width="2"/></svg> <svg viewBox="0 0 24 24" fill="var(--orange)"><circle cx="12" cy="12" r="4"/><line x1="12" y1="12" x2="12" y2="3" stroke="var(--orange)" stroke-width="2"/></svg>
</div> </div>
<div class="info-content"><span class="info-label">Zenit Sonne</span><span><span class="info-value" id="sun-zenith-value">--:--</span><span class="info-sub" id="sun-zenith-when"></span></span></div> <div class="info-content"><span class="info-label" data-i18n="Zenit Sonne">Zenit Sonne</span><span><span class="info-value" id="sun-zenith-value">--:--</span><span class="info-sub" id="sun-zenith-when"></span></span></div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="info-icon"> <div class="info-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="var(--orange)" stroke-width="2"><path d="M3 12 A9 9 0 0 1 21 12 Z"/></svg> <svg viewBox="0 0 24 24" fill="none" stroke="var(--orange)" stroke-width="2"><path d="M3 12 A9 9 0 0 1 21 12 Z"/></svg>
</div> </div>
<div class="info-content"><span class="info-label">Untergang</span><span><span class="info-value" id="sunset-value">--:--</span><span class="info-sub" id="sunset-when"></span></span></div> <div class="info-content"><span class="info-label" data-i18n="Untergang">Untergang</span><span><span class="info-value" id="sunset-value">--:--</span><span class="info-sub" id="sunset-when"></span></span></div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="info-icon"> <div class="info-icon">
<svg id="moon-phase-icon" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="var(--dark)"/></svg> <svg id="moon-phase-icon" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="var(--dark)"/></svg>
</div> </div>
<div class="info-content"><span class="info-label">Mondphase</span><span class="info-value" id="moon-phase-text">--</span></div> <div class="info-content"><span class="info-label" data-i18n="Mondphase">Mondphase</span><span class="info-value" id="moon-phase-text">--</span></div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="info-icon"> <div class="info-icon">
<svg viewBox="0 0 24 24" fill="var(--icon)"><path d="M12 2 a10 10 0 1 0 0 20 a8 8 0 1 1 0 -16 a6 6 0 1 1 0 8"/></svg> <svg viewBox="0 0 24 24" fill="var(--icon)"><path d="M12 2 a10 10 0 1 0 0 20 a8 8 0 1 1 0 -16 a6 6 0 1 1 0 8"/></svg>
</div> </div>
<div class="info-content"><span class="info-label">Mond auf</span><span><span class="info-value" id="moonrise-value">--:--</span><span class="info-sub" id="moonrise-when"></span></span></div> <div class="info-content"><span class="info-label" data-i18n="Mond auf">Mond auf</span><span><span class="info-value" id="moonrise-value">--:--</span><span class="info-sub" id="moonrise-when"></span></span></div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="info-icon"> <div class="info-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="var(--icon)" stroke-width="2"><path d="M3 12 A9 9 0 0 1 21 22 Z"/></svg> <svg viewBox="0 0 24 24" fill="none" stroke="var(--icon)" stroke-width="2"><path d="M3 12 A9 9 0 0 1 21 22 Z"/></svg>
</div> </div>
<div class="info-content"><span class="info-label">Mond unter</span><span><span class="info-value" id="moonset-value">--:--</span><span class="info-sub" id="moonset-when"></span></span></div> <div class="info-content"><span class="info-label" data-i18n="Mond unter">Mond unter</span><span><span class="info-value" id="moonset-value">--:--</span><span class="info-sub" id="moonset-when"></span></span></div>
</div> </div>
<div id="weather-divider" class="weather-divider"></div> <div id="weather-divider" class="weather-divider"></div>
<div id="weather-current" class="weather-current"></div> <div id="weather-current" class="weather-current"></div>
@@ -833,7 +834,7 @@ var effect = (VANTA.CLOUDS2 || VANTA.CLOUDS)({
var times = type.indexOf('moon') === 0 ? SunCalc.getMoonTimes(nextDate, lat, lon) : SunCalc.getTimes(nextDate, lat, lon); var times = type.indexOf('moon') === 0 ? SunCalc.getMoonTimes(nextDate, lat, lon) : SunCalc.getTimes(nextDate, lat, lon);
var ev = type === 'moonrise' ? times.rise : type === 'moonset' ? times.set : times[type]; var ev = type === 'moonrise' ? times.rise : type === 'moonset' ? times.set : times[type];
if (ev && !isNaN(ev.getTime())) { if (ev && !isNaN(ev.getTime())) {
var dayLabel = d === 1 ? 'morgen' : formatDateLong(nextDate); var dayLabel = d === 1 ? t('morgen') : formatDateLong(nextDate);
return { date: ev, label: dayLabel }; return { date: ev, label: dayLabel };
} }
} }
@@ -842,14 +843,18 @@ var effect = (VANTA.CLOUDS2 || VANTA.CLOUDS)({
function getMoonPhaseText(phase) { function getMoonPhaseText(phase) {
var p = phase; var p = phase;
if (p < 0.0625 || p >= 0.9375) return 'Neumond'; var keys = ['Neumond', 'Zunehm. Sichel', 'Erstes Viertel', 'Zunehm. Gibbous',
if (p < 0.1875) return 'Zunehm. Sichel'; 'Vollmond', 'Abnehm. Gibbous', 'Letztes Viertel', 'Abnehm. Sichel'];
if (p < 0.3125) return 'Erstes Viertel'; var idx;
if (p < 0.4375) return 'Zunehm. Gibbous'; if (p < 0.0625 || p >= 0.9375) idx = 0;
if (p < 0.5625) return 'Vollmond'; else if (p < 0.1875) idx = 1;
if (p < 0.6875) return 'Abnehm. Gibbous'; else if (p < 0.3125) idx = 2;
if (p < 0.8125) return 'Letztes Viertel'; else if (p < 0.4375) idx = 3;
return 'Abnehm. Sichel'; else if (p < 0.5625) idx = 4;
else if (p < 0.6875) idx = 5;
else if (p < 0.8125) idx = 6;
else idx = 7;
return t(keys[idx]);
} }
var CENTER_X = 140; var CENTER_X = 140;
@@ -1276,6 +1281,21 @@ window.weatherThemes = {
{ value: 'en', label: 'EN' } { value: 'en', label: 'EN' }
]; ];
function applyI18n() {
var els = document.querySelectorAll('[data-i18n]');
for (var i = 0; i < els.length; i++) {
var key = els[i].getAttribute('data-i18n');
els[i].textContent = t(key);
}
}
function setDebugLanguage(lang) {
window.CONFIG.language = lang;
applyI18n();
// Trigger weather refresh to update day labels
if (typeof fetchWeather === 'function') fetchWeather();
}
function populateDebugSelects() { function populateDebugSelects() {
// Theme select (from window.THEMES) // Theme select (from window.THEMES)
var themeSelect = document.getElementById('dbg-theme-select'); var themeSelect = document.getElementById('dbg-theme-select');
@@ -1579,9 +1599,10 @@ window.weatherThemes = {
var dayNames = ['Heute', 'Morgen']; var dayNames = ['Heute', 'Morgen'];
for (var d = 0; d < 4; d++) { for (var d = 0; d < 4; d++) {
var dayDate = new Date(daily.time[d]); var dayDate = new Date(daily.time[d]);
var dayLabel = d === 0 ? 'Heute' : dayNames[1] || dayDate.toLocaleDateString('de-DE', { weekday: 'short' }); var dayLabel = d === 0 ? t('heute') : t('morgen');
if (d > 1) { if (d > 1) {
dayLabel = dayDate.toLocaleDateString('de-DE', { weekday: 'short' }) + ' ' + var weekdayKey = ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'][dayDate.getDay()];
dayLabel = t(weekdayKey) + ' ' +
(dayDate.getDate() < 10 ? '0' : '') + dayDate.getDate() + '.' + (dayDate.getDate() < 10 ? '0' : '') + dayDate.getDate() + '.' +
(dayDate.getMonth() + 1 < 10 ? '0' : '') + (dayDate.getMonth() + 1) + '.'; (dayDate.getMonth() + 1 < 10 ? '0' : '') + (dayDate.getMonth() + 1) + '.';
} }
@@ -1620,6 +1641,7 @@ window.weatherThemes = {
} }
// Update weather every 15 minutes // Update weather every 15 minutes
applyI18n();
populateDebugSelects(); populateDebugSelects();
animate(); animate();
tick(); tick();