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,21 +1,20 @@
import variables from 'config/variables';
import { memo, useState, useEffect } from 'react';
import { useTranslation } from 'contexts/TranslationContext';
import { useT } from 'contexts/TranslationContext';
import { getIconComponent, DIVIDER_LABELS } from '../constants/tabConfig';
function Tab({ label, currentTab, onClick, navbarTab }) {
const { languagecode } = useTranslation();
const t = useT();
const [isExperimental, setIsExperimental] = useState(true);
useEffect(() => {
setIsExperimental(localStorage.getItem('experimental') !== 'false');
}, []);
// Get the icon component for this label
const IconComponent = getIconComponent(label, variables);
// Get the icon component for this label (label is already translated)
const IconComponent = getIconComponent(label, { getMessage: t });
// Determine if this label should have a divider after it
const hasDivider = DIVIDER_LABELS.some((key) => variables.getMessage(key) === label);
const hasDivider = DIVIDER_LABELS.some((key) => t(key) === label);
// Build className
const baseClass = navbarTab ? 'navbar-item' : 'tab-list-item';
@@ -23,8 +22,7 @@ function Tab({ label, currentTab, onClick, navbarTab }) {
const className = `${baseClass}${currentTab === label ? ` ${activeClass}` : ''}`;
// Hide experimental tab if experimental mode is disabled
const isExperimentalTab =
label === variables.getMessage('modals.main.settings.sections.experimental.title');
const isExperimentalTab = label === t('modals.main.settings.sections.experimental.title');
if (isExperimentalTab && !isExperimental) {
return <hr />;
}

View File

@@ -1,6 +1,6 @@
import variables from 'config/variables';
import { useState, useEffect } from 'react';
import { useTranslation } from 'contexts/TranslationContext';
import { useT } from 'contexts/TranslationContext';
import variables from 'config/variables';
import Tab from './Tab';
import ReminderInfo from '../components/ReminderInfo';
import ErrorBoundary from '../../../../features/misc/modals/ErrorBoundary';
@@ -16,7 +16,7 @@ const Tabs = ({
navigationTrigger,
sections,
}) => {
const { languagecode } = useTranslation();
const t = useT();
// Find initial section from deep link if available
const getInitialSection = () => {
@@ -24,7 +24,7 @@ const Tabs = ({
const section = sections.find((s) => s.name === deepLinkData.section);
if (section) {
return {
label: variables.getMessage(section.label),
label: t(section.label),
name: section.name,
};
}
@@ -66,23 +66,23 @@ const Tabs = ({
if (sections && currentName) {
const section = sections.find((s) => s.name === currentName);
if (section) {
const newLabel = variables.getMessage(section.label);
const newLabel = t(section.label);
setCurrentTab(newLabel);
}
}
}, [languagecode]);
}, [t, sections, currentName]);
// Handle navigation trigger for settings sections (popstate)
useEffect(() => {
if (navigationTrigger?.type === 'settings-section' && sections) {
const section = sections.find((s) => s.name === navigationTrigger.data);
if (section) {
const label = variables.getMessage(section.label);
const label = t(section.label);
setCurrentTab(label);
setCurrentName(section.name);
}
}
}, [navigationTrigger, sections]);
}, [navigationTrigger, sections, t]);
// Reset to first tab when requested
useEffect(() => {

View File

@@ -1,5 +1,4 @@
import variables from 'config/variables';
import { useTranslation } from 'contexts/TranslationContext';
import { useT } from 'contexts/TranslationContext';
import { MdClose, MdChevronRight, MdArrowBack, MdArrowForward } from 'react-icons/md';
import { Tooltip, Button } from 'components/Elements';
import { NAVBAR_BUTTONS } from '../constants/tabConfig';
@@ -32,13 +31,11 @@ function ModalTopBar({
canGoBack,
canGoForward,
}) {
const { languagecode } = useTranslation();
const t = useT();
// Get the current tab label (uses languagecode to re-evaluate on language change)
// Get the current tab label
const currentTabButton = NAVBAR_BUTTONS.find(({ tab }) => tab === currentTab);
const currentTabLabel = languagecode && currentTabButton
? variables.getMessage(currentTabButton.messageKey)
: '';
const currentTabLabel = currentTabButton ? t(currentTabButton.messageKey) : '';
// Utility function to get translated sub-section label
const getSubSectionLabel = (subSection, sectionName) => {
@@ -46,7 +43,7 @@ function ModalTopBar({
// Use the same translation pattern as the section components
const translationKey = `modals.main.settings.sections.${sectionName}.${subSection}.title`;
const translated = variables.getMessage(translationKey);
const translated = t(translationKey);
// If translation key is returned as-is or empty, it means translation doesn't exist
// Fall back to capitalized sub-section name
@@ -107,7 +104,7 @@ function ModalTopBar({
const categoryKey = MARKETPLACE_TYPE_TO_KEY[productView.type];
if (categoryKey) {
breadcrumbPath.push({
label: variables.getMessage(categoryKey),
label: t(categoryKey),
onClick: productView.onBack || null,
});
}
@@ -214,11 +211,11 @@ function ModalTopBar({
onClick={() => onTabChange(tab)}
active={currentTab === tab}
icon={<Icon />}
label={variables.getMessage(messageKey)}
label={t(messageKey)}
/>
))}
</div>
<Tooltip title={variables.getMessage('modals.welcome.buttons.close')} key="closeTooltip">
<Tooltip title={t('modals.welcome.buttons.close')} key="closeTooltip">
<span className="closeModal" onClick={onClose}>
<MdClose />
</span>

View File

@@ -1,5 +1,6 @@
import variables from 'config/variables';
import { memo, useState, useCallback } from 'react';
import { useTranslation } from 'contexts/TranslationContext';
import {
Radio as RadioUI,
RadioGroup,
@@ -9,9 +10,9 @@ import {
} from '@mui/material';
import EventBus from 'utils/eventbus';
import { translations } from 'lib/translations';
const Radio = memo((props) => {
const { changeLanguage } = useTranslation();
const [value, setValue] = useState(localStorage.getItem(props.name));
const handleChange = useCallback(async (e) => {
@@ -22,14 +23,8 @@ const Radio = memo((props) => {
}
if (props.name === 'language') {
// old tab name
if (localStorage.getItem('tabName') === variables.getMessage('tabname')) {
localStorage.setItem('tabName', translations[newValue.replace('-', '_')].tabname);
}
// Emit language change event for instant update
localStorage.setItem(props.name, newValue);
EventBus.emit('languageChange', { language: newValue });
// Use context to change language directly - no EventBus needed
changeLanguage(newValue);
setValue(newValue);
variables.stats.postEvent('setting', `${props.name} from ${value} to ${newValue}`);
@@ -59,7 +54,7 @@ const Radio = memo((props) => {
}
EventBus.emit('refresh', props.category);
}, [value, props]);
}, [value, props, changeLanguage]);
return (
<FormControl component="fieldset">

View File

@@ -1,43 +1,96 @@
# Translation System
## Using Reactive Translations
## Refactored Translation System ✨
The app now supports instant language switching without page refresh!
The app now has a robust, centralized translation system that updates instantly without page refresh!
### For new components
### Using Translations (New API)
Use the `useMessage` hook for reactive translations:
**Recommended approach** - Use the `useT()` hook:
```jsx
import { useMessage } from 'contexts/TranslationContext';
import { useT } from 'contexts/TranslationContext';
function MyComponent() {
const title = useMessage('modals.main.title');
const description = useMessage('modals.main.description');
const t = useT();
return (
<div>
<h1>{title}</h1>
<p>{description}</p>
<h1>{t('modals.main.title')}</h1>
<p>{t('modals.main.description')}</p>
</div>
);
}
```
### For existing components
**Alternative** - Use the full hook:
The old `variables.getMessage()` method still works but won't automatically update when language changes. Gradually migrate to `useMessage` for components that display translations prominently.
```jsx
import { useTranslation } from 'contexts/TranslationContext';
### How it works
function MyComponent() {
const { t, language, changeLanguage } = useTranslation();
1. `TranslationProvider` wraps the app and listens for language change events
2. When language is changed via the Radio component, it emits a `languageChange` event
3. The provider updates the i18n instance and triggers re-renders
4. Components using `useMessage` automatically update to show new translations
return (
<div>
<h1>{t('modals.main.title')}</h1>
<button onClick={() => changeLanguage('es')}>Español</button>
</div>
);
}
```
### Backward Compatibility
The old `variables.getMessage()` method still works and is automatically reactive:
```jsx
// This still works and updates on language change
const title = variables.getMessage('modals.main.title');
```
This allows gradual migration of components.
### How It Works
1. **TranslationProvider** wraps the app and manages the i18n instance
2. **Single source of truth** - All translation logic is centralized in the context
3. **Automatic reactivity** - Components using `t()` automatically re-render on language change
4. **Backward compatible** - `variables.getMessage()` is updated to use the context internally
5. **No EventBus needed** - Direct context updates for language changes
### Benefits
- No page refresh needed
- Minimal performance impact (translations already loaded)
- Backward compatible with existing code
- Smooth user experience
**Instant updates** - No page refresh needed
**Single API** - One consistent way to translate
**Automatic re-renders** - React handles updates efficiently
**Minimal performance impact** - Translations pre-loaded, only switching active language
**Type-safe ready** - Easy to add TypeScript support later
**Clean architecture** - Centralized translation logic
### Migration Guide
**Before:**
```jsx
const title = variables.getMessage('modals.main.title');
```
**After:**
```jsx
const t = useT();
const title = t('modals.main.title');
```
### Components Updated
Core navigation and UI:
- ✅ TranslationContext (centralized API)
- ✅ Radio component (language selector)
- ✅ Tab, Tabs (sidebar navigation)
- ✅ ModalTopBar (breadcrumbs)
- ✅ Settings view
- ✅ Language section
- ✅ Refresh button
- ✅ Welcome modal
All other components continue to work via backward compatibility.

View File

@@ -1,4 +1,4 @@
import { createContext, useContext, useState, useEffect, useCallback } from 'react';
import { createContext, useContext, useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { initTranslations, translations } from 'lib/translations';
import variables from 'config/variables';
import EventBus from 'utils/eventbus';
@@ -6,25 +6,51 @@ import EventBus from 'utils/eventbus';
const TranslationContext = createContext();
export function TranslationProvider({ children, initialLanguage }) {
const [languagecode, setLanguagecode] = useState(initialLanguage);
const [currentLanguage, setCurrentLanguage] = useState(initialLanguage);
const i18nInstance = useRef(null);
// Initialize i18n instance once
useEffect(() => {
i18nInstance.current = initTranslations(currentLanguage);
variables.language = i18nInstance.current;
variables.languagecode = currentLanguage;
document.documentElement.lang = currentLanguage.replace('_', '-');
}, [currentLanguage]);
// Change language function
const changeLanguage = useCallback((newLanguage) => {
// Update the i18n instance
variables.language = initTranslations(newLanguage);
i18nInstance.current = initTranslations(newLanguage);
variables.language = i18nInstance.current;
variables.languagecode = newLanguage;
document.documentElement.lang = newLanguage.replace('_', '-');
// Update tab name if it's still the default
if (localStorage.getItem('tabName') === variables.getMessage('tabname')) {
const newTabName = translations[newLanguage.replace('-', '_')]?.tabname || variables.getMessage('tabname');
const currentTabName = localStorage.getItem('tabName');
const oldDefaultTabName = i18nInstance.current?.getMessage(currentLanguage, 'tabname');
if (currentTabName === oldDefaultTabName || !currentTabName) {
const newTabName = translations[newLanguage.replace('-', '_')]?.tabname ||
i18nInstance.current?.getMessage(newLanguage, 'tabname') ||
'Mue';
localStorage.setItem('tabName', newTabName);
document.title = newTabName;
}
// Update state to trigger re-render
setLanguagecode(newLanguage);
}, []);
// Update language in localStorage
localStorage.setItem('language', newLanguage);
// Update state to trigger re-render
setCurrentLanguage(newLanguage);
}, [currentLanguage]);
// Single translation function - the main API
const t = useCallback((key, optional = {}) => {
if (!i18nInstance.current) return key;
return i18nInstance.current.getMessage(currentLanguage, key, optional);
}, [currentLanguage]);
// Listen for EventBus language change events (for backward compatibility)
useEffect(() => {
const handleLanguageChange = (data) => {
if (data?.language) {
@@ -39,8 +65,20 @@ export function TranslationProvider({ children, initialLanguage }) {
};
}, [changeLanguage]);
// Update variables.getMessage for backward compatibility
useEffect(() => {
variables.getMessage = (key, optional = {}) => t(key, optional);
}, [t]);
const value = useMemo(() => ({
language: currentLanguage,
languagecode: currentLanguage, // Alias for backward compatibility
changeLanguage,
t,
}), [currentLanguage, changeLanguage, t]);
return (
<TranslationContext.Provider value={{ languagecode, changeLanguage }}>
<TranslationContext.Provider value={value}>
{children}
</TranslationContext.Provider>
);
@@ -54,10 +92,8 @@ export function useTranslation() {
return context;
}
// Hook for reactive translations - triggers re-render when language changes
export function useMessage(key, optional = {}) {
const { languagecode } = useTranslation();
// The languagecode dependency ensures this hook re-evaluates when language changes
// This is intentional - we use it to trigger re-renders even though it's not directly used
return languagecode ? variables.getMessage(key, optional) : '';
// Convenience hook - just returns the t function
export function useT() {
const { t } = useTranslation();
return t;
}

View File

@@ -1 +1 @@
export { TranslationProvider, useTranslation, useMessage } from './TranslationContext';
export { TranslationProvider, useTranslation, useT } from './TranslationContext';

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) => {
@@ -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>