Add multi-language audio tracks and self-hosted flag-icons

Introduce per-video language support and multiple audio tracks
(VideoAudioTrack model + migrations for language, description, title),
a reusable language-select component, and a track-editor form. Bundle
the self-hosted flag-icons v7.2.3 library and a NAS auto-sync command.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-05-22 21:32:52 +03:00
1 parent f8d13457fa
commit 66fd78c10f
314 files changed
+16507 -3172

No files matched your search

@@ -15,7 +15,7 @@
$uid = 'csd_' . ($id ?? $name) . '_' . substr(md5(uniqid()), 0, 8);
$inputId = $id ?? $name;
$selFlag = '🌐';
$selFlag = '';
$selLabel = $placeholder;
$selSub = '';
if ($value) {
@@ -62,7 +62,8 @@
.csd-btn:focus-visible { border-color: var(--brand-red, #e61e1e); }
.csd-btn[aria-expanded="true"] { border-color: var(--brand-red, #e61e1e); }
.csd-ico { font-size: 18px; line-height: 1; flex-shrink: 0; user-select: none; }
.csd-ico { font-size: 18px; line-height: 1; flex-shrink: 0; user-select: none; display: flex; align-items: center; }
.csd-ico .fi, .csd-opt-ico .fi { width: 22px; height: 16px; border-radius: 2px; display: inline-block; flex-shrink: 0; }
.csd-val { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.csd-val.ph { color: var(--text-secondary, #aaa); }
.csd-sub { font-size: 12px; color: var(--text-secondary, #aaa); white-space: nowrap; flex-shrink: 0; }
@@ -71,7 +72,7 @@
.csd-panel {
position: fixed;
z-index: 1060;
z-index: 1080;
min-width: 220px;
background: var(--bg-secondary, #1e1e1e);
border: 1px solid var(--border-color, #303030);
@@ -225,7 +226,7 @@
_pick(opt) {
this.hidden.value = opt.dataset.v;
this.icoEl.textContent = opt.querySelector('.csd-opt-ico').textContent;
this.icoEl.innerHTML = opt.querySelector('.csd-opt-ico').innerHTML;
this.valEl.textContent = opt.querySelector('.csd-opt-main').textContent;
this.valEl.classList.remove('ph');
if (this.subEl) {
@@ -258,7 +259,7 @@
aria-haspopup="listbox"
aria-expanded="false"
aria-label="{{ $label ?? 'Phone code' }}">
<span class="csd-ico">{{ $selFlag }}</span>
<span class="csd-ico"><span class="fi fi-{{ $selFlag ?: 'xx' }}"></span></span>
<span class="csd-val{{ $isPlaceholder ? ' ph' : '' }}">{{ $selLabel }}</span>
@if(!$isPlaceholder && $selSub)
<span class="csd-sub">{{ $selSub }}</span>
@@ -279,7 +280,7 @@
data-v="{{ $opt['value'] }}"
data-s="{{ $opt['search'] }}"
aria-selected="{{ $value && str_starts_with($opt['value'], $value.'|') ? 'true' : 'false' }}">
<span class="csd-opt-ico">{{ $opt['flag'] }}</span>
<span class="csd-opt-ico"><span class="fi fi-{{ $opt['flag'] }}"></span></span>
<span class="csd-opt-main">{{ $opt['label'] }}</span>
<span class="csd-opt-sub">{{ $opt['secondary'] }}</span>
</li>