From 65d26da86735872ec10335531b31598549f967dd Mon Sep 17 00:00:00 2001 From: alexsparkes Date: Sat, 24 Jan 2026 22:05:31 +0000 Subject: [PATCH] feat: enhance time formatting and localization support in Clock and Weather components --- src/features/time/Clock.jsx | 46 ++++++++++++----------- src/features/weather/Weather.jsx | 9 +++-- src/features/weather/api/getWeather.js | 37 +++++++++++++++---- src/utils/data/default_settings.json | 2 +- src/utils/formatNumber.js | 51 ++++++++++++++++++++++++-- 5 files changed, 108 insertions(+), 37 deletions(-) diff --git a/src/features/time/Clock.jsx b/src/features/time/Clock.jsx index 99a40bea..f334aeb1 100644 --- a/src/features/time/Clock.jsx +++ b/src/features/time/Clock.jsx @@ -1,7 +1,7 @@ import { useState, useEffect, useRef } from 'react'; import { convertTimezone } from 'utils/date'; -import { formatPercentage } from 'utils/formatNumber'; +import { formatPercentage, formatDigits } from 'utils/formatNumber'; import { AnalogClock } from './components/AnalogClock'; import { VerticalClock } from './components/VerticalClock'; import EventBus from 'utils/eventbus'; @@ -50,21 +50,24 @@ const Clock = () => { const zero = localStorage.getItem('zero'); if (localStorage.getItem('seconds') === 'true') { - sec = `:${('00' + now.getSeconds()).slice(-2)}`; - setFinalSeconds(`${('00' + now.getSeconds()).slice(-2)}`); + const secs = ('00' + now.getSeconds()).slice(-2); + sec = `:${formatDigits(secs)}`; + setFinalSeconds(formatDigits(secs)); } if (localStorage.getItem('timeformat') === 'twentyfourhour') { if (zero === 'false') { - time = `${now.getHours()}:${('00' + now.getMinutes()).slice(-2)}:${sec}`; - setFinalHour(`${now.getHours()}`); - setFinalMinute(`${('00' + now.getMinutes()).slice(-2)}`); + const hours = now.getHours(); + const minutes = ('00' + now.getMinutes()).slice(-2); + time = `${formatDigits(hours)}:${formatDigits(minutes)}${sec}`; + setFinalHour(formatDigits(hours)); + setFinalMinute(formatDigits(minutes)); } else { - time = `${('00' + now.getHours()).slice(-2)}:${('00' + now.getMinutes()).slice( - -2, - )}${sec}`; - setFinalHour(`${('00' + now.getHours()).slice(-2)}`); - setFinalMinute(`${('00' + now.getMinutes()).slice(-2)}`); + const hours = ('00' + now.getHours()).slice(-2); + const minutes = ('00' + now.getMinutes()).slice(-2); + time = `${formatDigits(hours)}:${formatDigits(minutes)}${sec}`; + setFinalHour(formatDigits(hours)); + setFinalMinute(formatDigits(minutes)); } setTime(time); @@ -80,13 +83,16 @@ const Clock = () => { } if (zero === 'false') { - time = `${hours}:${('00' + now.getMinutes()).slice(-2)}${sec}`; - setFinalHour(`${hours}`); - setFinalMinute(`${('00' + now.getMinutes()).slice(-2)}`); + const minutes = ('00' + now.getMinutes()).slice(-2); + time = `${formatDigits(hours)}:${formatDigits(minutes)}${sec}`; + setFinalHour(formatDigits(hours)); + setFinalMinute(formatDigits(minutes)); } else { - time = `${('00' + hours).slice(-2)}:${('00' + now.getMinutes()).slice(-2)}${sec}`; - setFinalHour(`${('00' + hours).slice(-2)}`); - setFinalMinute(`${('00' + now.getMinutes()).slice(-2)}`); + const paddedHours = ('00' + hours).slice(-2); + const minutes = ('00' + now.getMinutes()).slice(-2); + time = `${formatDigits(paddedHours)}:${formatDigits(minutes)}${sec}`; + setFinalHour(formatDigits(paddedHours)); + setFinalMinute(formatDigits(minutes)); } setTime(time); @@ -137,11 +143,7 @@ const Clock = () => { if (localStorage.getItem('timeType') === 'verticalClock') { return ( - + ); } diff --git a/src/features/weather/Weather.jsx b/src/features/weather/Weather.jsx index 8f0dfa62..e1515ba2 100644 --- a/src/features/weather/Weather.jsx +++ b/src/features/weather/Weather.jsx @@ -1,5 +1,6 @@ import variables from 'config/variables'; import { memo, useState, useEffect, useCallback } from 'react'; +import { formatNumber } from 'utils/formatNumber'; import WeatherIcon from './components/WeatherIcon'; import Expanded from './components/Expanded'; @@ -70,12 +71,12 @@ const WeatherWidget = memo(() => {
- {`${weatherData.weather.temp}${weatherData.temp_text}`} + {`${formatNumber(weatherData.weather.temp)}${weatherData.temp_text}`}
{weatherType >= 2 && ( - {`${weatherData.weather.temp_min}${weatherData.temp_text}`} - {`${weatherData.weather.temp_max}${weatherData.temp_text}`} + {`${formatNumber(weatherData.weather.temp_min)}${weatherData.temp_text}`} + {`${formatNumber(weatherData.weather.temp_max)}${weatherData.temp_text}`} )}
@@ -83,7 +84,7 @@ const WeatherWidget = memo(() => {
{variables.getMessage('widgets.weather.feels_like', { - amount: `${weatherData.weather.feels_like}${weatherData.temp_text}`, + amount: `${formatNumber(weatherData.weather.feels_like)}${weatherData.temp_text}`, })} {location} diff --git a/src/features/weather/api/getWeather.js b/src/features/weather/api/getWeather.js index ae0e67c8..86650948 100644 --- a/src/features/weather/api/getWeather.js +++ b/src/features/weather/api/getWeather.js @@ -9,6 +9,35 @@ const convertTemperature = (temp, format) => { return Math.round(temp); }; +const getLocalizedTempSymbol = (format) => { + const language = localStorage.getItem('language') || 'en_GB'; + const baseLang = language.split('_')[0]; + + // Temperature symbols for different languages + const localizedSymbols = { + ar: { + celsius: '°س', // Arabic: Celsius (سيلزيوس) + fahrenheit: '°ف', // Arabic: Fahrenheit (فهرنهايت) + kelvin: 'ك', // Arabic: Kelvin (كلفن) + }, + fa: { + celsius: '°س', // Persian: Celsius + fahrenheit: '°ف', // Persian: Fahrenheit + kelvin: 'ک', // Persian: Kelvin + }, + }; + + // Default Western symbols + const defaultSymbols = { + celsius: '°C', + fahrenheit: '°F', + kelvin: 'K', + }; + + // Return localized symbol if available, otherwise default + return localizedSymbols[baseLang]?.[format] || defaultSymbols[format] || 'K'; +}; + export const getWeather = async (location) => { let cached = localStorage.getItem('currentWeather'); if (cached) { @@ -40,15 +69,9 @@ export const getWeather = async (location) => { const { temp, temp_min, temp_max, feels_like } = data.main; const tempFormat = localStorage.getItem('tempformat'); - const tempSymbols = { - celsius: '°C', - kelvin: 'K', - fahrenheit: '°F', - }; - const cacheable = { icon: data.weather[0].icon, - temp_text: tempSymbols[tempFormat] || 'K', + temp_text: getLocalizedTempSymbol(tempFormat), weather: { temp: convertTemperature(temp, tempFormat), description: data.weather[0].description, diff --git a/src/utils/data/default_settings.json b/src/utils/data/default_settings.json index becaf659..e02ffb1f 100644 --- a/src/utils/data/default_settings.json +++ b/src/utils/data/default_settings.json @@ -237,7 +237,7 @@ }, { "name": "localeFormatting", - "value": false + "value": true }, { "name": "offlineMode", diff --git a/src/utils/formatNumber.js b/src/utils/formatNumber.js index 1312ed5c..22c3e262 100644 --- a/src/utils/formatNumber.js +++ b/src/utils/formatNumber.js @@ -1,8 +1,30 @@ /** * Convert language code from Mue format (en_US) to locale format (en-US) + * and add numbering system extensions for languages that use non-Latin numerals */ export function getLocaleCode() { - return localStorage.getItem('language')?.replace(/_/g, '-') || 'en-GB'; + const language = localStorage.getItem('language')?.replace(/_/g, '-') || 'en-GB'; + + // Map language codes to their native numbering systems + const numberingSystems = { + ar: 'arab', // Arabic - Eastern Arabic numerals (٠-٩) + fa: 'arabext', // Persian - Extended Arabic-Indic numerals (۰-۹) + bn: 'beng', // Bengali - Bengali numerals (০-৯) + hi: 'deva', // Hindi - Devanagari numerals (०-९) + mr: 'deva', // Marathi - Devanagari numerals + ne: 'deva', // Nepali - Devanagari numerals + ta: 'tamldec', // Tamil - Tamil numerals (௦-௯) + }; + + // Get the base language code (e.g., 'ar' from 'ar-EG') + const baseLang = language.split('-')[0]; + + // If this language has a native numbering system, append it + if (numberingSystems[baseLang]) { + return `${language}-u-nu-${numberingSystems[baseLang]}`; + } + + return language; } /** @@ -12,7 +34,8 @@ export function getLocaleCode() { * @returns {string} Formatted number string */ export function formatNumber(value, options = {}) { - if (localStorage.getItem('localeFormatting') !== 'true') { + const localeFormatting = localStorage.getItem('localeFormatting'); + if (localeFormatting === 'false' || localeFormatting === null) { return String(value); } try { @@ -28,7 +51,8 @@ export function formatNumber(value, options = {}) { * @returns {string} Formatted percentage string */ export function formatPercentage(value) { - if (localStorage.getItem('localeFormatting') !== 'true') { + const localeFormatting = localStorage.getItem('localeFormatting'); + if (localeFormatting === 'false') { return Math.round(value * 100) + '%'; } try { @@ -40,3 +64,24 @@ export function formatPercentage(value) { return Math.round(value * 100) + '%'; } } + +/** + * Format digits with locale-specific numerals (e.g., Eastern Arabic numerals for Arabic) + * @param {string|number} value - The value to format (e.g., '10', 23) + * @returns {string} Formatted string with locale-specific numerals + */ +export function formatDigits(value) { + const localeFormatting = localStorage.getItem('localeFormatting'); + if (localeFormatting === 'false') { + return String(value); + } + try { + const numValue = typeof value === 'string' ? parseInt(value, 10) : value; + if (isNaN(numValue)) return String(value); + return new Intl.NumberFormat(getLocaleCode(), { + useGrouping: false, + }).format(numValue); + } catch { + return String(value); + } +}