feat: i18n

This commit is contained in:
Senko-san
2026-06-06 15:23:07 +03:00
parent bbd59cc225
commit e45bcef3a5
21 changed files with 613 additions and 163 deletions
+19 -24
View File
@@ -1,4 +1,5 @@
import { Slider, Badge } from '@olly/modern-sk';
import { useTranslation } from 'react-i18next';
import { Icon } from '../common/Icon';
import { ArtTile } from '../common/ArtTile';
import { useAppDispatch, useAppSelector } from '../../hooks/useAppDispatch';
@@ -10,6 +11,7 @@ import {
import { toggleQueue } from '../../store/slices/player';
export function QueuePanel() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const queue = useAppSelector((s) => s.queue);
const isOpen = useAppSelector((s) => s.player.isQueueOpen);
@@ -27,13 +29,13 @@ export function QueuePanel() {
<div className="qd-inner">
<div className="qd-head">
<div className="row">
<h3>Play queue</h3>
<h3>{t('queue.title')}</h3>
<div style={{ flex: 1 }} />
<button
type="button"
className="iconbtn sm"
onClick={() => dispatch(clearQueue())}
title="Clear queue"
title={t('queue.clear')}
>
<Icon name="trash" />
</button>
@@ -41,7 +43,7 @@ export function QueuePanel() {
type="button"
className="iconbtn sm"
onClick={() => dispatch(toggleQueue())}
title="Close"
title={t('queue.close')}
>
<Icon name="x" />
</button>
@@ -53,10 +55,10 @@ export function QueuePanel() {
/>
{isRadio ? (
<span style={{ color: 'var(--lime)' }}>
Radio · {sourceLabel}
{t('queue.radio', { source: sourceLabel })}
</span>
) : (
<span>From {sourceLabel}</span>
<span>{t('queue.from', { source: sourceLabel })}</span>
)}
</div>
</div>
@@ -68,7 +70,7 @@ export function QueuePanel() {
className="msk-label"
style={{ display: 'block', marginBottom: 8 }}
>
Now playing
{t('queue.nowPlaying')}
</span>
<div className="qd-now">
<ArtTile
@@ -87,21 +89,14 @@ export function QueuePanel() {
<div className="qd-radio">
<div className="row">
<Icon name="radio" />
<span
style={{
fontSize: 13,
fontWeight: 600,
color: 'var(--fg-1)',
}}
>
Radio active
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--fg-1)' }}>
{t('queue.radioActive')}
</span>
<div style={{ flex: 1 }} />
<Badge variant="neutral"> mixing</Badge>
<Badge variant="neutral">{t('queue.mixing')}</Badge>
</div>
{/* exploration balance — stub under the future ML contract */}
<div className="expl">
<span className="lab">Familiar</span>
<span className="lab">{t('queue.familiar')}</span>
<Slider
className="expl-slider"
min={0}
@@ -110,7 +105,7 @@ export function QueuePanel() {
defaultValue={[42]}
aria-label="Exploration"
/>
<span className="lab">New</span>
<span className="lab">{t('queue.new')}</span>
</div>
</div>
)}
@@ -119,17 +114,17 @@ export function QueuePanel() {
className="msk-label"
style={{ display: 'block', margin: '4px 0 8px' }}
>
Next up
{t('queue.nextUp')}
</span>
{upNext.length === 0 ? (
<div className="qd-empty">Nothing queued next</div>
<div className="qd-empty">{t('queue.nothingNext')}</div>
) : (
upNext.map(({ entry, index }) => (
<div
key={`${entry.trackId}-${index}`}
className="qrow"
onDoubleClick={() => dispatch(goToIndex(index))}
title="Double-click to play"
title={t('queue.doubleClickPlay')}
>
<span className="grip">
<Icon name="dots-six-vertical" />
@@ -147,7 +142,7 @@ export function QueuePanel() {
type="button"
className="iconbtn sm"
onClick={() => dispatch(removeFromQueue(index))}
title="Remove from queue"
title={t('queue.removeFromQueue')}
>
<Icon name="x" />
</button>
@@ -156,11 +151,11 @@ export function QueuePanel() {
)}
{isRadio && (
<div className="qd-loadmore">Loading more from radio</div>
<div className="qd-loadmore">{t('queue.loadingMore')}</div>
)}
</>
) : (
<div className="qd-empty">Queue is empty</div>
<div className="qd-empty">{t('queue.empty')}</div>
)}
</div>
</div>