Add translations and transliteration support to lyrics - #152
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
composer | 9336c6e | Commit Preview URL | Aug 28 2026, 01:43 AM |
923ed30 to
3b7bad4
Compare
|
React Doctor found 3 new issues in 1 file · 3 warnings · score 83 / 100 (Needs work) · 5 fixed · vs 3 warnings
Reviewed by React Doctor for commit |
…ons_and_transliteration_support_to_lyrics # Conflicts: # src/hooks/useSyncHandlers.ts # src/stores/project/groups-slice.ts # src/utils/ttml.ts # src/views/sync/sync-panel.browser.test.tsx # src/views/sync/sync-panel.tsx # src/views/timeline/fill-empty-lines-with-instance.ts # src/views/timeline/use-timeline-keyboard.ts # src/views/timeline/word-track.tsx
| }, [activeTab, performTap, beginHold, endHold, undo, redo, handleNudgeLastSynced, editMode, isHolding]); | ||
| }, [ | ||
| activeTab, | ||
| performTap, |
There was a problem hiding this comment.
React Doctor · react-doctor/prefer-use-effect-event (warning)
Your effect re-subscribes whenever "performTap" changes, even though it's only used inside addEventListener.
Fix → Wrap the callback with useEffectEvent(callback) (React 19.2+) and call it inside the sub-handler. An Effect Event always sees the latest props and state but isn't a dependency, so the effect won't re-subscribe every time the parent redraws. See https://react.dev/reference/react/useEffectEvent
| }, [ | ||
| activeTab, | ||
| performTap, | ||
| beginHold, |
There was a problem hiding this comment.
React Doctor · react-doctor/prefer-use-effect-event (warning)
Your effect re-subscribes whenever "beginHold" changes, even though it's only used inside addEventListener.
Fix → Wrap the callback with useEffectEvent(callback) (React 19.2+) and call it inside the sub-handler. An Effect Event always sees the latest props and state but isn't a dependency, so the effect won't re-subscribe every time the parent redraws. See https://react.dev/reference/react/useEffectEvent
| activeTab, | ||
| performTap, | ||
| beginHold, | ||
| endHold, |
There was a problem hiding this comment.
React Doctor · react-doctor/prefer-use-effect-event (warning)
Your effect re-subscribes whenever "endHold" changes, even though it's only used inside addEventListener.
Fix → Wrap the callback with useEffectEvent(callback) (React 19.2+) and call it inside the sub-handler. An Effect Event always sees the latest props and state but isn't a dependency, so the effect won't re-subscribe every time the parent redraws. See https://react.dev/reference/react/useEffectEvent

TL;DR
Adds a new Languages tab that lets users generate, edit, and import translations and transliterations for lyric lines, with full round-trip support through TTML export/import.
Transliterations show up in the timeline & synced tab (optional!)
What changed?
src/views/languages.tsx) is introduced as a first-class tab between Edit and Sync. On entry it automatically calls the Google Translate API to generate transliterations and English translations for all lyric lines. Results are editable per-line and marked as stale when the source text changes.LanguageProviderabstraction (src/services/language-provider.ts) and agoogleLanguageProviderimplementation (src/services/google-language-provider.ts) handle translation and transliteration requests with concurrency limiting, response caching, and non-Latin script detection/fallback.TransliterationTrack,TranslationTrack,TranslationTracks) are added to the lyric line model.translationsandtransliterationfields are propagated through group templates, instance expansion, and the timeline fill/copy operations.src/views/languages/paste-import-modal.tsx) handles multi-line clipboard pastes into any language field, with automatic blank-line alignment strategies ("preserve", "compact", "manual") and per-line editable mapping before committing.alignTransliterationToWords(src/domain/language/align.ts), which maps dash-separated syllables to split timing slots and space-separated words to canonical source-word boundaries.transliterationfield, a secondary character picker is shown for the romanized text, and both sets of split points are applied together viasplitWordIntoSyllables.L) that switches all displayed word labels to their aligned transliteration equivalents without altering timing data. The toggle state lives inuseTimelineStore.src/utils/ttml.ts) now emitsitunes:keyattributes on<p>elements and writes an<iTunesMetadata>block containing<translations>and<transliterations>sidecars. Timed transliteration words are emitted as individual<span>elements with proportional timing; untimed syllables use single spaces and word boundaries use double spaces.src/utils/lyrics-parsers/ttml-alternates.ts) parses the<iTunesMetadata>block and populatestranslationsandtransliterationon the corresponding lyric lines, including background text variants.global.goToLanguagesis added as Mod+3, and the existing Sync/Timeline/Preview/Export shortcuts shift up by one.languageSourceFingerprintutility produces a stable hash of a line's text and background text, used to detect when generated language content has gone stale.src/domain/language/script-detection.ts) identify non-Latin scripts by Unicode property and map them to BCP 47 language codes for use as transliteration source hints.How to test?
L) and verify that word labels switch to romanized text while timing is unaffected.transliterationfields on the resulting words.translationsandtransliterationfields round-trip correctly, including background text.Why make this change?
Lyric files distributed through platforms like Apple Music carry translations and transliterations alongside the timed text. This feature closes that gap by giving authors a dedicated workflow to generate, review, and manually correct alternate-language content, and by ensuring that content survives the full edit → sync → export → import round-trip in the TTML format.