GetPreferredAudioLanguagesAsync(CancellationToken cancellationToken) =>
+ dbContext.GetStringSettingAsync(SettingKeys.PreferredAudioLanguages, "", cancellationToken);
+
+ public Task SetPreferredAudioLanguagesAsync(string value, CancellationToken cancellationToken) =>
+ UpsertAsync(SettingKeys.PreferredAudioLanguages, value, cancellationToken);
+
+ private async Task UpsertAsync(string key, string value, CancellationToken cancellationToken)
+ {
var existing = await dbContext.AppSettings.FirstOrDefaultAsync(
- s => s.Key == SettingKeys.RegistrationEnabled,
+ s => s.Key == key,
cancellationToken
);
if (existing is null)
- dbContext.AppSettings.Add(AppSetting.Create(SettingKeys.RegistrationEnabled, value));
+ dbContext.AppSettings.Add(AppSetting.Create(key, value));
else
existing.SetValue(value);
}
diff --git a/backend/tests/TeleWave.Application.Tests/Media/AudioTrackSelectorTests.cs b/backend/tests/TeleWave.Application.Tests/Media/AudioTrackSelectorTests.cs
new file mode 100644
index 0000000..add9fe4
--- /dev/null
+++ b/backend/tests/TeleWave.Application.Tests/Media/AudioTrackSelectorTests.cs
@@ -0,0 +1,51 @@
+using TeleWave.Application.Media;
+using Xunit;
+
+namespace TeleWave.Application.Tests.Media;
+
+public class AudioTrackSelectorTests
+{
+ [Fact]
+ public void PicksFirstPreferredLanguage_ByPriorityOrder()
+ {
+ // Дорожки: eng(0), rus(1), jpn(2). Предпочтение rus,eng → выбираем rus (индекс 1).
+ var track = AudioTrackSelector.Select(["eng", "rus", "jpn"], ["rus", "eng"]);
+ Assert.Equal(1, track);
+ }
+
+ [Fact]
+ public void FallsBackToNextPreferred_WhenTopMissing()
+ {
+ // Нет rus — берём следующий по приоритету eng (индекс 0).
+ var track = AudioTrackSelector.Select(["eng", "jpn"], ["rus", "eng"]);
+ Assert.Equal(0, track);
+ }
+
+ [Fact]
+ public void CaseInsensitiveMatch()
+ {
+ var track = AudioTrackSelector.Select(["ENG", "RUS"], ["rus"]);
+ Assert.Equal(1, track);
+ }
+
+ [Fact]
+ public void NoMatch_ReturnsNull_KeepingFfmpegDefault()
+ {
+ var track = AudioTrackSelector.Select(["eng", "jpn"], ["rus"]);
+ Assert.Null(track);
+ }
+
+ [Fact]
+ public void SingleTrack_ReturnsNull()
+ {
+ var track = AudioTrackSelector.Select(["eng"], ["eng"]);
+ Assert.Null(track);
+ }
+
+ [Fact]
+ public void NoPreference_ReturnsNull()
+ {
+ var track = AudioTrackSelector.Select(["eng", "rus"], []);
+ Assert.Null(track);
+ }
+}
diff --git a/frontend/src/features/admin/settings/SettingsPanel.tsx b/frontend/src/features/admin/settings/SettingsPanel.tsx
index 1d0b4ab..ef2b27e 100644
--- a/frontend/src/features/admin/settings/SettingsPanel.tsx
+++ b/frontend/src/features/admin/settings/SettingsPanel.tsx
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
import { HttpError } from '@/shared/api/client'
import { Button } from '@/shared/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/shared/ui/card'
+import { Input } from '@/shared/ui/input'
import { toast } from '@/shared/ui/toast-store'
import { getSiteSettings, updateSiteSettings } from './api'
@@ -11,6 +12,7 @@ export function SettingsPanel() {
const { t } = useTranslation()
const queryClient = useQueryClient()
const [registrationEnabled, setRegistrationEnabled] = useState(false)
+ const [preferredAudioLanguages, setPreferredAudioLanguages] = useState('')
const { data, isLoading } = useQuery({
queryKey: ['admin', 'settings'],
@@ -18,11 +20,14 @@ export function SettingsPanel() {
})
useEffect(() => {
- if (data) setRegistrationEnabled(data.registrationEnabled)
+ if (data) {
+ setRegistrationEnabled(data.registrationEnabled)
+ setPreferredAudioLanguages(data.preferredAudioLanguages)
+ }
}, [data])
const save = useMutation({
- mutationFn: () => updateSiteSettings({ registrationEnabled }),
+ mutationFn: () => updateSiteSettings({ registrationEnabled, preferredAudioLanguages }),
onSuccess: () => {
toast.success(t('settings.saved'))
void queryClient.invalidateQueries({ queryKey: ['admin', 'settings'] })
@@ -50,6 +55,24 @@ export function SettingsPanel() {
/>
{t('admin.settings.registrationLabel')}
+
+
+
+
setPreferredAudioLanguages(e.target.value)}
+ />
+
+ {t('admin.settings.preferredAudioHint')}
+
+
+