Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lyrics Translation and Romaji (Fulfill #732) [Translation Part] #747

Merged
merged 25 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
08a9624
modified: package.json
ENDlezZenith Sep 13, 2024
f63dc01
Lyrics Translation Settings Init and Typo Revert
ENDlezZenith Sep 15, 2024
6334920
Merge branch 'jeffvli:development' into development
ENDlezZenith Sep 16, 2024
a0f1d56
Lib Type Add and Default Value
ENDlezZenith Sep 16, 2024
9135c3a
Merge branch 'development' of https://github.com/DrENDzZ/feishin into…
ENDlezZenith Sep 16, 2024
2e3e0fe
Merge branch 'jeffvli:development' into development
ENDlezZenith Sep 17, 2024
814714d
Init Translation API
ENDlezZenith Sep 17, 2024
164138e
Translation Module Usable
ENDlezZenith Sep 17, 2024
0a245a2
Translation Toggle
ENDlezZenith Sep 17, 2024
57e165b
Romaji Test
ENDlezZenith Sep 19, 2024
41b5635
Merge branch 'jeffvli:development' into development
ENDlezZenith Sep 19, 2024
063b419
Trivial Language Config and Font Size Change
ENDlezZenith Sep 19, 2024
942c55d
Merge branch 'development' of https://github.com/DrENDzZ/feishin into…
ENDlezZenith Sep 19, 2024
0bc8d84
Fallback Fix Dot in Path Error
ENDlezZenith Sep 19, 2024
5215298
Merge branch 'jeffvli:development' into development
ENDlezZenith Sep 19, 2024
8d796ae
Final Lint for Translation Module
ENDlezZenith Sep 19, 2024
f2cf2c3
Merge branch 'jeffvli:development' into development
ENDlezZenith Sep 20, 2024
83b981e
Add Google Cloud Translation API Support
ENDlezZenith Sep 20, 2024
d8b1b8d
Merge branch 'development' of https://github.com/DrENDzZ/feishin into…
ENDlezZenith Sep 20, 2024
c6be6ff
Clear Unused Libraries
ENDlezZenith Sep 20, 2024
b50f07d
Lint
ENDlezZenith Sep 20, 2024
9983a92
Revert All Romaji Related
ENDlezZenith Sep 20, 2024
930332d
Hide Settings in Web
ENDlezZenith Sep 21, 2024
75116c7
Endpoint Scope Note and Implement Directory Change
ENDlezZenith Sep 23, 2024
9c0190a
lyrics translation i18n
ENDlezZenith Sep 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,8 @@
"@tanstack/react-query-persist-client": "^4.32.1",
"@ts-rest/core": "^3.23.0",
"@xhayper/discord-rpc": "^1.0.24",
"auto-text-size": "^0.2.3",
"audiomotion-analyzer": "^4.5.0",
"auto-text-size": "^0.2.3",
"axios": "^1.6.0",
"clsx": "^2.0.0",
"cmdk": "^0.2.0",
Expand Down
3 changes: 2 additions & 1 deletion release/app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"trackNumber": "track",
"trackGain": "track gain",
"trackPeak": "track peak",
"translation": "translation",
"unknown": "unknown",
"version": "version",
"year": "year",
Expand Down Expand Up @@ -355,7 +356,8 @@
},
"lyrics": "lyrics",
"related": "related",
"upNext": "up next"
"upNext": "up next",
"visualizer": "visualizer"
},
"genreList": {
"showAlbums": "show $t(entity.genre_one) $t(entity.album_other)",
Expand Down Expand Up @@ -456,6 +458,8 @@
"albumBackground_description": "adds a background image for album pages containing the album art",
"albumBackgroundBlur": "album background image blur size",
"albumBackgroundBlur_description": "adjusts the amount of blur applied to the album background image",
"apiProvider": "api provider",
"apiKey": "api key",
"applicationHotkeys": "application hotkeys",
"applicationHotkeys_description": "configure application hotkeys. toggle the checkbox to set as a global hotkey (desktop only)",
"artistConfiguration": "album artist page configuration",
Expand Down Expand Up @@ -640,6 +644,7 @@
"skipPlaylistPage_description": "when navigating to a playlist, go to the playlist song list page instead of the default page",
"startMinimized": "start minimized",
"startMinimized_description": "start the application in system tray",
"targetLanguage": "target language",
"theme": "theme",
"theme_description": "sets the theme to use for the application",
"themeDark": "theme (dark)",
Expand Down
16 changes: 15 additions & 1 deletion src/renderer/features/lyrics/lyrics-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface LyricsActionsProps {
onRemoveLyric: () => void;
onResetLyric: () => void;
onSearchOverride: (params: LyricsOverride) => void;
onTranslateLyric: () => void;
setIndex: (idx: number) => void;
}

Expand All @@ -28,6 +29,7 @@ export const LyricsActions = ({
onRemoveLyric,
onResetLyric,
onSearchOverride,
onTranslateLyric,
setIndex,
}: LyricsActionsProps) => {
const { t } = useTranslation();
Expand Down Expand Up @@ -120,7 +122,6 @@ export const LyricsActions = ({
{isDesktop && sources.length ? (
<Button
uppercase
color="red"
disabled={isActionsDisabled}
variant="subtle"
onClick={onRemoveLyric}
Expand All @@ -129,6 +130,19 @@ export const LyricsActions = ({
</Button>
) : null}
</Box>

<Box style={{ position: 'absolute', right: 0, top: -50 }}>
{isDesktop && sources.length ? (
<Button
uppercase
disabled={isActionsDisabled}
variant="subtle"
onClick={onTranslateLyric}
>
{t('common.translation', { postProcess: 'sentenceCase' })}
</Button>
) : null}
</Box>
</Box>
);
};
59 changes: 44 additions & 15 deletions src/renderer/features/lyrics/lyrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { ErrorBoundary } from 'react-error-boundary';
import { RiInformationFill } from 'react-icons/ri';
import styled from 'styled-components';
import { useSongLyricsByRemoteId, useSongLyricsBySong } from './queries/lyric-query';
import { translateLyrics } from './queries/lyric-translate';
import { SynchronizedLyrics, SynchronizedLyricsProps } from './synchronized-lyrics';
import { Spinner, TextTitle } from '/@/renderer/components';
import { ErrorFallback } from '/@/renderer/features/action-required';
import {
UnsynchronizedLyrics,
UnsynchronizedLyricsProps,
} from '/@/renderer/features/lyrics/unsynchronized-lyrics';
import { useCurrentSong, usePlayerStore } from '/@/renderer/store';
import { useCurrentSong, usePlayerStore, useLyricsSettings } from '/@/renderer/store';
import { FullLyricsMetadata, LyricSource, LyricsOverride } from '/@/renderer/api/types';
import { LyricsActions } from '/@/renderer/features/lyrics/lyrics-actions';
import { queryKeys } from '/@/renderer/api/query-keys';
Expand Down Expand Up @@ -84,7 +85,10 @@ const ScrollContainer = styled(motion.div)`

export const Lyrics = () => {
const currentSong = useCurrentSong();
const lyricsSettings = useLyricsSettings();
const [index, setIndex] = useState(0);
const [translatedLyrics, setTranslatedLyrics] = useState<string | null>(null);
const [showTranslation, setShowTranslation] = useState(false);

const { data, isInitialLoading } = useSongLyricsBySong(
{
Expand All @@ -96,6 +100,19 @@ export const Lyrics = () => {

const [override, setOverride] = useState<LyricsOverride | undefined>(undefined);

const [lyrics, synced] = useMemo(() => {
if (Array.isArray(data)) {
if (data.length > 0) {
const selectedLyric = data[Math.min(index, data.length)];
return [selectedLyric, selectedLyric.synced];
}
} else if (data?.lyrics) {
return [data, Array.isArray(data.lyrics)];
}

return [undefined, false];
}, [data, index]);

const handleOnSearchOverride = useCallback((params: LyricsOverride) => {
setOverride(params);
}, []);
Expand Down Expand Up @@ -123,6 +140,26 @@ export const Lyrics = () => {
);
}, [currentSong?.id, currentSong?.serverId]);

const handleOnTranslateLyric = useCallback(async () => {
if (translatedLyrics) {
setShowTranslation(!showTranslation);
return;
}
if (!lyrics) return;
const originalLyrics = Array.isArray(lyrics.lyrics)
? lyrics.lyrics.map(([, line]) => line).join('\n')
: lyrics.lyrics;
const { apiKey, apiProvider, targetLanguage } = lyricsSettings;
const TranslatedText: string | null = await translateLyrics(
originalLyrics,
apiKey,
apiProvider,
targetLanguage,
);
setTranslatedLyrics(TranslatedText);
setShowTranslation(true);
}, [lyrics, lyricsSettings, translatedLyrics, showTranslation]);

const { isInitialLoading: isOverrideLoading } = useSongLyricsByRemoteId({
options: {
enabled: !!override,
Expand Down Expand Up @@ -150,19 +187,6 @@ export const Lyrics = () => {
};
}, []);

const [lyrics, synced] = useMemo(() => {
if (Array.isArray(data)) {
if (data.length > 0) {
const selectedLyric = data[Math.min(index, data.length)];
return [selectedLyric, selectedLyric.synced];
}
} else if (data?.lyrics) {
return [data, Array.isArray(data.lyrics)];
}

return [undefined, false];
}, [data, index]);

const languages = useMemo(() => {
if (Array.isArray(data)) {
return data.map((lyric, idx) => ({ label: lyric.lang, value: idx.toString() }));
Expand Down Expand Up @@ -203,10 +227,14 @@ export const Lyrics = () => {
transition={{ duration: 0.5 }}
>
{synced ? (
<SynchronizedLyrics {...(lyrics as SynchronizedLyricsProps)} />
<SynchronizedLyrics
{...(lyrics as SynchronizedLyricsProps)}
translatedLyrics={showTranslation ? translatedLyrics : null}
/>
) : (
<UnsynchronizedLyrics
{...(lyrics as UnsynchronizedLyricsProps)}
translatedLyrics={showTranslation ? translatedLyrics : null}
/>
)}
</ScrollContainer>
Expand All @@ -221,6 +249,7 @@ export const Lyrics = () => {
onRemoveLyric={handleOnRemoveLyric}
onResetLyric={handleOnResetLyric}
onSearchOverride={handleOnSearchOverride}
onTranslateLyric={handleOnTranslateLyric}
/>
</ActionsContainer>
</LyricsContainer>
Expand Down
50 changes: 50 additions & 0 deletions src/renderer/features/lyrics/queries/lyric-translate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import axios from 'axios';

export const translateLyrics = async (
originalLyrics: string,
apiKey: string,
apiProvider: string | null,
targetLanguage: string | null,
) => {
let TranslatedText = '';
if (apiProvider === 'Microsoft Azure') {
try {
const response = await axios({
data: [
{
Text: originalLyrics,
},
],
headers: {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': apiKey,
},
method: 'post',
url: `https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=${targetLanguage as string}`,
});
TranslatedText = response.data[0].translations[0].text;
} catch (e) {
console.error('Microsoft Azure translate request got an error!', e);
return null;
}
} else if (apiProvider === 'Google Cloud') {
try {
const response = await axios({
data: {
format: 'text',
q: originalLyrics,
},
headers: {
'Content-Type': 'application/json',
},
method: 'post',
url: `https://translation.googleapis.com/language/translate/v2?target=${targetLanguage as string}&key=${apiKey}`,
});
TranslatedText = response.data.data.translations[0].translatedText;
} catch (e) {
console.error('Google Cloud translate request got an error!', e);
return null;
}
}
return TranslatedText;
};
30 changes: 21 additions & 9 deletions src/renderer/features/lyrics/synchronized-lyrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const SynchronizedLyricsContainer = styled.div<{ $gap: number }>`

export interface SynchronizedLyricsProps extends Omit<FullLyricsMetadata, 'lyrics'> {
lyrics: SynchronizedLyricsArray;
translatedLyrics?: string | null;
}

export const SynchronizedLyrics = ({
Expand All @@ -63,6 +64,7 @@ export const SynchronizedLyrics = ({
name,
remote,
source,
translatedLyrics,
}: SynchronizedLyricsProps) => {
const playersRef = PlayersRef;
const status = useCurrentStatus();
Expand Down Expand Up @@ -364,15 +366,25 @@ export const SynchronizedLyrics = ({
/>
)}
{lyrics.map(([time, text], idx) => (
<LyricLine
key={idx}
alignment={settings.alignment}
className="lyric-line synchronized"
fontSize={settings.fontSize}
id={`lyric-${idx}`}
text={text}
onClick={() => handleSeek(time / 1000)}
/>
<div key={idx}>
<LyricLine
alignment={settings.alignment}
className="lyric-line synchronized"
fontSize={settings.fontSize}
id={`lyric-${idx}`}
text={text}
onClick={() => handleSeek(time / 1000)}
/>
{translatedLyrics && (
<LyricLine
alignment={settings.alignment}
className="lyric-line synchronized translation"
fontSize={settings.fontSize * 0.8}
text={translatedLyrics.split('\n')[idx]}
onClick={() => handleSeek(time / 1000)}
/>
)}
</div>
))}
</SynchronizedLyricsContainer>
);
Expand Down
Loading
Loading