feat(translation): implement reactive translation system with context and hooks

This commit is contained in:
alexsparkes
2026-01-24 17:02:41 +00:00
parent d0dbd7e75c
commit b8647538fd
13 changed files with 210 additions and 35 deletions

View File

@@ -1,5 +1,6 @@
import variables from 'config/variables';
import { useState, useEffect, useRef } from 'react';
import { useMessage } from 'contexts/TranslationContext';
import { MdOutlineOpenInNew } from 'react-icons/md';
@@ -8,9 +9,14 @@ 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 [quoteLanguages, setQuoteLanguages] = useState([
{
name: variables.getMessage('modals.main.loading'),
name: loadingText,
value: 'loading',
},
]);
@@ -18,33 +24,39 @@ const LanguageOptions = () => {
const controllerRef = useRef(new AbortController());
const getquoteLanguages = async () => {
const data = await (
await fetch(variables.constants.API_URL + '/quotes/languages', {
signal: controllerRef.current.signal,
})
).json();
try {
const data = await (
await fetch(variables.constants.API_URL + '/quotes/languages', {
signal: controllerRef.current.signal,
})
).json();
if (controllerRef.current.signal.aborted === true) {
return;
if (controllerRef.current.signal.aborted === true) {
return;
}
const fetchedQuoteLanguages = data.map((language) => {
return {
name: languages.find((l) => l.value === language.name)
? languages.find((l) => l.value === language.name).name
: 'English',
value: language,
};
});
setQuoteLanguages(fetchedQuoteLanguages);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Failed to fetch quote languages:', error);
}
}
const fetchedQuoteLanguages = data.map((language) => {
return {
name: languages.find((l) => l.value === language.name)
? languages.find((l) => l.value === language.name).name
: 'English',
value: language,
};
});
setQuoteLanguages(fetchedQuoteLanguages);
};
useEffect(() => {
if (navigator.onLine === false || localStorage.getItem('offlineMode') === 'true') {
setQuoteLanguages([
{
name: variables.getMessage('modals.main.marketplace.offline.description'),
name: offlineText,
value: 'loading',
},
]);
@@ -53,17 +65,18 @@ const LanguageOptions = () => {
getquoteLanguages();
const controller = controllerRef.current;
return () => {
// stop making requests
controllerRef.current.abort();
controller.abort();
};
}, []);
}, [offlineText]);
return (
<>
<div className="modalHeader">
<span className="mainTitle">
{variables.getMessage('modals.main.settings.sections.language.title')}
{languageTitle}
</span>
<div className="headerActions">
<a
@@ -81,7 +94,7 @@ const LanguageOptions = () => {
<Radio name="language" options={languages} element=".other" />
</div>
<span className="mainTitle">
{variables.getMessage('modals.main.settings.sections.language.quote')}
{quoteTitle}
</span>
<div className="languageSettings">
<Radio

View File

@@ -1,5 +1,6 @@
import variables from 'config/variables';
import { memo } from 'react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'contexts/TranslationContext';
import Tabs from 'components/Elements/MainModal/backend/Tabs';
@@ -89,6 +90,17 @@ const sections = [
];
function Settings(props) {
const { languagecode } = useTranslation();
// Recalculate section labels when language changes
const translatedSections = useMemo(() =>
sections.map(section => ({
...section,
translatedLabel: variables.getMessage(section.label)
})),
[languagecode]
);
return (
<Tabs
changeTab={(type) => props.changeTab(type)}
@@ -101,8 +113,8 @@ function Settings(props) {
navigationTrigger={props.navigationTrigger}
sections={sections}
>
{sections.map(({ label, name, component: Component }) => (
<div key={name} label={variables.getMessage(label)} name={name}>
{translatedSections.map(({ label, name, component: Component, translatedLabel }) => (
<div key={name} label={translatedLabel} name={name}>
<Component
currentSubSection={props.currentSubSection}
onSubSectionChange={props.onSubSectionChange}