Skip to content

Commit 2d9b637

Browse files
authored
feat(lyrics-plus): translation display individual copy implementation (#3308)
1 parent 9ef3366 commit 2d9b637

File tree

5 files changed

+178
-60
lines changed

5 files changed

+178
-60
lines changed

CustomApps/lyrics-plus/OptionsMenu.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,6 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => {
9292
none: "None",
9393
};
9494

95-
const savedTranslationDisplay = localStorage.getItem(`${APP_NAME}:visual:translate:display-mode`) || "replace";
96-
CONFIG.visual["translate:display-mode"] = savedTranslationDisplay;
9795
const translationDisplayOptions = {
9896
replace: "Replace original",
9997
below: "Below original",
@@ -168,13 +166,7 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => {
168166
desc: "Translation Display",
169167
key: "translate:display-mode",
170168
type: ConfigSelection,
171-
onChange: (name, value) => {
172-
CONFIG.visual[name] = value;
173-
localStorage.setItem(`${APP_NAME}:visual:${name}`, value);
174-
lyricContainerUpdate?.();
175-
},
176169
options: translationDisplayOptions,
177-
defaultValue: savedTranslationDisplay,
178170
renderInline: true,
179171
},
180172
{
@@ -183,13 +175,17 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => {
183175
type: ConfigSelection,
184176
options: languageOptions,
185177
renderInline: true,
178+
// for songs in languages that support translation but not Convert (e.g., English), the option is disabled.
179+
when: () => friendlyLanguage,
186180
},
187181
{
188182
desc: "Display Mode",
189183
key: `translation-mode:${friendlyLanguage}`,
190184
type: ConfigSelection,
191185
options: modeOptions,
192186
renderInline: true,
187+
// for songs in languages that support translation but not Convert (e.g., English), the option is disabled.
188+
when: () => friendlyLanguage,
193189
},
194190
{
195191
desc: "Convert",
@@ -198,6 +194,8 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => {
198194
trigger: "click",
199195
action: "toggle",
200196
renderInline: true,
197+
// for songs in languages that support translation but not Convert (e.g., English), the option is disabled.
198+
when: () => friendlyLanguage,
201199
},
202200
];
203201
}, [friendlyLanguage]);
@@ -235,6 +233,15 @@ const TranslationMenu = react.memo(({ friendlyLanguage, hasTranslation }) => {
235233
type: "translation-menu",
236234
items,
237235
onChange: (name, value) => {
236+
if (name === "translate:translated-lyrics-source" && friendlyLanguage) {
237+
CONFIG.visual.translate = false;
238+
localStorage.setItem(`${APP_NAME}:visual:translate`, false);
239+
}
240+
if (name === "translate") {
241+
CONFIG.visual["translate:translated-lyrics-source"] = "none";
242+
localStorage.setItem(`${APP_NAME}:visual:translate:translated-lyrics-source`, "none");
243+
}
244+
238245
CONFIG.visual[name] = value;
239246
localStorage.setItem(`${APP_NAME}:visual:${name}`, value);
240247
lyricContainerUpdate?.();

CustomApps/lyrics-plus/Pages.js

Lines changed: 76 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,6 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara
123123
offset += -(activeLineEle.current.offsetTop + activeLineEle.current.clientHeight / 2);
124124
}
125125

126-
const rawLyrics = Utils.convertParsedToLRC(lyrics);
127-
128126
return react.createElement(
129127
"div",
130128
{
@@ -174,6 +172,12 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara
174172
// Otherwise we should show the translated text
175173
const lineText = originalText && showTranslatedBelow ? originalText : text;
176174

175+
// Convert lyrics to text for comparison
176+
const belowOrigin = typeof originalText === "object" ? originalText?.props?.children?.[0] : originalText;
177+
const belowTxt = typeof text === "object" ? text?.props?.children?.[0] : text;
178+
179+
const belowMode = showTranslatedBelow && originalText && belowOrigin !== belowTxt;
180+
177181
return react.createElement(
178182
"div",
179183
{
@@ -192,23 +196,32 @@ const SyncedLyricsPage = react.memo(({ lyrics = [], provider, copyright, isKara
192196
Spicetify.Player.seek(startTime);
193197
}
194198
},
195-
onContextMenu: (event) => {
196-
event.preventDefault();
197-
Spicetify.Platform.ClipboardAPI.copy(rawLyrics)
198-
.then(() => Spicetify.showNotification("Lyrics copied to clipboard"))
199-
.catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard"));
200-
},
201199
},
202-
react.createElement("p", {}, !isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive })),
203-
showTranslatedBelow &&
204-
originalText &&
205-
originalText !== text &&
200+
react.createElement(
201+
"p",
202+
{
203+
onContextMenu: (event) => {
204+
event.preventDefault();
205+
Spicetify.Platform.ClipboardAPI.copy(Utils.convertParsedToLRC(lyrics, belowMode).original)
206+
.then(() => Spicetify.showNotification("Lyrics copied to clipboard"))
207+
.catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard"));
208+
},
209+
},
210+
!isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive })
211+
),
212+
belowMode &&
206213
react.createElement(
207214
"p",
208215
{
209216
style: {
210217
opacity: 0.5,
211218
},
219+
onContextMenu: (event) => {
220+
event.preventDefault();
221+
Spicetify.Platform.ClipboardAPI.copy(Utils.convertParsedToLRC(lyrics, belowMode).conver)
222+
.then(() => Spicetify.showNotification("Translated lyrics copied to clipboard"))
223+
.catch(() => Spicetify.showNotification("Failed to copy translated lyrics to clipboard"));
224+
},
212225
},
213226
text
214227
)
@@ -405,8 +418,6 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa
405418
}
406419
}
407420

408-
const rawLyrics = Utils.convertParsedToLRC(lyrics);
409-
410421
useEffect(() => {
411422
if (activeLineRef.current && (!intialScroll[0] || isInViewport(activeLineRef.current))) {
412423
activeLineRef.current.scrollIntoView({
@@ -442,6 +453,13 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa
442453
// If we have original text and we are showing translated below, we should show the original text
443454
// Otherwise we should show the translated text
444455
const lineText = originalText && showTranslatedBelow ? originalText : text;
456+
457+
// Convert lyrics to text for comparison
458+
const belowOrigin = typeof originalText === "object" ? originalText?.props?.children?.[0] : originalText;
459+
const belowTxt = typeof text === "object" ? text?.props?.children?.[0] : text;
460+
461+
const belowMode = showTranslatedBelow && originalText && belowOrigin !== belowTxt;
462+
445463
return react.createElement(
446464
"div",
447465
{
@@ -457,21 +475,30 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa
457475
Spicetify.Player.seek(startTime);
458476
}
459477
},
460-
onContextMenu: (event) => {
461-
event.preventDefault();
462-
Spicetify.Platform.ClipboardAPI.copy(rawLyrics)
463-
.then(() => Spicetify.showNotification("Lyrics copied to clipboard"))
464-
.catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard"));
465-
},
466478
},
467-
react.createElement("p", {}, !isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive })),
468-
showTranslatedBelow &&
469-
originalText &&
470-
originalText !== text &&
479+
react.createElement(
480+
"p",
481+
{
482+
onContextMenu: (event) => {
483+
event.preventDefault();
484+
Spicetify.Platform.ClipboardAPI.copy(Utils.convertParsedToLRC(lyrics, belowMode).original)
485+
.then(() => Spicetify.showNotification("Lyrics copied to clipboard"))
486+
.catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard"));
487+
},
488+
},
489+
!isKara ? lineText : react.createElement(KaraokeLine, { text, startTime, position, isActive })
490+
),
491+
belowMode &&
471492
react.createElement(
472493
"p",
473494
{
474495
style: { opacity: 0.5 },
496+
onContextMenu: (event) => {
497+
event.preventDefault();
498+
Spicetify.Platform.ClipboardAPI.copy(Utils.convertParsedToLRC(lyrics, belowMode).conver)
499+
.then(() => Spicetify.showNotification("Translated lyrics copied to clipboard"))
500+
.catch(() => Spicetify.showNotification("Failed to copy translated lyrics to clipboard"));
501+
},
475502
},
476503
text
477504
)
@@ -489,8 +516,6 @@ const SyncedExpandedLyricsPage = react.memo(({ lyrics, provider, copyright, isKa
489516
});
490517

491518
const UnsyncedLyricsPage = react.memo(({ lyrics, provider, copyright }) => {
492-
const rawLyrics = lyrics.map((lyrics) => (typeof lyrics.text !== "object" ? lyrics.text : lyrics.text?.props?.children?.[0])).join("\n");
493-
494519
return react.createElement(
495520
"div",
496521
{
@@ -505,27 +530,42 @@ const UnsyncedLyricsPage = react.memo(({ lyrics, provider, copyright }) => {
505530
// Otherwise we should show the translated text
506531
const lineText = originalText && showTranslatedBelow ? originalText : text;
507532

533+
// Convert lyrics to text for comparison
534+
const belowOrigin = typeof originalText === "object" ? originalText?.props?.children?.[0] : originalText;
535+
const belowTxt = typeof text === "object" ? text?.props?.children?.[0] : text;
536+
537+
const belowMode = showTranslatedBelow && originalText && belowOrigin !== belowTxt;
538+
508539
return react.createElement(
509540
"div",
510541
{
511542
className: "lyrics-lyricsContainer-LyricsLine lyrics-lyricsContainer-LyricsLine-active",
512543
key: index,
513544
dir: "auto",
514-
onContextMenu: (event) => {
515-
event.preventDefault();
516-
Spicetify.Platform.ClipboardAPI.copy(rawLyrics)
517-
.then(() => Spicetify.showNotification("Lyrics copied to clipboard"))
518-
.catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard"));
519-
},
520545
},
521-
react.createElement("p", {}, lineText),
522-
showTranslatedBelow &&
523-
originalText &&
524-
originalText !== text &&
546+
react.createElement(
547+
"p",
548+
{
549+
onContextMenu: (event) => {
550+
event.preventDefault();
551+
Spicetify.Platform.ClipboardAPI.copy(Utils.convertParsedToUnsynced(lyrics, belowMode).original)
552+
.then(() => Spicetify.showNotification("Lyrics copied to clipboard"))
553+
.catch(() => Spicetify.showNotification("Failed to copy lyrics to clipboard"));
554+
},
555+
},
556+
lineText
557+
),
558+
belowMode &&
525559
react.createElement(
526560
"p",
527561
{
528562
style: { opacity: 0.5 },
563+
onContextMenu: (event) => {
564+
event.preventDefault();
565+
Spicetify.Platform.ClipboardAPI.copy(Utils.convertParsedToUnsynced(lyrics, belowMode).conver)
566+
.then(() => Spicetify.showNotification("Translated lyrics copied to clipboard"))
567+
.catch(() => Spicetify.showNotification("Failed to copy translated lyrics to clipboard"));
568+
},
529569
},
530570
text
531571
)

CustomApps/lyrics-plus/Settings.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ const RefreshTokenButton = ({ setTokenCallback }) => {
109109
const ConfigSlider = ({ name, defaultValue, onChange = () => {} }) => {
110110
const [active, setActive] = useState(defaultValue);
111111

112+
useEffect(() => {
113+
setActive(defaultValue);
114+
}, [defaultValue]);
115+
112116
const toggleState = useCallback(() => {
113117
const state = !active;
114118
setActive(state);
@@ -512,6 +516,7 @@ const OptionList = ({ type, items, onChange }) => {
512516
);
513517
});
514518
};
519+
515520
const languageCodes =
516521
"none,en,af,ar,bg,bn,ca,zh,cs,da,de,el,es,et,fa,fi,fr,gu,he,hi,hr,hu,id,is,it,ja,jv,kn,ko,lt,lv,ml,mr,ms,nl,no,pl,pt,ro,ru,sk,sl,sr,su,sv,ta,te,th,tr,uk,ur,vi,zu".split(
517522
","
@@ -523,9 +528,6 @@ const languageOptions = languageCodes.reduce((acc, code) => {
523528
return acc;
524529
}, {});
525530

526-
const savedLanguage = localStorage.getItem(`${APP_NAME}:visual:musixmatch-translation-language`) || "none";
527-
CONFIG.visual["musixmatch-translation-language"] = savedLanguage;
528-
529531
function openConfig() {
530532
const configContainer = react.createElement(
531533
"div",
@@ -645,17 +647,22 @@ function openConfig() {
645647
},
646648
{
647649
desc: "Musixmatch Translation Language.",
648-
info: "Choose the language you want to translate the lyrics to. Changes will take effect after the next track.",
650+
info: "Choose the language you want to translate the lyrics to. When the language is changed, the lyrics reloads.",
649651
key: "musixmatch-translation-language",
650652
type: ConfigSelection,
651653
options: languageOptions,
652-
defaultValue: savedLanguage,
653654
},
654655
],
655656
onChange: (name, value) => {
656657
CONFIG.visual[name] = value;
657658
localStorage.setItem(`${APP_NAME}:visual:${name}`, value);
658-
lyricContainerUpdate?.();
659+
660+
// Reload Lyrics if translation language is changed
661+
if (name === "musixmatch-translation-language") {
662+
reloadLyrics?.();
663+
} else {
664+
lyricContainerUpdate?.();
665+
}
659666

660667
const configChange = new CustomEvent("lyrics-plus", {
661668
detail: {
@@ -673,15 +680,17 @@ function openConfig() {
673680
onListChange: (list) => {
674681
CONFIG.providersOrder = list;
675682
localStorage.setItem(`${APP_NAME}:services-order`, JSON.stringify(list));
683+
reloadLyrics?.();
676684
},
677685
onToggle: (name, value) => {
678686
CONFIG.providers[name].on = value;
679687
localStorage.setItem(`${APP_NAME}:provider:${name}:on`, value);
680-
lyricContainerUpdate?.();
688+
reloadLyrics?.();
681689
},
682690
onTokenChange: (name, value) => {
683691
CONFIG.providers[name].token = value;
684692
localStorage.setItem(`${APP_NAME}:provider:${name}:token`, value);
693+
reloadLyrics?.();
685694
},
686695
}),
687696
react.createElement("h2", null, "CORS Proxy Template"),

0 commit comments

Comments
 (0)