Skip to content

Conversation

@ianz56
Copy link
Contributor

@ianz56 ianz56 commented Oct 21, 2025

Added dynamic language selection for Musixmatch translations. The dropdown now reflects available languages per track.

image image

Summary by CodeRabbit

  • New Features

    • Multi-language Musixmatch translations: choose from available Musixmatch languages and apply per track.
    • Translation source normalization: Musixmatch-prefixed sources are recognized and available languages are shown in the translation menu.
    • Automatic fetching and caching of Musixmatch translations with language-aware refresh.
  • Bug Fixes

    • UI and stored configuration stay synchronized when changing Musixmatch translation language.
    • Translations refresh reliably on language or track changes.

@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

Walkthrough

Adds multi-language Musixmatch translation support: new translation-prefix persistence, translation-source normalization, extraction of available translation statuses from API responses, trackId-based translation fetching, UI options for Musixmatch languages, state fields and caching, and config/localStorage synchronization.

Changes

Cohort / File(s) Summary
Provider & API changes
CustomApps/lyrics-plus/ProviderMusixmatch.js,
CustomApps/lyrics-plus/Providers.js
Extract track_lyrics_translation_status recursively from Musixmatch responses; include track_lyrics_translation_status in queries; augment response bodies with __musixmatchTranslationStatus and __musixmatchTrackId; change getTranslation signature to accept trackId; add musixmatchAvailableTranslations, musixmatchTrackId, and musixmatchTranslationLanguage to provider results; map/apply translations when available.
UI: Options & Settings
CustomApps/lyrics-plus/OptionsMenu.js,
CustomApps/lyrics-plus/Settings.js
Add getMusixmatchTranslationPrefix() helper; expand TranslationMenu props to accept musixmatchLanguages and musixmatchSelectedLanguage; dynamically build Musixmatch-prefixed translation options with Intl.DisplayNames; persist/sync selected Musixmatch language to CONFIG and localStorage; centralize updates to use lyricContainerUpdate() and adjust change handling for Musixmatch source selection.
Orchestration / Container
CustomApps/lyrics-plus/index.js
Add translation prefix persistence and resolveTranslationSource(); add state fields musixmatchAvailableTranslations, musixmatchTrackId, musixmatchTranslationLanguage, and currentMusixmatchLanguage; implement refreshMusixmatchTranslation() to fetch/apply/cached translations per URI; wire UI props and trigger refresh/reset on track/language/config changes; integrate translation overrides into final state/caching.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant OptionsMenu
    participant Settings
    participant LyricsContainer
    participant Providers
    participant ProviderMusixmatch
    participant MusixmatchAPI as API

    User->>OptionsMenu: select Musixmatch language
    OptionsMenu->>Settings: update translate:translated-lyrics-source + musixmatch-translation-language
    Settings->>LyricsContainer: lyricContainerUpdate()

    LyricsContainer->>LyricsContainer: resolveTranslationSource()
    LyricsContainer->>LyricsContainer: refreshMusixmatchTranslation()

    LyricsContainer->>Providers: request lyrics (include translation metadata)
    Providers->>ProviderMusixmatch: forward request (adds track_lyrics_translation_status)
    ProviderMusixmatch->>MusixmatchAPI: fetch lyrics + status
    MusixmatchAPI-->>ProviderMusixmatch: lyrics with track_lyrics_translation_status
    ProviderMusixmatch-->>Providers: return augmented body (__musixmatchTranslationStatus, __musixmatchTrackId)
    Providers-->>LyricsContainer: provider result (with available translations)

    alt selected language available
        LyricsContainer->>ProviderMusixmatch: getTranslation(trackId)
        ProviderMusixmatch->>MusixmatchAPI: fetch translation for trackId/language
        MusixmatchAPI-->>ProviderMusixmatch: translation payload
        ProviderMusixmatch-->>LyricsContainer: translation array
        LyricsContainer->>LyricsContainer: apply translation mapping to lyrics
    else unavailable
        LyricsContainer->>LyricsContainer: clear or skip translation
    end

    LyricsContainer->>User: render lyrics (with/without translation)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰
I hopped through prefixes, tiny and spry,
Found track IDs and languages nearby,
I stitched translations, one nibble at a time,
Cached each chorus, made the UI rhyme,
Now lyrics hop in languages sublime.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "feat(lyrics-plus): enhance Musixmatch integration with translation status and language handling" is fully related to the core objective and changes made across all five files. The changes collectively implement dynamic language selection for Musixmatch translations, introduce translation status detection infrastructure, and synchronize language preferences between the UI and storage. The title accurately captures this primary enhancement and uses clear, specific language without vague terms or noise. The title is concise and appropriately scoped to describe the main architectural improvement being delivered.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a208710 and cf3bea0.

📒 Files selected for processing (1)
  • CustomApps/lyrics-plus/Settings.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
CustomApps/lyrics-plus/Settings.js (1)
CustomApps/lyrics-plus/index.js (1)
  • lyricContainerUpdate (158-158)
