Implement multilingual support: Integrate i18next for language translation across components, update configuration for multilingual descriptions and keywords, and enhance user interface elements with dynamic language switching. Add new dependencies for i18next and related libraries in package.json and package-lock.json.
This commit is contained in:
248
src/components/LanguageSwitcher.js
Normal file
248
src/components/LanguageSwitcher.js
Normal file
@@ -0,0 +1,248 @@
|
||||
import React, { Component } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { withI18n } from '../i18n/withTranslation.js';
|
||||
|
||||
class LanguageSwitcher extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
anchorEl: null,
|
||||
loadedFlags: {}
|
||||
};
|
||||
}
|
||||
|
||||
handleClick = (event) => {
|
||||
this.setState({ anchorEl: event.currentTarget });
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({ anchorEl: null });
|
||||
};
|
||||
|
||||
handleLanguageChange = (language) => {
|
||||
const { languageContext } = this.props;
|
||||
if (languageContext) {
|
||||
languageContext.changeLanguage(language);
|
||||
}
|
||||
this.handleClose();
|
||||
};
|
||||
|
||||
// Lazy load flag components
|
||||
loadFlagComponent = async (lang) => {
|
||||
if (this.state.loadedFlags[lang]) {
|
||||
return this.state.loadedFlags[lang];
|
||||
}
|
||||
|
||||
try {
|
||||
const flagMap = {
|
||||
'de': () => import('country-flag-icons/react/3x2').then(m => m.DE),
|
||||
'en': () => import('country-flag-icons/react/3x2').then(m => m.US),
|
||||
'es': () => import('country-flag-icons/react/3x2').then(m => m.ES),
|
||||
'fr': () => import('country-flag-icons/react/3x2').then(m => m.FR),
|
||||
'it': () => import('country-flag-icons/react/3x2').then(m => m.IT),
|
||||
'pl': () => import('country-flag-icons/react/3x2').then(m => m.PL),
|
||||
'hu': () => import('country-flag-icons/react/3x2').then(m => m.HU),
|
||||
'sr': () => import('country-flag-icons/react/3x2').then(m => m.RS),
|
||||
'bg': () => import('country-flag-icons/react/3x2').then(m => m.BG),
|
||||
'ru': () => import('country-flag-icons/react/3x2').then(m => m.RU),
|
||||
'uk': () => import('country-flag-icons/react/3x2').then(m => m.UA),
|
||||
'sk': () => import('country-flag-icons/react/3x2').then(m => m.SK),
|
||||
'cs': () => import('country-flag-icons/react/3x2').then(m => m.CZ),
|
||||
'ro': () => import('country-flag-icons/react/3x2').then(m => m.RO)
|
||||
};
|
||||
|
||||
const flagLoader = flagMap[lang];
|
||||
if (flagLoader) {
|
||||
const FlagComponent = await flagLoader();
|
||||
this.setState(prevState => ({
|
||||
loadedFlags: {
|
||||
...prevState.loadedFlags,
|
||||
[lang]: FlagComponent
|
||||
}
|
||||
}));
|
||||
return FlagComponent;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load flag for language: ${lang}`, error);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
getLanguageFlag = (lang) => {
|
||||
const FlagComponent = this.state.loadedFlags[lang];
|
||||
|
||||
if (FlagComponent) {
|
||||
return (
|
||||
<FlagComponent
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '14px',
|
||||
borderRadius: '2px',
|
||||
border: '1px solid #ddd'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Loading placeholder or fallback
|
||||
return (
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minWidth: '20px',
|
||||
height: '14px',
|
||||
backgroundColor: '#f5f5f5',
|
||||
color: '#666',
|
||||
fontSize: '8px',
|
||||
fontWeight: 'bold',
|
||||
borderRadius: '2px',
|
||||
fontFamily: 'monospace',
|
||||
border: '1px solid #ddd'
|
||||
}}
|
||||
>
|
||||
{this.getLanguageLabel(lang)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
// Load flags when menu opens
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { anchorEl } = this.state;
|
||||
const { languageContext } = this.props;
|
||||
|
||||
if (anchorEl && !prevState.anchorEl && languageContext) {
|
||||
// Menu just opened, lazy load all flags
|
||||
languageContext.availableLanguages.forEach(lang => {
|
||||
if (!this.state.loadedFlags[lang]) {
|
||||
this.loadFlagComponent(lang);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getLanguageLabel = (lang) => {
|
||||
const labels = {
|
||||
'de': 'DE',
|
||||
'en': 'US',
|
||||
'es': 'ES',
|
||||
'fr': 'FR',
|
||||
'it': 'IT',
|
||||
'pl': 'PL',
|
||||
'hu': 'HU',
|
||||
'sr': 'RS',
|
||||
'bg': 'BG',
|
||||
'ru': 'RU',
|
||||
'uk': 'UA',
|
||||
'sk': 'SK',
|
||||
'cs': 'CZ',
|
||||
'ro': 'RO'
|
||||
};
|
||||
return labels[lang] || lang.toUpperCase();
|
||||
};
|
||||
|
||||
getLanguageName = (lang) => {
|
||||
const names = {
|
||||
'de': 'Deutsch',
|
||||
'en': 'English',
|
||||
'es': 'Español',
|
||||
'fr': 'Français',
|
||||
'it': 'Italiano',
|
||||
'pl': 'Polski',
|
||||
'hu': 'Magyar',
|
||||
'sr': 'Српски',
|
||||
'bg': 'Български',
|
||||
'ru': 'Русский',
|
||||
'uk': 'Українська',
|
||||
'sk': 'Slovenčina',
|
||||
'cs': 'Čeština',
|
||||
'ro': 'Română'
|
||||
};
|
||||
return names[lang] || lang;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { languageContext } = this.props;
|
||||
const { anchorEl } = this.state;
|
||||
|
||||
if (!languageContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { currentLanguage, availableLanguages } = languageContext;
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Button
|
||||
aria-controls={open ? 'language-menu' : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
onClick={this.handleClick}
|
||||
color="inherit"
|
||||
size="small"
|
||||
sx={{
|
||||
my: 1,
|
||||
mx: 0.5,
|
||||
minWidth: 'auto',
|
||||
textTransform: 'none',
|
||||
fontSize: '0.875rem'
|
||||
}}
|
||||
>
|
||||
{this.getLanguageLabel(currentLanguage)}
|
||||
</Button>
|
||||
<Menu
|
||||
id="language-menu"
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={this.handleClose}
|
||||
disableScrollLock={true}
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'language-button',
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
{availableLanguages.map((language) => (
|
||||
<MenuItem
|
||||
key={language}
|
||||
onClick={() => this.handleLanguageChange(language)}
|
||||
selected={language === currentLanguage}
|
||||
sx={{
|
||||
minWidth: 160,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: 2
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{this.getLanguageFlag(language)}
|
||||
<Typography variant="body2">
|
||||
{this.getLanguageName(language)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ ml: 'auto' }}>
|
||||
{this.getLanguageLabel(language)}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withI18n()(LanguageSwitcher);
|
||||
Reference in New Issue
Block a user