feat(Language): add search functionality and system language detection to language options

This commit is contained in:
alexsparkes
2026-01-24 21:24:20 +00:00
parent 9824b8e99a
commit 745e4e7106
36 changed files with 276 additions and 43 deletions

View File

@@ -1,5 +1,6 @@
import { useMemo } from 'react';
import { MdOutlineOpenInNew } from 'react-icons/md';
import { useState, useMemo } from 'react';
import { MdOutlineOpenInNew, MdSearch } from 'react-icons/md';
import { TextField, InputAdornment } from '@mui/material';
import languages from '@/i18n/languages.json';
import { useT, useTranslation } from 'contexts/TranslationContext';
import variables from 'config/variables';
@@ -9,15 +10,17 @@ import { Header, Content } from '../Layout';
function ChooseLanguage() {
const t = useT();
const { language: currentLanguage } = useTranslation();
const { language: currentLanguage, changeLanguage } = useTranslation();
const title = t('modals.welcome.sections.language.title');
const description = t('modals.welcome.sections.language.description');
const [searchQuery, setSearchQuery] = useState('');
const languageOptions = useMemo(() => {
const currentLanguageISO = currentLanguage.replace('_', '-');
const displayNames = new Intl.DisplayNames([currentLanguageISO], { type: 'language' });
return languages.map((lang) => {
const mappedLanguages = languages.map((lang) => {
const nativeName = lang.name;
const isoCode = lang.value.replace('_', '-');
@@ -44,10 +47,29 @@ function ChooseLanguage() {
return {
name: displayName,
value: lang.value,
nativeName,
searchText: `${nativeName} ${translatedName || ''}`.toLowerCase(),
};
});
// Sort alphabetically by native name
return mappedLanguages.sort((a, b) => a.nativeName.localeCompare(b.nativeName));
}, [currentLanguage]);
// Filter languages based on search query
const filteredLanguages = useMemo(() => {
if (!searchQuery.trim()) return languageOptions;
const query = searchQuery.toLowerCase();
return languageOptions.filter(lang => lang.searchText.includes(query));
}, [languageOptions, searchQuery]);
// Detect system language
const systemLanguage = useMemo(() => {
const browserLang = navigator.language.replace('-', '_');
return languages.find(l => l.value === browserLang) ||
languages.find(l => l.value.startsWith(browserLang.split('_')[0]));
}, []);
return (
<Content>
<Header
@@ -67,8 +89,48 @@ function ChooseLanguage() {
</>
}
/>
{systemLanguage && systemLanguage.value !== currentLanguage && (
<button
className="uploadbg"
onClick={() => changeLanguage(systemLanguage.value)}
style={{ marginBottom: 12 }}
>
{t('modals.main.settings.sections.language.use_system')} ({systemLanguage.name})
</button>
)}
<TextField
placeholder={t('modals.main.settings.sections.language.search')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
variant="outlined"
size="small"
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<MdSearch style={{ color: '#888' }} />
</InputAdornment>
),
}}
sx={{
marginBottom: 2,
'& .MuiOutlinedInput-root': {
borderRadius: '24px',
backgroundColor: 'rgba(255, 255, 255, 0.08)',
'& fieldset': {
border: 'none',
},
'&:hover fieldset': {
border: 'none',
},
'&.Mui-focused fieldset': {
border: 'none',
},
},
}}
/>
<div className="languageSettings">
<Radio name="language" options={languageOptions} category="welcomeLanguage" />
<Radio name="language" options={filteredLanguages} category="welcomeLanguage" />
</div>
</Content>
);