mirror of
https://github.com/mue/mue.git
synced 2026-06-05 23:45:53 +02:00
feat(translation): refactor translation system to use useT hook for reactive updates
This commit is contained in:
@@ -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 />;
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { TranslationProvider, useTranslation, useMessage } from './TranslationContext';
|
||||
export { TranslationProvider, useTranslation, useT } from './TranslationContext';
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 }}>
|
||||
|
||||
@@ -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([
|
||||
{
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user