mirror of
https://github.com/mue/mue.git
synced 2026-07-16 05:23:49 +02:00
feat: add translation percentage tracking and update related components
This commit is contained in:
@@ -148,19 +148,22 @@ const CustomSettings = memo(() => {
|
||||
setCurrentBackgroundIndex(newIndex);
|
||||
}, [customBackground.length]);
|
||||
|
||||
const addCustomURL = useCallback(async (e) => {
|
||||
// regex: https://ihateregex.io/expr/url/
|
||||
const urlRegex =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._~#=]{1,256}\.[a-zA-Z0-9()]{1,63}\b([-a-zA-Z0-9()!@:%_.~#?&=]*)/;
|
||||
if (urlRegex.test(e) === false) {
|
||||
return setUrlError(variables.getMessage('widgets.quicklinks.url_error'));
|
||||
}
|
||||
const addCustomURL = useCallback(
|
||||
async (e) => {
|
||||
// regex: https://ihateregex.io/expr/url/
|
||||
const urlRegex =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._~#=]{1,256}\.[a-zA-Z0-9()]{1,63}\b([-a-zA-Z0-9()!@:%_.~#?&=]*)/;
|
||||
if (urlRegex.test(e) === false) {
|
||||
return setUrlError(variables.getMessage('widgets.quicklinks.url_error'));
|
||||
}
|
||||
|
||||
const newIndex = customBackground.length;
|
||||
setCustomURLModal(false);
|
||||
setCurrentBackgroundIndex(newIndex);
|
||||
await handleCustomBackground({ target: { result: e } }, newIndex);
|
||||
}, [customBackground.length, handleCustomBackground]);
|
||||
const newIndex = customBackground.length;
|
||||
setCustomURLModal(false);
|
||||
setCurrentBackgroundIndex(newIndex);
|
||||
await handleCustomBackground({ target: { result: e } }, newIndex);
|
||||
},
|
||||
[customBackground.length, handleCustomBackground],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const dnd = customDnd.current;
|
||||
@@ -229,7 +232,7 @@ const CustomSettings = memo(() => {
|
||||
dnd.ondrop = null;
|
||||
}
|
||||
};
|
||||
}, [currentBackgroundIndex, handleCustomBackground]);
|
||||
}, [customBackground.length, handleCustomBackground]);
|
||||
|
||||
const hasVideo = customBackground.filter((bg) => bg && videoCheck(bg)).length > 0;
|
||||
|
||||
@@ -265,9 +268,7 @@ const CustomSettings = memo(() => {
|
||||
type="settings"
|
||||
onClick={uploadCustomBackground}
|
||||
icon={<MdOutlineFileUpload />}
|
||||
label={variables.getMessage(
|
||||
'modals.main.settings.sections.background.source.upload',
|
||||
)}
|
||||
label={variables.getMessage('modals.main.settings.sections.background.source.upload')}
|
||||
/>
|
||||
<Button
|
||||
type="settings"
|
||||
@@ -285,10 +286,7 @@ const CustomSettings = memo(() => {
|
||||
{customBackground.map((url, index) => (
|
||||
<div key={index}>
|
||||
{url && !videoCheck(url) ? (
|
||||
<img
|
||||
alt={'Custom background ' + (index || 0)}
|
||||
src={customBackground[index]}
|
||||
/>
|
||||
<img alt={'Custom background ' + (index || 0)} src={customBackground[index]} />
|
||||
) : url && videoCheck(url) ? (
|
||||
<MdPersonalVideo className="customvideoicon" />
|
||||
) : null}
|
||||
@@ -318,12 +316,9 @@ const CustomSettings = memo(() => {
|
||||
)}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
{variables.getMessage(
|
||||
'modals.main.settings.sections.background.source.formats',
|
||||
{
|
||||
list: 'jpeg, png, webp, webm, gif, mp4, webm, ogg',
|
||||
},
|
||||
)}
|
||||
{variables.getMessage('modals.main.settings.sections.background.source.formats', {
|
||||
list: 'jpeg, png, webp, webm, gif, mp4, webm, ogg',
|
||||
})}
|
||||
</span>
|
||||
<Button
|
||||
type="settings"
|
||||
|
||||
@@ -7,6 +7,7 @@ import { TextField, InputAdornment } from '@mui/material';
|
||||
import { Radio, Checkbox } from 'components/Form/Settings';
|
||||
|
||||
import languages from '@/i18n/languages.json';
|
||||
import translationPercentages from '@/i18n/translationPercentages.json';
|
||||
|
||||
const LanguageOptions = () => {
|
||||
const t = useT();
|
||||
@@ -29,6 +30,7 @@ const LanguageOptions = () => {
|
||||
// Convert language code to ISO format for Intl.DisplayNames
|
||||
// e.g., "en_GB" -> "en-GB", "zh_CN" -> "zh-CN"
|
||||
const isoCode = lang.value.replace('_', '-');
|
||||
const percentage = translationPercentages[lang.value]?.percent || 0;
|
||||
|
||||
let translatedName;
|
||||
try {
|
||||
@@ -37,25 +39,31 @@ const LanguageOptions = () => {
|
||||
if (translatedName) {
|
||||
translatedName = translatedName.split(' (')[0];
|
||||
}
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Fallback if the code isn't recognized
|
||||
translatedName = nativeName;
|
||||
}
|
||||
|
||||
// Show native name first, then translated name in brackets (greyed and smaller)
|
||||
const displayName = !translatedName || translatedName === nativeName
|
||||
? nativeName
|
||||
: (
|
||||
<>
|
||||
{nativeName}{' '}
|
||||
<span style={{ color: '#999', fontSize: '0.85em' }}>({translatedName})</span>
|
||||
</>
|
||||
);
|
||||
const displayName =
|
||||
!translatedName || translatedName === nativeName ? (
|
||||
<>
|
||||
{nativeName} <span style={{ color: '#999', fontSize: '0.85em' }}>({percentage}%)</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{nativeName}{' '}
|
||||
<span style={{ color: '#999', fontSize: '0.85em' }}>
|
||||
({translatedName} • {percentage}%)
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
|
||||
return {
|
||||
name: displayName,
|
||||
value: lang.value,
|
||||
nativeName,
|
||||
percentage,
|
||||
searchText: `${nativeName} ${translatedName || ''}`.toLowerCase(),
|
||||
};
|
||||
});
|
||||
@@ -68,26 +76,26 @@ const LanguageOptions = () => {
|
||||
const filteredLanguages = useMemo(() => {
|
||||
if (!searchQuery.trim()) return languageOptions;
|
||||
const query = searchQuery.toLowerCase();
|
||||
return languageOptions.filter(lang => lang.searchText.includes(query));
|
||||
return languageOptions.filter((lang) => lang.searchText.includes(query));
|
||||
}, [languageOptions, searchQuery]);
|
||||
|
||||
// Detect system language
|
||||
const systemLanguage = useMemo(() => {
|
||||
const browserLang = navigator.language.replace('-', '_');
|
||||
// Check exact match first, then base language
|
||||
return languages.find(l => l.value === browserLang) ||
|
||||
languages.find(l => l.value.startsWith(browserLang.split('_')[0]));
|
||||
return (
|
||||
languages.find((l) => l.value === browserLang) ||
|
||||
languages.find((l) => l.value.startsWith(browserLang.split('_')[0]))
|
||||
);
|
||||
}, []);
|
||||
|
||||
// Find current language option for display
|
||||
const currentLangOption = languageOptions.find(l => l.value === currentLanguage);
|
||||
const currentLangOption = languageOptions.find((l) => l.value === currentLanguage);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="modalHeader">
|
||||
<span className="mainTitle">
|
||||
{languageTitle}
|
||||
</span>
|
||||
<span className="mainTitle">{languageTitle}</span>
|
||||
<div className="headerActions">
|
||||
<a
|
||||
className="link"
|
||||
@@ -107,7 +115,14 @@ const LanguageOptions = () => {
|
||||
category="other"
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
placeholder={t('modals.main.settings.sections.language.search')}
|
||||
value={searchQuery}
|
||||
@@ -140,7 +155,8 @@ const LanguageOptions = () => {
|
||||
/>
|
||||
{currentLangOption && (
|
||||
<div style={{ color: '#888', whiteSpace: 'nowrap' }}>
|
||||
{t('modals.main.settings.sections.language.current')}: <strong style={{ color: 'var(--fg)' }}>{currentLangOption.nativeName}</strong>
|
||||
{t('modals.main.settings.sections.language.current')}:{' '}
|
||||
<strong style={{ color: 'var(--fg)' }}>{currentLangOption.nativeName}</strong>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ 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 translationPercentages from '@/i18n/translationPercentages.json';
|
||||
import { useT, useTranslation } from 'contexts/TranslationContext';
|
||||
import variables from 'config/variables';
|
||||
|
||||
@@ -23,6 +24,7 @@ function ChooseLanguage() {
|
||||
const mappedLanguages = languages.map((lang) => {
|
||||
const nativeName = lang.name;
|
||||
const isoCode = lang.value.replace('_', '-');
|
||||
const percentage = translationPercentages[lang.value]?.percent || 0;
|
||||
|
||||
let translatedName;
|
||||
try {
|
||||
@@ -30,17 +32,21 @@ function ChooseLanguage() {
|
||||
if (translatedName) {
|
||||
translatedName = translatedName.split(' (')[0];
|
||||
}
|
||||
} catch (e) {
|
||||
} catch {
|
||||
translatedName = nativeName;
|
||||
}
|
||||
|
||||
const displayName =
|
||||
!translatedName || translatedName === nativeName ? (
|
||||
nativeName
|
||||
<>
|
||||
{nativeName} <span style={{ color: '#999', fontSize: '0.85em' }}>({percentage}%)</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{nativeName}{' '}
|
||||
<span style={{ color: '#999', fontSize: '0.85em' }}>({translatedName})</span>
|
||||
<span style={{ color: '#999', fontSize: '0.85em' }}>
|
||||
({translatedName} • {percentage}%)
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -48,6 +54,7 @@ function ChooseLanguage() {
|
||||
name: displayName,
|
||||
value: lang.value,
|
||||
nativeName,
|
||||
percentage,
|
||||
searchText: `${nativeName} ${translatedName || ''}`.toLowerCase(),
|
||||
};
|
||||
});
|
||||
@@ -60,14 +67,16 @@ function ChooseLanguage() {
|
||||
const filteredLanguages = useMemo(() => {
|
||||
if (!searchQuery.trim()) return languageOptions;
|
||||
const query = searchQuery.toLowerCase();
|
||||
return languageOptions.filter(lang => lang.searchText.includes(query));
|
||||
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 (
|
||||
languages.find((l) => l.value === browserLang) ||
|
||||
languages.find((l) => l.value.startsWith(browserLang.split('_')[0]))
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user