mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-05-15 22:26:58 +00:00
Merge pull request #42 from Qortal/fix/changing-languages
fix language select to work in client-side prod
This commit is contained in:
commit
99aa214a0f
58
i18n.js
58
i18n.js
@ -1,58 +0,0 @@
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import HttpBackend from 'i18next-http-backend';
|
||||
import LocalStorageBackend from 'i18next-localstorage-backend';
|
||||
import HttpApi from 'i18next-http-backend';
|
||||
import i18n from 'i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
// Detect environment
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
// Register custom postProcessor: it capitalizes the first letter of a translation-
|
||||
// Usage:
|
||||
// t('greeting', { postProcess: 'capitalize' })
|
||||
const capitalize = {
|
||||
type: 'postProcessor',
|
||||
name: 'capitalize',
|
||||
process: (value) => {
|
||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||
},
|
||||
};
|
||||
|
||||
export const supportedLanguages = {
|
||||
de: { name: 'Deutsch', flag: '🇩🇪' },
|
||||
en: { name: 'English', flag: '🇺🇸' },
|
||||
es: { name: 'Español', flag: '🇪🇸' },
|
||||
fr: { name: 'Français', flag: '🇫🇷' },
|
||||
it: { name: 'Italiano', flag: '🇮🇹' },
|
||||
ru: { name: 'Русский', flag: '🇷🇺' },
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(HttpApi)
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.use(capitalize)
|
||||
.init({
|
||||
backend: {
|
||||
backends: [LocalStorageBackend, HttpBackend],
|
||||
backendOptions: [
|
||||
{
|
||||
expirationTime: 7 * 24 * 60 * 60 * 1000, // 7 days
|
||||
},
|
||||
{
|
||||
loadPath: '/locales/{{lng}}/{{ns}}.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
debug: isDev,
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
lng: navigator.language,
|
||||
ns: ['auth', 'core', 'group', 'tutorial'],
|
||||
supportedLngs: Object.keys(supportedLanguages),
|
||||
});
|
||||
|
||||
export default i18n;
|
@ -1,7 +1,13 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { supportedLanguages } from '../../../i18n';
|
||||
import { Tooltip, useTheme } from '@mui/material';
|
||||
import { supportedLanguages } from '../../i18n/i18n';
|
||||
import {
|
||||
FormControl,
|
||||
MenuItem,
|
||||
Select,
|
||||
Tooltip,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
|
||||
const LanguageSelector = () => {
|
||||
const { i18n, t } = useTranslation(['core']);
|
||||
@ -19,20 +25,6 @@ const LanguageSelector = () => {
|
||||
const { name, flag } =
|
||||
supportedLanguages[currentLang] || supportedLanguages['en'];
|
||||
|
||||
// Detect clicks outside the component
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (selectorRef.current && !selectorRef.current.contains(event.target)) {
|
||||
setShowSelect(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={selectorRef}
|
||||
@ -44,33 +36,13 @@ const LanguageSelector = () => {
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
title={t('core:action.change_language', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
>
|
||||
{showSelect ? (
|
||||
<select
|
||||
style={{
|
||||
fontSize: '1rem',
|
||||
border: '2px',
|
||||
background: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
bottom: '7px',
|
||||
}}
|
||||
value={currentLang}
|
||||
onChange={handleChange}
|
||||
autoFocus
|
||||
>
|
||||
{Object.entries(supportedLanguages).map(([code, { name }]) => (
|
||||
<option key={code} value={code}>
|
||||
{code.toUpperCase()} - {name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
{!showSelect && (
|
||||
<Tooltip
|
||||
key={currentLang}
|
||||
title={t('core:action.change_language', {
|
||||
postProcess: 'capitalize',
|
||||
})}
|
||||
>
|
||||
<button
|
||||
onClick={() => setShowSelect(true)}
|
||||
style={{
|
||||
@ -81,10 +53,36 @@ const LanguageSelector = () => {
|
||||
}}
|
||||
aria-label={`Current language: ${name}`}
|
||||
>
|
||||
{showSelect ? undefined : flag}
|
||||
{flag}
|
||||
</button>
|
||||
)}
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{showSelect && (
|
||||
<FormControl
|
||||
size="small"
|
||||
sx={{
|
||||
minWidth: 120,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
open
|
||||
labelId="language-select-label"
|
||||
id="language-select"
|
||||
value={currentLang}
|
||||
onChange={handleChange}
|
||||
autoFocus
|
||||
onClose={() => setShowSelect(false)}
|
||||
>
|
||||
{Object.entries(supportedLanguages).map(([code, { name }]) => (
|
||||
<MenuItem key={code} value={code}>
|
||||
{code.toUpperCase()} – {name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
54
src/i18n/i18n.ts
Normal file
54
src/i18n/i18n.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
const capitalize = {
|
||||
type: 'postProcessor',
|
||||
name: 'capitalize',
|
||||
process: (value: string) => value.charAt(0).toUpperCase() + value.slice(1),
|
||||
};
|
||||
|
||||
export const supportedLanguages = {
|
||||
de: { name: 'Deutsch', flag: '🇩🇪' },
|
||||
en: { name: 'English', flag: '🇺🇸' },
|
||||
es: { name: 'Español', flag: '🇪🇸' },
|
||||
fr: { name: 'Français', flag: '🇫🇷' },
|
||||
it: { name: 'Italiano', flag: '🇮🇹' },
|
||||
ru: { name: 'Русский', flag: '🇷🇺' },
|
||||
};
|
||||
|
||||
// Load all JSON files under locales/**/*
|
||||
const modules = import.meta.glob('./locales/**/*.json', {
|
||||
eager: true,
|
||||
}) as Record<string, any>;
|
||||
|
||||
// Construct i18n resources object
|
||||
const resources: Record<string, Record<string, any>> = {};
|
||||
|
||||
for (const path in modules) {
|
||||
// Path format: './locales/en/core.json'
|
||||
const match = path.match(/\.\/locales\/([^/]+)\/([^/]+)\.json$/);
|
||||
if (!match) continue;
|
||||
|
||||
const [, lang, ns] = match;
|
||||
resources[lang] = resources[lang] || {};
|
||||
resources[lang][ns] = modules[path].default;
|
||||
}
|
||||
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
.use(capitalize as any)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: 'en',
|
||||
lng: navigator.language,
|
||||
supportedLngs: Object.keys(supportedLanguages),
|
||||
ns: ['core', 'auth', 'group', 'tutorial'],
|
||||
defaultNS: 'core',
|
||||
interpolation: { escapeValue: false },
|
||||
react: { useSuspense: false },
|
||||
debug: import.meta.env.MODE === 'development',
|
||||
});
|
||||
|
||||
export default i18n;
|
@ -5,7 +5,7 @@ import './messaging/messagesToBackground';
|
||||
import { MessageQueueProvider } from './MessageQueueContext.tsx';
|
||||
import { ThemeProvider } from './components/Theme/ThemeContext.tsx';
|
||||
import { CssBaseline } from '@mui/material';
|
||||
import '../i18n';
|
||||
import './i18n/i18n.js';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<>
|
||||
|
@ -8,12 +8,13 @@
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"moduleResolution": "node",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user