🔇 Additional comments (1)
CustomApps/lyrics-plus/Settings.js (1)

678-691: LGTM! Clean simplification of the update flow.

The uniform use of lyricContainerUpdate?.() for visual configuration changes is a good simplification that aligns with the PR's dynamic Musixmatch translation handling. The separation between lyricContainerUpdate (visual changes) and reloadLyrics (provider changes, cache clearing) is clear and intentional.

Verified: lyricContainerUpdate is properly assigned in componentDidMount() (index.js:863) after state initialization, so the optional chaining in Settings.js safely handles the callback.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CustomApps/lyrics-plus/ProviderMusixmatch.js (1)

205-238: Handle network errors in getTranslation to avoid unhandled rejections

CosmosAsync.get can throw; without try/catch, refresh paths may break. Return null on errors.

-  async function getTranslation(trackId) {
+  async function getTranslation(trackId) {
     if (!trackId) return null;
@@
-    let result = await Spicetify.CosmosAsync.get(finalURL, null, headers);
+    let result;
+    try {
+      result = await Spicetify.CosmosAsync.get(finalURL, null, headers);
+    } catch (e) {
+      console.error("Musixmatch translation request failed", e);
+      return null;
+    }
🧹 Nitpick comments (3)
CustomApps/lyrics-plus/OptionsMenu.js (1)

121-135: Avoid duplicate Musixmatch language options

After pushing the active language, ensure uniqueness to prevent duplicate menu entries.

-    const availableMusixmatchLanguages = Array.isArray(musixmatchLanguages) ? [...new Set(musixmatchLanguages.filter(Boolean))] : [];
-    const activeMusixmatchLanguage =
+    let availableMusixmatchLanguages = Array.isArray(musixmatchLanguages) ? [...new Set(musixmatchLanguages.filter(Boolean))] : [];
+    const activeMusixmatchLanguage =
       musixmatchSelectedLanguage && musixmatchSelectedLanguage !== "none" ? musixmatchSelectedLanguage : null;
     if (hasTranslation.musixmatch && activeMusixmatchLanguage) {
-      availableMusixmatchLanguages.push(activeMusixmatchLanguage);
+      availableMusixmatchLanguages = [...new Set([...availableMusixmatchLanguages, activeMusixmatchLanguage])];
     }
CustomApps/lyrics-plus/index.js (1)

297-379: Harden refreshMusixmatchTranslation against fetch errors and unmounts

Great race checks and cache updates. Add a try/catch around the translation fetch; optionally guard setState after unmount.

-    const translation = await ProviderMusixmatch.getTranslation(trackId);
+    let translation = null;
+    try {
+      translation = await ProviderMusixmatch.getTranslation(trackId);
+    } catch (e) {
+      console.error("refreshMusixmatchTranslation failed", e);
+      translation = null;
+    }

Optional (nice-to-have): track mount status to avoid setState after unmount.

 class LyricsContainer extends react.Component {
   constructor() {
     super();
+    this._mounted = false;
@@
   componentDidMount() {
+    this._mounted = true;
@@
   componentWillUnmount() {
+    this._mounted = false;
@@
   async refreshMusixmatchTranslation() {
     // ...
-    this.setState({
+    if (!this._mounted) return;
+    this.setState({
       musixmatchTranslation: null,
       musixmatchTranslationLanguage: null,
     });
@@
-    this.setState({
+    if (!this._mounted) return;
+    this.setState({
       musixmatchTranslation: mappedTranslation,
       musixmatchTranslationLanguage: currentLanguage,
     });
CustomApps/lyrics-plus/Settings.js (1)

700-709: Remove duplicate lyricContainerUpdate call; init order is safe via optional chaining

The duplicate call at lines 706 and 708 should be consolidated. Move lyricContainerUpdate?.(); outside the if-else block:

// Reload Lyrics if translation language is changed
if (name === "musixmatch-translation-language") {
	if (value === "none") {
		CONFIG.visual["translate:translated-lyrics-source"] = "none";
		localStorage.setItem(`${APP_NAME}:visual:translate:translated-lyrics-source`, "none");
	}
}
lyricContainerUpdate?.();

Init order is already safe: lyricContainerUpdate is assigned in componentDidMount() before user interactions can occur, and optional chaining here guards against edge cases. The refactoring is sound.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15d1365 and be409c3.

📒 Files selected for processing (5)
  • CustomApps/lyrics-plus/OptionsMenu.js (6 hunks)
  • CustomApps/lyrics-plus/ProviderMusixmatch.js (5 hunks)
  • CustomApps/lyrics-plus/Providers.js (2 hunks)
  • CustomApps/lyrics-plus/Settings.js (1 hunks)
  • CustomApps/lyrics-plus/index.js (14 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
CustomApps/lyrics-plus/Providers.js (2)
CustomApps/lyrics-plus/index.js (1)
  • CONFIG (40-106)
CustomApps/lyrics-plus/ProviderMusixmatch.js (1)
  • ProviderMusixmatch (1-241)
CustomApps/lyrics-plus/ProviderMusixmatch.js (1)
CustomApps/lyrics-plus/Providers.js (6)
  • body (14-14)
  • result (3-10)
  • result (47-59)
  • result (108-116)
  • result (151-158)
  • result (207-213)
CustomApps/lyrics-plus/OptionsMenu.js (1)
CustomApps/lyrics-plus/index.js (4)
  • react (6-6)
  • react (7-7)
  • CONFIG (40-106)
  • APP_NAME (23-23)
CustomApps/lyrics-plus/index.js (2)
CustomApps/lyrics-plus/ProviderMusixmatch.js (1)
  • ProviderMusixmatch (1-241)
CustomApps/lyrics-plus/Utils.js (1)
  • Utils (1-338)
CustomApps/lyrics-plus/Settings.js (1)
CustomApps/lyrics-plus/index.js (1)
  • lyricContainerUpdate (158-158)
🔇 Additional comments (12)
CustomApps/lyrics-plus/Providers.js (1)

54-57: New Musixmatch fields on result look good

Adding musixmatchAvailableTranslations, musixmatchTrackId, and musixmatchTranslationLanguage makes downstream state handling straightforward. LGTM.

CustomApps/lyrics-plus/ProviderMusixmatch.js (3)

7-35: Recursive translation-status extraction looks good

Traversal handles objects/arrays and returns early on first match. Works for current macro response shapes.


52-53: Including part=track_lyrics_translation_status is correct

Ensures the macro response contains translation status; necessary for dynamic language options.


78-91: Non-enumerable augmentation for status and trackId is neat

Using Object.defineProperties to stash __musixmatchTranslationStatus and __musixmatchTrackId avoids polluting consumers’ iterations. LGTM.

CustomApps/lyrics-plus/OptionsMenu.js (2)

89-96: Prefix helper aligns with global prefix use

Fetching the prefix from window with a sane default decouples UI from internal constants. Good.


266-278: Syncing musixmatch-translation-language with source selection is correct

This keeps config/UI consistent when a Musixmatch-prefixed source is chosen or cleared.

CustomApps/lyrics-plus/index.js (6)

23-33: Globalizable translation prefix is well factored

Constants + window propagation enable cross-file access without tight coupling. LGTM.


125-142: Backward-compat upgrade path is solid

Migrates legacy "musixmatchTranslation" to the new prefixed format and syncs musixmatch-translation-language. Nice touch.


146-156: Empty state extended with Musixmatch fields

Matches downstream usage and avoids stale data when switching tracks. Looks good.


166-177: resolveTranslationSource keeps state normalized

Cleanly maps prefixed sources to a key+language pair, reducing special-casing elsewhere.


483-505: Good UX: reset selection when chosen language isn’t available

Auto-clearing prefixed source and language avoids stuck states when a track lacks that translation.


593-606: Source-language sync on render path is correct

Keeps CONFIG.visual in sync with resolved source; reduces drift between menu and runtime state.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
CustomApps/lyrics-plus/Providers.js (1)

94-121: Excellent O(n) optimization addresses past review concerns!

The translation mapping now uses a Map for O(1) lookups instead of nested find(), resolving the O(n²) performance issue flagged in the previous review. The fallback normalization function appropriately handles cases where Utils is unavailable due to module loading order.

However, consider adding defensive checks for the translation entries:

Apply this diff to guard against malformed API responses:

 const translationMap = new Map();
 for (const entry of translation) {
+   if (!entry || typeof entry.matchedLine !== "string" || typeof entry.translation !== "string") {
+     continue;
+   }
   const normalizedMatched = normalizeLyrics(entry.matchedLine);
-   if (!translationMap.has(normalizedMatched)) {
+   if (normalizedMatched && !translationMap.has(normalizedMatched)) {
     translationMap.set(normalizedMatched, entry.translation);
   }
 }

This ensures:

  • Entries with missing or non-string properties are skipped
  • Empty normalized keys (from empty matchedLine) don't create Map entries
  • The mapping remains robust against unexpected API responses
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be409c3 and a208710.

📒 Files selected for processing (1)
  • CustomApps/lyrics-plus/Providers.js (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
CustomApps/lyrics-plus/Providers.js (3)
CustomApps/lyrics-plus/Utils.js (2)
  • result (22-36)
  • Utils (1-338)
CustomApps/lyrics-plus/index.js (2)
  • CONFIG (40-106)
  • value (19-19)
CustomApps/lyrics-plus/ProviderMusixmatch.js (1)
  • ProviderMusixmatch (1-241)
🔇 Additional comments (3)
CustomApps/lyrics-plus/Providers.js (3)

54-56: LGTM! Clean field initialization.

The new fields for translation support are properly initialized with appropriate default values (empty array for translations, null for trackId and language).


87-88: LGTM! Robust metadata extraction.

The defensive checks ensure safe handling of the properties attached by ProviderMusixmatch.findLyrics, with appropriate fallbacks for missing or malformed data.


90-92: LGTM! Effective translation request gating.

The logic correctly validates that a translation language is configured and available before making the API request, preventing unnecessary calls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants