mirror of
https://github.com/mue/mue.git
synced 2026-06-08 14:10:42 +02:00
feat: enhance time formatting and localization support in Clock and Weather components
This commit is contained in:
@@ -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}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -237,7 +237,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "localeFormatting",
|
"name": "localeFormatting",
|
||||||
"value": false
|
"value": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "offlineMode",
|
"name": "offlineMode",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user