feat: enhance time formatting and localization support in Clock and Weather components

This commit is contained in:
alexsparkes
2026-01-24 22:05:31 +00:00
parent 4e08570599
commit 65d26da867
5 changed files with 108 additions and 37 deletions

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
import { convertTimezone } from 'utils/date'; import { convertTimezone } from 'utils/date';
import { formatPercentage } from 'utils/formatNumber'; import { formatPercentage, formatDigits } from 'utils/formatNumber';
import { AnalogClock } from './components/AnalogClock'; import { AnalogClock } from './components/AnalogClock';
import { VerticalClock } from './components/VerticalClock'; import { VerticalClock } from './components/VerticalClock';
import EventBus from 'utils/eventbus'; import EventBus from 'utils/eventbus';
@@ -50,21 +50,24 @@ const Clock = () => {
const zero = localStorage.getItem('zero'); const zero = localStorage.getItem('zero');
if (localStorage.getItem('seconds') === 'true') { if (localStorage.getItem('seconds') === 'true') {
sec = `:${('00' + now.getSeconds()).slice(-2)}`; const secs = ('00' + now.getSeconds()).slice(-2);
setFinalSeconds(`${('00' + now.getSeconds()).slice(-2)}`); sec = `:${formatDigits(secs)}`;
setFinalSeconds(formatDigits(secs));
} }
if (localStorage.getItem('timeformat') === 'twentyfourhour') { if (localStorage.getItem('timeformat') === 'twentyfourhour') {
if (zero === 'false') { if (zero === 'false') {
time = `${now.getHours()}:${('00' + now.getMinutes()).slice(-2)}:${sec}`; const hours = now.getHours();
setFinalHour(`${now.getHours()}`); const minutes = ('00' + now.getMinutes()).slice(-2);
setFinalMinute(`${('00' + now.getMinutes()).slice(-2)}`); time = `${formatDigits(hours)}:${formatDigits(minutes)}${sec}`;
setFinalHour(formatDigits(hours));
setFinalMinute(formatDigits(minutes));
} else { } else {
time = `${('00' + now.getHours()).slice(-2)}:${('00' + now.getMinutes()).slice( const hours = ('00' + now.getHours()).slice(-2);
-2, const minutes = ('00' + now.getMinutes()).slice(-2);
)}${sec}`; time = `${formatDigits(hours)}:${formatDigits(minutes)}${sec}`;
setFinalHour(`${('00' + now.getHours()).slice(-2)}`); setFinalHour(formatDigits(hours));
setFinalMinute(`${('00' + now.getMinutes()).slice(-2)}`); setFinalMinute(formatDigits(minutes));
} }
setTime(time); setTime(time);
@@ -80,13 +83,16 @@ const Clock = () => {
} }
if (zero === 'false') { if (zero === 'false') {
time = `${hours}:${('00' + now.getMinutes()).slice(-2)}${sec}`; const minutes = ('00' + now.getMinutes()).slice(-2);
setFinalHour(`${hours}`); time = `${formatDigits(hours)}:${formatDigits(minutes)}${sec}`;
setFinalMinute(`${('00' + now.getMinutes()).slice(-2)}`); setFinalHour(formatDigits(hours));
setFinalMinute(formatDigits(minutes));
} else { } else {
time = `${('00' + hours).slice(-2)}:${('00' + now.getMinutes()).slice(-2)}${sec}`; const paddedHours = ('00' + hours).slice(-2);
setFinalHour(`${('00' + hours).slice(-2)}`); const minutes = ('00' + now.getMinutes()).slice(-2);
setFinalMinute(`${('00' + now.getMinutes()).slice(-2)}`); time = `${formatDigits(paddedHours)}:${formatDigits(minutes)}${sec}`;
setFinalHour(formatDigits(paddedHours));
setFinalMinute(formatDigits(minutes));
} }
setTime(time); setTime(time);
@@ -137,11 +143,7 @@ const Clock = () => {
if (localStorage.getItem('timeType') === 'verticalClock') { if (localStorage.getItem('timeType') === 'verticalClock') {
return ( return (
<VerticalClock <VerticalClock finalHour={finalHour} finalMinute={finalMinute} finalSeconds={finalSeconds} />
finalHour={finalHour}
finalMinute={finalMinute}
finalSeconds={finalSeconds}
/>
); );
} }

View File

@@ -1,5 +1,6 @@
import variables from 'config/variables'; import variables from 'config/variables';
import { memo, useState, useEffect, useCallback } from 'react'; import { memo, useState, useEffect, useCallback } from 'react';
import { formatNumber } from 'utils/formatNumber';
import WeatherIcon from './components/WeatherIcon'; import WeatherIcon from './components/WeatherIcon';
import Expanded from './components/Expanded'; import Expanded from './components/Expanded';
@@ -70,12 +71,12 @@ const WeatherWidget = memo(() => {
<div className="iconAndTemps"> <div className="iconAndTemps">
<div className="weathericon"> <div className="weathericon">
<WeatherIcon name={weatherData.icon} /> <WeatherIcon name={weatherData.icon} />
<span>{`${weatherData.weather.temp}${weatherData.temp_text}`}</span> <span>{`${formatNumber(weatherData.weather.temp)}${weatherData.temp_text}`}</span>
</div> </div>
{weatherType >= 2 && ( {weatherType >= 2 && (
<span className="minmax"> <span className="minmax">
<span className="subtitle">{`${weatherData.weather.temp_min}${weatherData.temp_text}`}</span> <span className="subtitle">{`${formatNumber(weatherData.weather.temp_min)}${weatherData.temp_text}`}</span>
<span className="subtitle">{`${weatherData.weather.temp_max}${weatherData.temp_text}`}</span> <span className="subtitle">{`${formatNumber(weatherData.weather.temp_max)}${weatherData.temp_text}`}</span>
</span> </span>
)} )}
</div> </div>
@@ -83,7 +84,7 @@ const WeatherWidget = memo(() => {
<div className="extra-info"> <div className="extra-info">
<span> <span>
{variables.getMessage('widgets.weather.feels_like', { {variables.getMessage('widgets.weather.feels_like', {
amount: `${weatherData.weather.feels_like}${weatherData.temp_text}`, amount: `${formatNumber(weatherData.weather.feels_like)}${weatherData.temp_text}`,
})} })}
</span> </span>
<span className="loc">{location}</span> <span className="loc">{location}</span>

View File

@@ -9,6 +9,35 @@ const convertTemperature = (temp, format) => {
return Math.round(temp); 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) => { export const getWeather = async (location) => {
let cached = localStorage.getItem('currentWeather'); let cached = localStorage.getItem('currentWeather');
if (cached) { if (cached) {
@@ -40,15 +69,9 @@ export const getWeather = async (location) => {
const { temp, temp_min, temp_max, feels_like } = data.main; const { temp, temp_min, temp_max, feels_like } = data.main;
const tempFormat = localStorage.getItem('tempformat'); const tempFormat = localStorage.getItem('tempformat');
const tempSymbols = {
celsius: '°C',
kelvin: 'K',
fahrenheit: '°F',
};
const cacheable = { const cacheable = {
icon: data.weather[0].icon, icon: data.weather[0].icon,
temp_text: tempSymbols[tempFormat] || 'K', temp_text: getLocalizedTempSymbol(tempFormat),
weather: { weather: {
temp: convertTemperature(temp, tempFormat), temp: convertTemperature(temp, tempFormat),
description: data.weather[0].description, description: data.weather[0].description,

View File

@@ -237,7 +237,7 @@
}, },
{ {
"name": "localeFormatting", "name": "localeFormatting",
"value": false "value": true
}, },
{ {
"name": "offlineMode", "name": "offlineMode",

View File

@@ -1,8 +1,30 @@
/** /**
* Convert language code from Mue format (en_US) to locale format (en-US) * 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() { 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 * @returns {string} Formatted number string
*/ */
export function formatNumber(value, options = {}) { export function formatNumber(value, options = {}) {
if (localStorage.getItem('localeFormatting') !== 'true') { const localeFormatting = localStorage.getItem('localeFormatting');
if (localeFormatting === 'false' || localeFormatting === null) {
return String(value); return String(value);
} }
try { try {
@@ -28,7 +51,8 @@ export function formatNumber(value, options = {}) {
* @returns {string} Formatted percentage string * @returns {string} Formatted percentage string
*/ */
export function formatPercentage(value) { export function formatPercentage(value) {
if (localStorage.getItem('localeFormatting') !== 'true') { const localeFormatting = localStorage.getItem('localeFormatting');
if (localeFormatting === 'false') {
return Math.round(value * 100) + '%'; return Math.round(value * 100) + '%';
} }
try { try {
@@ -40,3 +64,24 @@ export function formatPercentage(value) {
return Math.round(value * 100) + '%'; 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);
}
}