feat(translation): refactor translation system to use useT hook for reactive updates

This commit is contained in:
alexsparkes
2026-01-24 17:18:29 +00:00
parent b8647538fd
commit c88b3cc03c
15 changed files with 218 additions and 127 deletions

View File

@@ -1,5 +1,6 @@
import variables from 'config/variables';
import { useState, memo } from 'react';
import { useT } from 'contexts';
import Favourite from './Favourite';
import {
MdInfo,
@@ -54,6 +55,7 @@ const downloadImage = async (info) => {
};
function PhotoInformation({ info, url, api }) {
const t = useT();
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const [usePhotoMap, setPhotoMap] = useState(false);
@@ -64,7 +66,7 @@ function PhotoInformation({ info, url, api }) {
const [shareModal, openShareModal] = useState(false);
const [excludeModal, openExcludeModal] = useState(false);
const [favouriteTooltipText, setFavouriteTooltipText] = useState(
variables.getMessage('widgets.quote.favourite'),
t('widgets.quote.favourite'),
);
if (info.hidden === true || !info.credit) {
@@ -72,7 +74,7 @@ function PhotoInformation({ info, url, api }) {
}
let credit = info.credit;
let photo = variables.getMessage('widgets.background.credit');
let photo = t('widgets.background.credit');
// unsplash credit
if (info.photographerURL && info.photographerURL !== '' && !info.offline && api) {
@@ -142,31 +144,31 @@ function PhotoInformation({ info, url, api }) {
return (
<div className="extra-content">
{info.location && info.location !== 'N/A' ? (
<div className="row" title={variables.getMessage('widgets.background.location')}>
<div className="row" title={t('widgets.background.location')}>
<MdLocationOn />
<span id="infoLocation">{info.location}</span>
</div>
) : null}
{info.camera && info.camera !== 'N/A' ? (
<div className="row" title={variables.getMessage('widgets.background.camera')}>
<div className="row" title={t('widgets.background.camera')}>
<MdPhotoCamera />
<span id="infoCamera">{info.camera}</span>
</div>
) : null}
<div className="row" title={variables.getMessage('widgets.background.resolution')}>
<div className="row" title={t('widgets.background.resolution')}>
<Resolution />
<span id="infoResolution">
{width}x{height}
</span>
</div>
{info.category && (
<div className="row" title={variables.getMessage('widgets.background.category')}>
<div className="row" title={t('widgets.background.category')}>
<Category />
<span id="infoCategory">{info.category[0].toUpperCase() + info.category.slice(1)}</span>
</div>
)}
{api && (
<div className="row" title={variables.getMessage('widgets.background.source')}>
<div className="row" title={t('widgets.background.source')}>
<Source />
<span id="infoSource">
{info.photoURL ? (
@@ -189,7 +191,7 @@ function PhotoInformation({ info, url, api }) {
return (
<div className="buttons">
{!info.offline && (
<Tooltip title={variables.getMessage('widgets.quote.share')} key="share" placement="top">
<Tooltip title={t('widgets.quote.share')} key="share" placement="top">
<Share onClick={() => openShareModal(true)} />
</Tooltip>
)}
@@ -204,7 +206,7 @@ function PhotoInformation({ info, url, api }) {
</Tooltip>
{!info.offline && (
<Tooltip
title={variables.getMessage('widgets.background.download')}
title={t('widgets.background.download')}
key="download"
placement="top"
>
@@ -213,7 +215,7 @@ function PhotoInformation({ info, url, api }) {
)}
{info.pun && info.category && (
<Tooltip
title={variables.getMessage('widgets.background.exclude')}
title={t('widgets.background.exclude')}
key="exclude"
placement="top"
>
@@ -227,16 +229,16 @@ function PhotoInformation({ info, url, api }) {
const UnsplashStats = () => {
return (
<div className="unsplashStats">
<div title={variables.getMessage('widgets.background.views')}>
<div title={t('widgets.background.views')}>
<Views />
<span>{info.views.toLocaleString()}</span>
</div>
<div title={variables.getMessage('widgets.background.downloads')}>
<div title={t('widgets.background.downloads')}>
<Download />
<span>{info.downloads.toLocaleString()}</span>
</div>
{info.likes ? (
<div title={variables.getMessage('widgets.background.likes')}>
<div title={t('widgets.background.likes')}>
<MdFavourite />
<span>{info.likes.toLocaleString()}</span>
</div>
@@ -366,7 +368,7 @@ function PhotoInformation({ info, url, api }) {
{(showExtraInfo || other) && excludeModal === false ? (
<>
<span className="subtitle">
{variables.getMessage('widgets.background.information')}
{t('widgets.background.information')}
</span>
<InformationItems />
<ActionButtons />

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react';
import variables from 'config/variables';
import { nth, convertTimezone } from 'utils/date';
import EventBus from 'utils/eventbus';
import { useT } from 'contexts';
import './greeting.scss';
/**
@@ -44,6 +45,7 @@ const calculateAge = (date) => {
};
const Greeting = () => {
const t = useT();
const [greeting, setGreeting] = useState('');
const [display, setDisplay] = useState('block');
const [fontSize, setFontSize] = useState('1.6em');
@@ -63,13 +65,13 @@ const Greeting = () => {
let message;
switch (true) {
case hour < 12:
message = variables.getMessage('widgets.greeting.morning');
message = t('widgets.greeting.morning');
break;
case hour < 18:
message = variables.getMessage('widgets.greeting.afternoon');
message = t('widgets.greeting.afternoon');
break;
default:
message = variables.getMessage('widgets.greeting.evening');
message = t('widgets.greeting.evening');
break;
}
@@ -103,10 +105,10 @@ const Greeting = () => {
if (birth.getDate() === now.getDate() && birth.getMonth() === now.getMonth()) {
if (localStorage.getItem('birthdayage') === 'true' && calculateAge(birth) !== 0) {
const text = variables.getMessage('widgets.greeting.birthday').split(' ');
const text = t('widgets.greeting.birthday').split(' ');
message = `${text[0]} ${nth(calculateAge(birth))} ${text[1]}`;
} else {
message = variables.getMessage('widgets.greeting.birthday');
message = t('widgets.greeting.birthday');
}
}
}
@@ -149,7 +151,7 @@ const Greeting = () => {
clearTimeout(timerRef.current);
}
};
}, []);
}, [t]);
return (
<span className="greeting" ref={greetingRef} style={{ display, fontSize }}>

View File

@@ -1,6 +1,6 @@
import variables from 'config/variables';
import { useState, useEffect, useRef } from 'react';
import { useMessage } from 'contexts/TranslationContext';
import { useT } from 'contexts/TranslationContext';
import variables from 'config/variables';
import { MdOutlineOpenInNew } from 'react-icons/md';
@@ -9,10 +9,11 @@ import { Radio } from 'components/Form/Settings';
import languages from '@/i18n/languages.json';
const LanguageOptions = () => {
const loadingText = useMessage('modals.main.loading');
const offlineText = useMessage('modals.main.marketplace.offline.description');
const languageTitle = useMessage('modals.main.settings.sections.language.title');
const quoteTitle = useMessage('modals.main.settings.sections.language.quote');
const t = useT();
const loadingText = t('modals.main.loading');
const offlineText = t('modals.main.marketplace.offline.description');
const languageTitle = t('modals.main.settings.sections.language.title');
const quoteTitle = t('modals.main.settings.sections.language.quote');
const [quoteLanguages, setQuoteLanguages] = useState([
{

View File

@@ -1,6 +1,5 @@
import variables from 'config/variables';
import { memo, useMemo } from 'react';
import { useTranslation } from 'contexts/TranslationContext';
import { useT } from 'contexts/TranslationContext';
import Tabs from 'components/Elements/MainModal/backend/Tabs';
@@ -90,15 +89,15 @@ const sections = [
];
function Settings(props) {
const { languagecode } = useTranslation();
const t = useT();
// Recalculate section labels when language changes
const translatedSections = useMemo(() =>
sections.map(section => ({
...section,
translatedLabel: variables.getMessage(section.label)
translatedLabel: t(section.label)
})),
[languagecode]
[t]
);
return (

View File

@@ -1,11 +1,13 @@
import variables from 'config/variables';
import { memo, useState, useCallback } from 'react';
import { useT } from 'contexts';
import { MdCropFree } from 'react-icons/md';
import { Tooltip } from 'components/Elements';
const Maximise = memo(({ fontSize }) => {
const t = useT();
const [hidden, setHidden] = useState(false);
const setAttribute = useCallback((blur, brightness, filter) => {
@@ -44,7 +46,7 @@ const Maximise = memo(({ fontSize }) => {
const maximise = useCallback(() => {
// hide widgets
const widgets = document.getElementById('widgets');
if (!hidden) {
widgets.style.display = 'none';
setHidden(true);
@@ -60,13 +62,13 @@ const Maximise = memo(({ fontSize }) => {
return (
<Tooltip
title={variables.getMessage('modals.main.settings.sections.background.buttons.view')}
title={t('modals.main.settings.sections.background.buttons.view')}
>
<button
className="navbarButton"
style={{ fontSize }}
onClick={maximise}
aria-label={variables.getMessage('modals.main.settings.sections.background.buttons.view')}
aria-label={t('modals.main.settings.sections.background.buttons.view')}
>
<MdCropFree className="topicons" />
</button>

View File

@@ -1,34 +1,39 @@
import { useState, useEffect } from 'react';
import variables from 'config/variables';
import { useT } from 'contexts/TranslationContext';
import { MdRefresh } from 'react-icons/md';
import { Tooltip } from 'components/Elements';
import EventBus from 'utils/eventbus';
import { useTranslation } from 'contexts/TranslationContext';
import variables from 'config/variables';
function Refresh() {
const { languagecode } = useTranslation();
const t = useT();
const [refreshText, setRefreshText] = useState('');
const [refreshOption, setRefreshOption] = useState(localStorage.getItem('refreshOption') || '');
useEffect(() => {
EventBus.on('refresh', (data) => {
const handleRefresh = (data) => {
if (data === 'navbar' || data === 'background' || data === 'language') {
setRefreshOption(localStorage.getItem('refreshOption'));
updateRefreshText();
}
});
};
EventBus.on('refresh', handleRefresh);
updateRefreshText();
}, [languagecode]);
return () => {
EventBus.off('refresh', handleRefresh);
};
}, [t]);
function updateRefreshText() {
let text;
switch (localStorage.getItem('refreshOption')) {
case 'background':
text = variables.getMessage('modals.main.settings.sections.background.title');
text = t('modals.main.settings.sections.background.title');
break;
case 'quote':
text = variables.getMessage('modals.main.settings.sections.quote.title');
text = t('modals.main.settings.sections.quote.title');
break;
case 'quotebackground':
text = new Intl.ListFormat(
@@ -38,14 +43,12 @@ function Refresh() {
type: 'conjunction',
},
).format([
variables.getMessage('modals.main.settings.sections.quote.title'),
variables.getMessage('modals.main.settings.sections.background.title'),
t('modals.main.settings.sections.quote.title'),
t('modals.main.settings.sections.background.title'),
]);
break;
default:
text = variables.getMessage(
'modals.main.settings.sections.appearance.navbar.refresh_options.page',
);
text = t('modals.main.settings.sections.appearance.navbar.refresh_options.page');
break;
}

View File

@@ -3,12 +3,14 @@ import variables from 'config/variables';
import { memo, useRef, useEffect, useState, useCallback } from 'react';
import { MdSearch, MdMic } from 'react-icons/md';
import { Tooltip } from 'components/Elements';
import { useT } from 'contexts';
import EventBus from 'utils/eventbus';
import './search.scss';
function Search() {
const t = useT();
const [microphone, setMicrophone] = useState(null);
const [classList] = useState(
localStorage.getItem('widgetStyle') === 'legacy' ? 'searchIcons old' : 'searchIcons',
@@ -113,14 +115,14 @@ function Search() {
<div className="searchMain">
<div className={classList}>
<Tooltip
title={variables.getMessage('modals.main.settings.sections.search.voice_search')}
title={t('modals.main.settings.sections.search.voice_search')}
>
{microphone}
</Tooltip>
</div>
<form onSubmit={searchButton} className="searchBar">
<div className={classList}>
<Tooltip title={variables.getMessage('widgets.search')}>
<Tooltip title={t('widgets.search')}>
<button className="navbarButton" onClick={searchButton} aria-label="Search">
<MdSearch />
</button>
@@ -128,7 +130,7 @@ function Search() {
</div>
<input
type="text"
placeholder={variables.getMessage('widgets.search')}
placeholder={t('widgets.search')}
id="searchtext"
className="searchInput"
/>

View File

@@ -1,14 +1,15 @@
import variables from 'config/variables';
import { MdOutlineOpenInNew } from 'react-icons/md';
import languages from '@/i18n/languages.json';
import { useMessage } from 'contexts/TranslationContext';
import { useT } from 'contexts/TranslationContext';
import variables from 'config/variables';
import { Radio } from 'components/Form/Settings';
import { Header, Content } from '../Layout';
function ChooseLanguage() {
const title = useMessage('modals.welcome.sections.language.title');
const description = useMessage('modals.welcome.sections.language.description');
const t = useT();
const title = t('modals.welcome.sections.language.title');
const description = t('modals.welcome.sections.language.description');
return (
<Content>