Fix Lyric Translation Not Resetting (#1208)

* Refactor lyrics translation and index handling

* Add auto translation toggle to lyric settings

* Add enableAutoTranslation setting to lyrics
This commit is contained in:
Xudong Zhou 2025-11-01 08:08:10 +08:00 committed by GitHub
parent d12e4a1635
commit dd34888961
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 51 additions and 18 deletions

View file

@ -507,7 +507,6 @@
"artistBackgroundBlur_description": "adjusts the amount of blur applied to the artist background image", "artistBackgroundBlur_description": "adjusts the amount of blur applied to the artist background image",
"artistConfiguration": "album artist page configuration", "artistConfiguration": "album artist page configuration",
"artistConfiguration_description": "configure what items are shown, and in what order, on the album artist page", "artistConfiguration_description": "configure what items are shown, and in what order, on the album artist page",
"artistConfiguration": "album artist page configuration",
"audioDevice_description": "select the audio device to use for playback (web player only)", "audioDevice_description": "select the audio device to use for playback (web player only)",
"audioDevice": "audio device", "audioDevice": "audio device",
"audioExclusiveMode_description": "enable exclusive output mode. In this mode, the system is usually locked out, and only mpv will be able to output audio", "audioExclusiveMode_description": "enable exclusive output mode. In this mode, the system is usually locked out, and only mpv will be able to output audio",
@ -560,9 +559,10 @@
"discordServeImage_description": "share cover art for {{discord}} rich presence from server itself, only available for Jellyfin and Navidrome. {{discord}} uses a bot to fetch images, so your server must be reachable from the public internet", "discordServeImage_description": "share cover art for {{discord}} rich presence from server itself, only available for Jellyfin and Navidrome. {{discord}} uses a bot to fetch images, so your server must be reachable from the public internet",
"discordUpdateInterval": "{{discord}} rich presence update interval", "discordUpdateInterval": "{{discord}} rich presence update interval",
"discordUpdateInterval_description": "the time in seconds between each update (minimum 15 seconds)", "discordUpdateInterval_description": "the time in seconds between each update (minimum 15 seconds)",
"discordUpdateInterval": "{{discord}} rich presence update interval",
"doubleClickBehavior_description": "if true, all matching tracks in a track search will be queued. otherwise, only the clicked one will be queued", "doubleClickBehavior_description": "if true, all matching tracks in a track search will be queued. otherwise, only the clicked one will be queued",
"doubleClickBehavior": "queue all searched tracks when double clicking", "doubleClickBehavior": "queue all searched tracks when double clicking",
"enableAutoTranslation_description": "enable translation automatically when lyrics are loaded",
"enableAutoTranslation": "enable auto translation",
"enableRemote_description": "enables the remote control server to allow other devices to control the application", "enableRemote_description": "enables the remote control server to allow other devices to control the application",
"enableRemote": "enable remote control server", "enableRemote": "enable remote control server",
"exitToTray_description": "exit the application to the system tray", "exitToTray_description": "exit the application to the system tray",

View file

@ -32,8 +32,12 @@ import { FullLyricsMetadata, LyricSource, LyricsOverride } from '/@/shared/types
export const Lyrics = () => { export const Lyrics = () => {
const currentSong = useCurrentSong(); const currentSong = useCurrentSong();
const { translationApiKey, translationApiProvider, translationTargetLanguage } = const {
useLyricsSettings(); enableAutoTranslation,
translationApiKey,
translationApiProvider,
translationTargetLanguage,
} = useLyricsSettings();
const { t } = useTranslation(); const { t } = useTranslation();
const [index, setIndex] = useState(0); const [index, setIndex] = useState(0);
const [translatedLyrics, setTranslatedLyrics] = useState<null | string>(null); const [translatedLyrics, setTranslatedLyrics] = useState<null | string>(null);
@ -52,7 +56,7 @@ export const Lyrics = () => {
const [lyrics, synced] = useMemo(() => { const [lyrics, synced] = useMemo(() => {
if (Array.isArray(data)) { if (Array.isArray(data)) {
if (data.length > 0) { if (data.length > 0) {
const selectedLyric = data[Math.min(index, data.length)]; const selectedLyric = data[Math.min(index, data.length - 1)];
return [selectedLyric, selectedLyric.synced]; return [selectedLyric, selectedLyric.synced];
} }
} else if (data?.lyrics) { } else if (data?.lyrics) {
@ -89,11 +93,7 @@ export const Lyrics = () => {
); );
}, [currentSong?.id, currentSong?.serverId]); }, [currentSong?.id, currentSong?.serverId]);
const handleOnTranslateLyric = useCallback(async () => { const fetchTranslation = useCallback(async () => {
if (translatedLyrics) {
setShowTranslation(!showTranslation);
return;
}
if (!lyrics) return; if (!lyrics) return;
const originalLyrics = Array.isArray(lyrics.lyrics) const originalLyrics = Array.isArray(lyrics.lyrics)
? lyrics.lyrics.map(([, line]) => line).join('\n') ? lyrics.lyrics.map(([, line]) => line).join('\n')
@ -106,14 +106,15 @@ export const Lyrics = () => {
); );
setTranslatedLyrics(TranslatedText); setTranslatedLyrics(TranslatedText);
setShowTranslation(true); setShowTranslation(true);
}, [ }, [lyrics, translationApiKey, translationApiProvider, translationTargetLanguage]);
translatedLyrics,
lyrics, const handleOnTranslateLyric = useCallback(async () => {
translationApiKey, if (translatedLyrics) {
translationApiProvider, setShowTranslation(!showTranslation);
translationTargetLanguage, return;
showTranslation, }
]); await fetchTranslation();
}, [translatedLyrics, showTranslation, fetchTranslation]);
const { isInitialLoading: isOverrideLoading } = useSongLyricsByRemoteId({ const { isInitialLoading: isOverrideLoading } = useSongLyricsByRemoteId({
options: { options: {
@ -133,6 +134,8 @@ export const Lyrics = () => {
() => { () => {
setOverride(undefined); setOverride(undefined);
setIndex(0); setIndex(0);
setShowTranslation(false);
setTranslatedLyrics(null);
}, },
{ equalityFn: (a, b) => a?.id === b?.id }, { equalityFn: (a, b) => a?.id === b?.id },
); );
@ -142,6 +145,12 @@ export const Lyrics = () => {
}; };
}, []); }, []);
useEffect(() => {
if (lyrics && !translatedLyrics && enableAutoTranslation) {
fetchTranslation();
}
}, [lyrics, translatedLyrics, enableAutoTranslation, fetchTranslation]);
const languages = useMemo(() => { const languages = useMemo(() => {
if (Array.isArray(data)) { if (Array.isArray(data)) {
return data.map((lyric, idx) => ({ label: lyric.lang, value: idx.toString() })); return data.map((lyric, idx) => ({ label: lyric.lang, value: idx.toString() }));

View file

@ -214,6 +214,28 @@ export const LyricSettings = () => {
isHidden: !isElectron(), isHidden: !isElectron(),
title: t('setting.translationApiKey', { postProcess: 'sentenceCase' }), title: t('setting.translationApiKey', { postProcess: 'sentenceCase' }),
}, },
{
control: (
<Switch
aria-label="Enable auto translation"
defaultChecked={settings.enableAutoTranslation}
onChange={(e) => {
setSettings({
lyrics: {
...settings,
enableAutoTranslation: e.currentTarget.checked,
},
});
}}
/>
),
description: t('setting.enableAutoTranslation', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: !isElectron(),
title: t('setting.enableAutoTranslation', { postProcess: 'sentenceCase' }),
},
]; ];
return <SettingsSection divider={false} options={lyricOptions} />; return <SettingsSection divider={false} options={lyricOptions} />;

View file

@ -225,6 +225,7 @@ const HotkeysSettingsSchema = z.object({
const LyricsSettingsSchema = z.object({ const LyricsSettingsSchema = z.object({
alignment: z.enum(['center', 'left', 'right']), alignment: z.enum(['center', 'left', 'right']),
delayMs: z.number(), delayMs: z.number(),
enableAutoTranslation: z.boolean(),
enableNeteaseTranslation: z.boolean(), enableNeteaseTranslation: z.boolean(),
fetch: z.boolean(), fetch: z.boolean(),
follow: z.boolean(), follow: z.boolean(),
@ -612,6 +613,7 @@ const initialState: SettingsState = {
lyrics: { lyrics: {
alignment: 'center', alignment: 'center',
delayMs: 0, delayMs: 0,
enableAutoTranslation: false,
enableNeteaseTranslation: false, enableNeteaseTranslation: false,
fetch: false, fetch: false,
follow: true, follow: true,