From acc187d0726bb403a64924cb25a5657138e15d7f Mon Sep 17 00:00:00 2001 From: nutty Date: Sat, 29 Aug 2026 12:11:04 +1000 Subject: [PATCH] =?UTF-8?q?=E2=80=A2=20Added=20language=20support=20?= =?UTF-8?q?=E2=80=A2=20Added=20timezone=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .common/core/settings-core/script.js | 44 +++++++++++++++++++++- clocko/script.js | 55 ++++++++++++++++++---------- clocko/settings/settings.json | 49 +++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 20 deletions(-) diff --git a/.common/core/settings-core/script.js b/.common/core/settings-core/script.js index f0f0e8c..ab4af18 100644 --- a/.common/core/settings-core/script.js +++ b/.common/core/settings-core/script.js @@ -188,6 +188,21 @@ function LoadJSON(settingsJson) { await PopulateFontDatalist(); }, { once: true }); break; + case 'timezone': + inputElement = document.createElement('input'); + inputElement.type = 'text'; + inputElement.placeholder = 'Type to search timezone...'; + inputElement.id = setting.id; + inputElement.value = settingsMap.has(setting.id) ? settingsMap.get(setting.id) : setting.defaultValue; + inputElement.setAttribute('list', 'timezones'); + inputElement.autocomplete = 'off'; + + // Populate datalist on focus (runs once) + inputElement.addEventListener('focus', function loadOnce() { + inputElement.removeEventListener('focus', loadOnce); + PopulateTimezoneDatalist(); + }, { once: true }); + break; case 'button': inputElement = document.createElement('button'); inputElement.id = setting.id; //Added setting ID @@ -431,6 +446,30 @@ async function PopulateFontDatalist() { } } +function PopulateTimezoneDatalist() { + try { + // Fetch all official IANA timezones natively from the browser + const timezones = Intl.supportedValuesOf('timeZone'); + + // Create the datalist element + const datalistElement = document.createElement('datalist'); + datalistElement.id = 'timezones'; + + // Append each timezone as an option + timezones.forEach(tz => { + const option = document.createElement('option'); + option.value = tz; + datalistElement.appendChild(option); + }); + + document.body.appendChild(datalistElement); + console.debug(`Loaded ${timezones.length} timezones into auto-suggest.`); + + } catch (err) { + console.error("Error generating timezone suggestions:", err); + } +} + ///////////////////////// @@ -599,4 +638,7 @@ LoadSettingsFromStorage(); LoadJSON(settingsJson); // Populate local fonts for auto-suggest -PopulateFontDatalist(); \ No newline at end of file +PopulateFontDatalist(); + +// Populate timezones for auto-suggest +PopulateTimezoneDatalist(); diff --git a/clocko/script.js b/clocko/script.js index 81bbd89..ae77c2a 100644 --- a/clocko/script.js +++ b/clocko/script.js @@ -27,6 +27,8 @@ const line3 = document.getElementById('line3'); ///////////// const font = urlParams.get("font") || ""; +const language = urlParams.get("language") || "en"; +const timezone = urlParams.get("timezone") || ""; const enableLine1 = GetBooleanParam("enableLine1", true); const line1Format = urlParams.get("line1Format") || "hh:mm:ss A"; @@ -63,7 +65,7 @@ const line3TextAlignment = urlParams.get("line3TextAlignment") || "center"; // Set the font for the entire page if specified if (font) - document.body.style.fontFamily = `'${font}'`; + document.body.style.fontFamily = `'${font}'`; // Hide lines that are not enabled if (!enableLine1) @@ -75,38 +77,53 @@ if (!enableLine3) -//////////// +//////////////// // CLOCKO // -//////////// +//////////////// -// Check if any active format string includes milliseconds (e.g., 'S', 'SS', 'SSS') -const usesMilliseconds = - (enableLine1 && line1Format.includes('S')) || - (enableLine2 && line2Format.includes('S')) || - (enableLine3 && line3Format.includes('S')); +// Helper function to start the clock loop +function StartClock() { + // Check if any active format string includes milliseconds (e.g., 'S', 'SS', 'SSS') + const usesMilliseconds = + (enableLine1 && line1Format.includes('S')) || + (enableLine2 && line2Format.includes('S')) || + (enableLine3 && line3Format.includes('S')); -UpdateTime(); + UpdateTime(); -if (usesMilliseconds) { - // High-frequency updates for millisecond precision - function updateFrame() { - UpdateTime(); + if (usesMilliseconds) { + function updateFrame() { + UpdateTime(); + requestAnimationFrame(updateFrame); + } requestAnimationFrame(updateFrame); + } else { + setInterval(UpdateTime, 1000); } - requestAnimationFrame(updateFrame); -} else { - // Efficient 1-second interval for standard clocks - setInterval(UpdateTime, 1000); } function UpdateTime() { - const now = dayjs().tz(dayjs.tz.guess()); - + const activeTimezone = timezone ? timezone : dayjs.tz.guess(); + const now = dayjs().tz(activeTimezone); + if (enableLine1) line1.textContent = now.format(line1Format); if (enableLine2) line2.textContent = now.format(line2Format); if (enableLine3) line3.textContent = now.format(line3Format); } +// Set the language and start the clock only after it's loaded +const script = document.createElement('script'); +script.src = `https://cdn.jsdelivr.net/npm/dayjs@1/locale/${language}.js`; +script.onload = () => { + dayjs.locale(language); + StartClock(); +}; +script.onerror = () => { + console.warn(`Locale '${language}' failed to load. Falling back to English.`); + dayjs.locale('en'); + StartClock(); +}; +document.head.appendChild(script); ///////////// diff --git a/clocko/settings/settings.json b/clocko/settings/settings.json index de49bb5..d47db6e 100644 --- a/clocko/settings/settings.json +++ b/clocko/settings/settings.json @@ -1,5 +1,54 @@ { "settings": [ + { + "id": "language", + "label": "Language", + "description": "", + "type": "select", + "options": [ + { "value": "en", "label": "English" }, + { "value": "ar", "label": "العربية (Arabic)" }, + { "value": "bg", "label": "Български (Bulgarian)" }, + { "value": "zh-cn", "label": "中文 (Chinese Simplified)" }, + { "value": "zh-tw", "label": "中文 (Chinese Traditional)" }, + { "value": "cs", "label": "Čeština (Czech)" }, + { "value": "da", "label": "Dansk (Danish)" }, + { "value": "nl", "label": "Nederlands (Dutch)" }, + { "value": "fi", "label": "Suomi (Finnish)" }, + { "value": "fr", "label": "Français (French)" }, + { "value": "de", "label": "Deutsch (German)" }, + { "value": "el", "label": "Ελληνικά (Greek)" }, + { "value": "he", "label": "עברית (Hebrew)" }, + { "value": "hi", "label": "हिन्दी (Hindi)" }, + { "value": "hu", "label": "Magyar (Hungarian)" }, + { "value": "id", "label": "Bahasa Indonesia (Indonesian)" }, + { "value": "it", "label": "Italiano (Italian)" }, + { "value": "ja", "label": "日本語 (Japanese)" }, + { "value": "ko", "label": "한국어 (Korean)" }, + { "value": "nb", "label": "Norsk bokmål (Norwegian Bokmål)" }, + { "value": "pl", "label": "Polski (Polish)" }, + { "value": "pt", "label": "Português (Portuguese)" }, + { "value": "ro", "label": "Română (Romanian)" }, + { "value": "ru", "label": "Русский (Russian)" }, + { "value": "sk", "label": "Slovenčina (Slovak)" }, + { "value": "es", "label": "Español (Spanish)" }, + { "value": "sv", "label": "Svenska (Swedish)" }, + { "value": "th", "label": "ไทย (Thai)" }, + { "value": "tr", "label": "Türkçe (Turkish)" }, + { "value": "uk", "label": "Українська (Ukrainian)" }, + { "value": "vi", "label": "Tiếng Việt (Vietnamese)" } + ], + "defaultValue": "en", + "group": "General" + }, + { + "id": "timezone", + "label": "Timezone", + "description": "Enter an IANA timezone (e.g., America/New_York, Europe/London). Leave blank to use local time.", + "type": "timezone", + "defaultValue": "", + "group": "General" + }, { "id": "font", "label": "Font",