@braccato/core 1.7.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -1
- package/dist/README.md +26 -1
- package/dist/inject.d.ts +1 -1
- package/dist/inject.js +5 -1
- package/dist/language.d.ts +9 -0
- package/dist/language.js +47 -0
- package/dist/renderer.js +17 -0
- package/dist/styles/lyrics.css +15 -2
- package/dist/styles/variables.css +1 -2
- package/dist/types.d.ts +2 -0
- package/dist/view.d.ts +2 -0
- package/dist/view.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ connects, and everything it was handed by then is applied at once.
|
|
|
76
76
|
| Property | Attribute | Type | Default | Description |
|
|
77
77
|
| --------------- | -------------- | ----------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
78
78
|
| `lyrics` | | `Lyric[] \| null` | `null` | The song. Null means it was never given one, and an empty array clears the view, so there is a way to say both. |
|
|
79
|
-
| `lyricsOptions` | | `{ loaderVisible?, noLyrics? }` | `{}` | How the lines are built. `noLyrics` marks a message as a placeholder rather than a song, which keeps passive scrolling from drifting it. |
|
|
79
|
+
| `lyricsOptions` | | `{ loaderVisible?, noLyrics?, language? }` | `{}` | How the lines are built. `noLyrics` marks a message as a placeholder rather than a song, which keeps passive scrolling from drifting it. |
|
|
80
80
|
| `source` | `source` | `string \| HTMLMediaElement \| null` | `null` | A selector or the media element itself. See Following a media element. |
|
|
81
81
|
| `mediaElement` | | `HTMLMediaElement \| null` (get) | `null` | What `source` resolved to. Null while disconnected, and null for a selector that missed. |
|
|
82
82
|
| `currentTime` | `current-time` | `number` | `0` | Playback position in **seconds**. Writing it renders the view again, so whoever holds the clock drives the lyrics by writing this. |
|
|
@@ -365,3 +365,28 @@ repository and run `pnpm -C demo dev`, then open `http://localhost:5173/`.
|
|
|
365
365
|
## Licence
|
|
366
366
|
|
|
367
367
|
MIT. See `LICENSE`.
|
|
368
|
+
|
|
369
|
+
### Content language and CJK fonts
|
|
370
|
+
|
|
371
|
+
Pass the source BCP 47 language to `renderer.setLyrics(lyrics, { language: "ja" })`
|
|
372
|
+
(or `element.lyricsOptions = { language: "ja" }` before assigning lyrics). If language detection
|
|
373
|
+
finishes later, `renderer.setLanguage("ja")` updates the existing lines and their measurements
|
|
374
|
+
without replacing their DOM or animations. Repeating the same language (including equivalent spellings
|
|
375
|
+
such as `ja-JP` and `ja_JP`) does not write to the DOM or measure again. Omitting the language on a
|
|
376
|
+
new song clears the old hint.
|
|
377
|
+
|
|
378
|
+
The renderer preserves Chinese script/region tags and uses kana or Hangul as a fallback when
|
|
379
|
+
metadata is missing or contradicts the text. Han characters alone cannot identify a language;
|
|
380
|
+
untagged Han-only lyrics remain unknown. Translation and romanization settings are not needed
|
|
381
|
+
for this local inference.
|
|
382
|
+
|
|
383
|
+
Use `injectTranslation(document, lineElement, text, "zh-Hant")` to label a translation separately
|
|
384
|
+
from its source. The language argument is optional; unknown translations get `lang=""` so they
|
|
385
|
+
do not inherit the original lyrics' language. Romanizations use the source language with `Latn`.
|
|
386
|
+
|
|
387
|
+
Default font stacks now resolve on each lyric and decoration instead of only at the root. Hosts
|
|
388
|
+
loading regional CJK web fonts should set `--noto-sans-universal` on these elements according to
|
|
389
|
+
`:lang()`: a Japanese line and its Chinese translation can then choose different font subsets.
|
|
390
|
+
`--blyrics-font-family` and `--blyrics-translated-font-family` remain explicit theme overrides.
|
|
391
|
+
They are unset by default; custom CSS that reads them directly should use
|
|
392
|
+
`var(--blyrics-font-family, var(--blyrics-default-font-family))` for the default stack.
|
package/dist/README.md
CHANGED
|
@@ -76,7 +76,7 @@ connects, and everything it was handed by then is applied at once.
|
|
|
76
76
|
| Property | Attribute | Type | Default | Description |
|
|
77
77
|
| --------------- | -------------- | ----------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
78
78
|
| `lyrics` | | `Lyric[] \| null` | `null` | The song. Null means it was never given one, and an empty array clears the view, so there is a way to say both. |
|
|
79
|
-
| `lyricsOptions` | | `{ loaderVisible?, noLyrics? }` | `{}` | How the lines are built. `noLyrics` marks a message as a placeholder rather than a song, which keeps passive scrolling from drifting it. |
|
|
79
|
+
| `lyricsOptions` | | `{ loaderVisible?, noLyrics?, language? }` | `{}` | How the lines are built. `noLyrics` marks a message as a placeholder rather than a song, which keeps passive scrolling from drifting it. |
|
|
80
80
|
| `source` | `source` | `string \| HTMLMediaElement \| null` | `null` | A selector or the media element itself. See Following a media element. |
|
|
81
81
|
| `mediaElement` | | `HTMLMediaElement \| null` (get) | `null` | What `source` resolved to. Null while disconnected, and null for a selector that missed. |
|
|
82
82
|
| `currentTime` | `current-time` | `number` | `0` | Playback position in **seconds**. Writing it renders the view again, so whoever holds the clock drives the lyrics by writing this. |
|
|
@@ -365,3 +365,28 @@ repository and run `pnpm -C demo dev`, then open `http://localhost:5173/`.
|
|
|
365
365
|
## Licence
|
|
366
366
|
|
|
367
367
|
MIT. See `LICENSE`.
|
|
368
|
+
|
|
369
|
+
### Content language and CJK fonts
|
|
370
|
+
|
|
371
|
+
Pass the source BCP 47 language to `renderer.setLyrics(lyrics, { language: "ja" })`
|
|
372
|
+
(or `element.lyricsOptions = { language: "ja" }` before assigning lyrics). If language detection
|
|
373
|
+
finishes later, `renderer.setLanguage("ja")` updates the existing lines and their measurements
|
|
374
|
+
without replacing their DOM or animations. Repeating the same language (including equivalent spellings
|
|
375
|
+
such as `ja-JP` and `ja_JP`) does not write to the DOM or measure again. Omitting the language on a
|
|
376
|
+
new song clears the old hint.
|
|
377
|
+
|
|
378
|
+
The renderer preserves Chinese script/region tags and uses kana or Hangul as a fallback when
|
|
379
|
+
metadata is missing or contradicts the text. Han characters alone cannot identify a language;
|
|
380
|
+
untagged Han-only lyrics remain unknown. Translation and romanization settings are not needed
|
|
381
|
+
for this local inference.
|
|
382
|
+
|
|
383
|
+
Use `injectTranslation(document, lineElement, text, "zh-Hant")` to label a translation separately
|
|
384
|
+
from its source. The language argument is optional; unknown translations get `lang=""` so they
|
|
385
|
+
do not inherit the original lyrics' language. Romanizations use the source language with `Latn`.
|
|
386
|
+
|
|
387
|
+
Default font stacks now resolve on each lyric and decoration instead of only at the root. Hosts
|
|
388
|
+
loading regional CJK web fonts should set `--noto-sans-universal` on these elements according to
|
|
389
|
+
`:lang()`: a Japanese line and its Chinese translation can then choose different font subsets.
|
|
390
|
+
`--blyrics-font-family` and `--blyrics-translated-font-family` remain explicit theme overrides.
|
|
391
|
+
They are unset by default; custom CSS that reads them directly should use
|
|
392
|
+
`var(--blyrics-font-family, var(--blyrics-default-font-family))` for the default stack.
|
package/dist/inject.d.ts
CHANGED
|
@@ -43,4 +43,4 @@ export declare function createLyricsLine(doc: Document, parts: LyricPart[], line
|
|
|
43
43
|
export declare function buildLineSyncedParts(item: Lyric): LyricPart[];
|
|
44
44
|
export declare function addSeekHandler(seek: (timeS: number) => void, lyricElement: HTMLElement, allZero: boolean): void;
|
|
45
45
|
export declare function injectRomanization(doc: Document, lyricElement: HTMLElement, lineData: LineData, text: string, timedRomanization?: LyricPart[] | null): void;
|
|
46
|
-
export declare function injectTranslation(doc: Document, lyricElement: HTMLElement, text: string): void;
|
|
46
|
+
export declare function injectTranslation(doc: Document, lyricElement: HTMLElement, text: string, language?: string | null): void;
|
package/dist/inject.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// animates is one word, so every part is split on whitespace with its timing pro-rated across the
|
|
12
12
|
// split by character count. A line that arrives with no timed parts at all is rebuilt the same way
|
|
13
13
|
// into zero duration words, so line synced lyrics reach the DOM the sweep already knows.
|
|
14
|
+
import { normalizeLanguage } from "./language.js";
|
|
14
15
|
import { BACKGROUND_LINE_CLASS, BACKGROUND_LYRIC_CLASS, BIDI_RUN_CLASS, BIDI_SENSITIVE_CLASS, CONTENT_LINE_CLASS, EXPLICIT_WORD_CLASS, HIGHLIGHT_RUN_CLASS, LETTER_CLASS, LINE_MAIN_CLASS, LINE_SYNCED_WORD_CLASS, LONG_WORD_GROUP_CLASS, ROMANIZED_LYRICS_CLASS, RTL_CLASS, TRANSLATED_LYRICS_CLASS, WORD_CLASS, WORD_GROUP_CLASS, WORD_HIGHLIGHT_CLASS, WORD_HIGHLIGHT_LETTERED_CLASS, WORD_STATE_ATTR, WORD_STATE_UPCOMING, ZERO_DURATION_ANIMATION_CLASS, } from "./constants.js";
|
|
15
16
|
import { getSeekTimeFromClick } from "./seek.js";
|
|
16
17
|
import { testRtl } from "./text.js";
|
|
@@ -405,6 +406,8 @@ export function injectRomanization(doc, lyricElement, lineData, text, timedRoman
|
|
|
405
406
|
const romanizedLine = doc.createElement("div");
|
|
406
407
|
romanizedLine.classList.add(ROMANIZED_LYRICS_CLASS, CONTENT_LINE_CLASS);
|
|
407
408
|
romanizedLine.dir = "auto";
|
|
409
|
+
const language = normalizeLanguage(lyricElement.lang);
|
|
410
|
+
romanizedLine.lang = `${language ? new Intl.Locale(language).language : "und"}-Latn`;
|
|
408
411
|
applyDirection(romanizedLine, text);
|
|
409
412
|
if (timedRomanization && timedRomanization.length > 0 && !disableRichsync.getBooleanValue()) {
|
|
410
413
|
createLyricsLine(doc, timedRomanization, lineData, romanizedLine, { splitBackgroundLine: false });
|
|
@@ -415,12 +418,13 @@ export function injectRomanization(doc, lyricElement, lineData, text, timedRoman
|
|
|
415
418
|
const translation = lyricElement.querySelector(`.${TRANSLATED_LYRICS_CLASS}`);
|
|
416
419
|
lyricElement.insertBefore(romanizedLine, translation);
|
|
417
420
|
}
|
|
418
|
-
export function injectTranslation(doc, lyricElement, text) {
|
|
421
|
+
export function injectTranslation(doc, lyricElement, text, language) {
|
|
419
422
|
if (lyricElement.querySelector(`.${TRANSLATED_LYRICS_CLASS}`))
|
|
420
423
|
return;
|
|
421
424
|
const translatedLine = doc.createElement("div");
|
|
422
425
|
translatedLine.classList.add(TRANSLATED_LYRICS_CLASS, CONTENT_LINE_CLASS);
|
|
423
426
|
translatedLine.dir = "auto";
|
|
427
|
+
translatedLine.lang = normalizeLanguage(language);
|
|
424
428
|
applyDirection(translatedLine, text);
|
|
425
429
|
translatedLine.textContent = text;
|
|
426
430
|
lyricElement.appendChild(translatedLine);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface LyricText {
|
|
2
|
+
words: string;
|
|
3
|
+
isInstrumental?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function normalizeLanguage(language?: string | null): string;
|
|
6
|
+
export declare function resolveLyricLanguages(lyrics: readonly LyricText[], sourceLanguage?: string | null): string[];
|
|
7
|
+
/** Tags only lyric content, leaving the host UI and source footer in their own language. */
|
|
8
|
+
export declare function applyLyricLanguage(element: HTMLElement, language?: string | null): void;
|
|
9
|
+
export {};
|
package/dist/language.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ROMANIZED_LYRICS_CLASS } from "./constants.js";
|
|
2
|
+
export function normalizeLanguage(language) {
|
|
3
|
+
if (!language || language === "auto" || language === "und")
|
|
4
|
+
return "";
|
|
5
|
+
try {
|
|
6
|
+
return Intl.getCanonicalLocales(language.trim().replaceAll("_", "-"))[0] ?? "";
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return "";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function detectCjkLanguage(text) {
|
|
13
|
+
if (/[\p{Script=Hiragana}\p{Script=Katakana}]/u.test(text))
|
|
14
|
+
return "ja";
|
|
15
|
+
if (/\p{Script=Hangul}/u.test(text))
|
|
16
|
+
return "ko";
|
|
17
|
+
// Han alone cannot distinguish Japanese, Korean, or the Chinese writing systems.
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function isCjkLanguage(language) {
|
|
21
|
+
return /^(ja|ko|zh|yue)(-|$)|-(Hans|Hant)(-|$)/i.test(language);
|
|
22
|
+
}
|
|
23
|
+
export function resolveLyricLanguages(lyrics, sourceLanguage) {
|
|
24
|
+
const language = normalizeLanguage(sourceLanguage);
|
|
25
|
+
const hints = lyrics.map(line => (line.isInstrumental ? null : detectCjkLanguage(line.words)));
|
|
26
|
+
const songHints = new Set(hints.filter(hint => hint !== null));
|
|
27
|
+
const songHint = songHints.size === 1 ? [...songHints][0] : "";
|
|
28
|
+
const fallback = isCjkLanguage(language) ? language : songHint || language;
|
|
29
|
+
return lyrics.map((line, index) => {
|
|
30
|
+
if (line.isInstrumental)
|
|
31
|
+
return "";
|
|
32
|
+
const hint = hints[index];
|
|
33
|
+
if (hint && fallback.split("-")[0] !== hint)
|
|
34
|
+
return hint;
|
|
35
|
+
if (/\p{Script=Han}/u.test(line.words) && !isCjkLanguage(fallback))
|
|
36
|
+
return "";
|
|
37
|
+
return fallback;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** Tags only lyric content, leaving the host UI and source footer in their own language. */
|
|
41
|
+
export function applyLyricLanguage(element, language) {
|
|
42
|
+
const normalized = normalizeLanguage(language);
|
|
43
|
+
element.lang = normalized;
|
|
44
|
+
const romanization = element.querySelector(`.${ROMANIZED_LYRICS_CLASS}`);
|
|
45
|
+
if (romanization)
|
|
46
|
+
romanization.lang = `${normalized ? new Intl.Locale(normalized).language : "und"}-Latn`;
|
|
47
|
+
}
|
package/dist/renderer.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { CUSTOM_THEME_STYLE_ID } from "./constants.js";
|
|
13
13
|
import { clearLyrics, clearOnScreenLyrics as clearEngineOnScreenLyrics, clearStyleCaches as clearEngineStyleCaches, createAnimationEngineInstance, getRenderedLines, getRenderedSyncType, hasUnsyncedLyrics, noteContainerResize, noteUserScroll as noteEngineUserScroll, noteVisibilityChange as noteEngineVisibilityChange, relayout, resetScrollResume, resolveTickOptions, retickFromPlaybackClock as retickEngineFromPlaybackClock, scheduleLyricPositionUpdate as scheduleEngineLyricPositionUpdate, tickView, } from "./engine.js";
|
|
14
14
|
import { parseThemeConfig, setThemeSettings } from "./themeSettings.js";
|
|
15
|
+
import { applyLyricLanguage, normalizeLanguage, resolveLyricLanguages } from "./language.js";
|
|
15
16
|
import { setLyrics as buildLyricsView } from "./view.js";
|
|
16
17
|
/**
|
|
17
18
|
* What the default `seek` dispatches at the mount. A consumer that gave the renderer no way to
|
|
@@ -134,6 +135,8 @@ export function createLyricsRenderer(rendererOptions) {
|
|
|
134
135
|
// Only the theme element this renderer created, which is the only one it may take away again.
|
|
135
136
|
let createdThemeStyleElement = null;
|
|
136
137
|
let isDestroyed = false;
|
|
138
|
+
let currentLyrics = [];
|
|
139
|
+
let currentLanguage = "";
|
|
137
140
|
const { host, forgetScrollElement } = withHostDefaults(rendererOptions.host, rendererWindow, () => mount);
|
|
138
141
|
const engine = createAnimationEngineInstance(rendererDocument, rendererWindow, host);
|
|
139
142
|
/**
|
|
@@ -165,6 +168,8 @@ export function createLyricsRenderer(rendererOptions) {
|
|
|
165
168
|
stopObservingContainer();
|
|
166
169
|
engine.lyricsContainer?.remove();
|
|
167
170
|
clearLyrics(engine);
|
|
171
|
+
currentLyrics = [];
|
|
172
|
+
currentLanguage = "";
|
|
168
173
|
}
|
|
169
174
|
/**
|
|
170
175
|
* Watches the built container for the layout it settles into. The guard is what stops this
|
|
@@ -252,13 +257,25 @@ export function createLyricsRenderer(rendererOptions) {
|
|
|
252
257
|
clearBuiltView();
|
|
253
258
|
mount = nextMount;
|
|
254
259
|
buildLyricsView(engine, nextMount, lyrics, {
|
|
260
|
+
language: options?.language,
|
|
255
261
|
loaderVisible: options?.loaderVisible ?? false,
|
|
256
262
|
noLyrics: options?.noLyrics ?? false,
|
|
257
263
|
});
|
|
264
|
+
currentLyrics = lyrics;
|
|
265
|
+
currentLanguage = normalizeLanguage(options?.language);
|
|
258
266
|
measure();
|
|
259
267
|
if (engine.lyricsContainer)
|
|
260
268
|
observeContainer(engine.lyricsContainer);
|
|
261
269
|
},
|
|
270
|
+
setLanguage(language) {
|
|
271
|
+
const normalized = normalizeLanguage(language);
|
|
272
|
+
if (isDestroyed || currentLyrics.length === 0 || normalized === currentLanguage)
|
|
273
|
+
return;
|
|
274
|
+
currentLanguage = normalized;
|
|
275
|
+
const languages = resolveLyricLanguages(currentLyrics, normalized);
|
|
276
|
+
getRenderedLines(engine).forEach((line, index) => applyLyricLanguage(line.lyricElement, languages[index]));
|
|
277
|
+
measure();
|
|
278
|
+
},
|
|
262
279
|
setTheme(css) {
|
|
263
280
|
if (isDestroyed)
|
|
264
281
|
return false;
|
package/dist/styles/lyrics.css
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
/* Resolve the default stack at each language boundary. A root-level var() would freeze
|
|
2
|
+
the host's language-specific fallback before the lyric/translation language is known.
|
|
3
|
+
Theme overrides remain inherited through --blyrics-font-family. */
|
|
4
|
+
.blyrics--line,
|
|
5
|
+
.blyrics--translated,
|
|
6
|
+
.blyrics--romanized {
|
|
7
|
+
--blyrics-default-font-family:
|
|
8
|
+
Satoshi, var(--noto-sans-universal, "Noto Sans"), Avenir, -apple-system,
|
|
9
|
+
BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans,
|
|
10
|
+
Helvetica Neue, sans-serif;
|
|
11
|
+
font-family: var(--blyrics-font-family, var(--blyrics-default-font-family));
|
|
12
|
+
}
|
|
13
|
+
|
|
1
14
|
/* Main container styles */
|
|
2
15
|
.blyrics-container {
|
|
3
|
-
font-family: var(--blyrics-font-family);
|
|
16
|
+
font-family: var(--blyrics-font-family, var(--blyrics-default-font-family));
|
|
4
17
|
font-size: var(--blyrics-font-size);
|
|
5
18
|
font-weight: var(--blyrics-font-weight);
|
|
6
19
|
isolation: isolate;
|
|
@@ -215,7 +228,7 @@
|
|
|
215
228
|
cubic-bezier(0.25, 1, 0.5, 1) both;
|
|
216
229
|
color: var(--blyrics-translated-color);
|
|
217
230
|
display: block;
|
|
218
|
-
font-family: var(--blyrics-translated-font-family);
|
|
231
|
+
font-family: var(--blyrics-translated-font-family, var(--blyrics-font-family, var(--blyrics-default-font-family)));
|
|
219
232
|
font-size: var(--blyrics-translated-font-size);
|
|
220
233
|
font-weight: var(--blyrics-translated-font-weight);
|
|
221
234
|
line-height: 1.1;
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
so that a standalone consumer without it still falls through to the rest of this stack; the
|
|
37
37
|
CJK, Devanagari, Arabic, Hebrew and Thai coverage is what such a consumer gives up either
|
|
38
38
|
way. */
|
|
39
|
-
--blyrics-font-family:
|
|
39
|
+
--blyrics-default-font-family:
|
|
40
40
|
Satoshi, var(--noto-sans-universal, "Noto Sans"), Avenir, -apple-system,
|
|
41
41
|
BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans,
|
|
42
42
|
Helvetica Neue, sans-serif;
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
|
|
47
47
|
--blyrics-translated-font-size: 2rem;
|
|
48
48
|
--blyrics-translated-font-weight: 600;
|
|
49
|
-
--blyrics-translated-font-family: var(--blyrics-font-family);
|
|
50
49
|
--blyrics-translated-color: color(
|
|
51
50
|
display-p3 1 1 1 / var(--blyrics-translated-opacity, 0.6)
|
|
52
51
|
);
|
package/dist/types.d.ts
CHANGED
|
@@ -109,6 +109,8 @@ export interface LyricsRenderer {
|
|
|
109
109
|
setLyrics(lyrics: Lyric[], options?: Partial<SetLyricsOptions> & {
|
|
110
110
|
mount?: HTMLElement;
|
|
111
111
|
}): void;
|
|
112
|
+
/** Updates a late-detected source language and layout without rebuilding lines or animations. */
|
|
113
|
+
setLanguage(language?: string | null): void;
|
|
112
114
|
/**
|
|
113
115
|
* Applies a compiled theme. The `blyrics-*` configuration in its comments becomes the module's
|
|
114
116
|
* theme settings, and the stylesheet itself is applied to the document this renderer builds in.
|
package/dist/view.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type AnimationEngineInstance } from "./engine.js";
|
|
2
2
|
import type { Lyric } from "./types.js";
|
|
3
3
|
export interface SetLyricsOptions {
|
|
4
|
+
/** BCP 47 language of the original lyrics. Kana/Hangul provide a fallback when omitted. */
|
|
5
|
+
language?: string | null;
|
|
4
6
|
/**
|
|
5
7
|
* Whether the loader is still covering the view. Recorded on the container for CSS to key on.
|
|
6
8
|
*/
|
package/dist/view.js
CHANGED
|
@@ -13,6 +13,7 @@ import { LINE_CLASS, LYRICS_CLASS, RTL_CLASS } from "./constants.js";
|
|
|
13
13
|
import { setupLineCullObserver } from "./engine.js";
|
|
14
14
|
import { addSeekHandler, applyDirection, buildLineSyncedParts, createLyricsLine, deriveSyncType, disableRichsync, findNearestAgent, isNearestLyricRtl, newLineData, } from "./inject.js";
|
|
15
15
|
import { createInstrumentalElement } from "./instrumental.js";
|
|
16
|
+
import { applyLyricLanguage, resolveLyricLanguages } from "./language.js";
|
|
16
17
|
const INITIAL_SKIP_SCROLLS = 2;
|
|
17
18
|
const SKIP_SCROLL_DECAY_MS = 2000;
|
|
18
19
|
function buildInstrumentalLine(doc, lyricElement, lyrics, lineIndex) {
|
|
@@ -58,6 +59,7 @@ export function setLyrics(engine, mount, lyrics, options) {
|
|
|
58
59
|
const seek = (timeS) => engine.host.seek(timeS);
|
|
59
60
|
const lines = [];
|
|
60
61
|
const syncType = deriveSyncType(lyrics);
|
|
62
|
+
const languages = resolveLyricLanguages(lyrics, options.language);
|
|
61
63
|
for (const [lineIndex, lyricItem] of lyrics.entries()) {
|
|
62
64
|
const lyricElement = doc.createElement("div");
|
|
63
65
|
const line = newLineData(lyricElement, lyricItem.startTimeMs, lyricItem.durationMs);
|
|
@@ -66,6 +68,7 @@ export function setLyrics(engine, mount, lyrics, options) {
|
|
|
66
68
|
lyricElement.dataset.lineNumber = String(lineIndex);
|
|
67
69
|
lyricElement.classList.add(LINE_CLASS);
|
|
68
70
|
lyricElement.dir = "auto";
|
|
71
|
+
applyLyricLanguage(lyricElement, languages[lineIndex]);
|
|
69
72
|
addSeekHandler(seek, lyricElement, allZero);
|
|
70
73
|
lines.push(line);
|
|
71
74
|
if (lyricItem.isInstrumental) {
|