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
+12 -14
View File
@@ -1,4 +1,5 @@
import { Slider } 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';
@@ -17,6 +18,7 @@ import { formatDuration } from '../../lib/format';
import { getCoverUrl } from '../../api/endpoints/streaming';
export function PersistentPlayer() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { seek, playNext, playPrev } = useAudioPlayer();
const player = useAppSelector((s) => s.player);
@@ -24,17 +26,15 @@ export function PersistentPlayer() {
const currentEntry = queue.entries[queue.currentIndex];
if (!currentEntry && !player.currentTrackId) {
return <div className="player empty">Nothing playing</div>;
return <div className="player empty">{t('player.nothingPlaying')}</div>;
}
const artUrl = getCoverUrl(currentEntry?.albumArtUrl);
const seedLabel = currentEntry?.albumTitle ?? currentEntry?.title ?? '';
// Streaming is the web default; local playback is a mobile-client concern.
const onStream = true;
return (
<div className="player">
{/* now-playing identity */}
<div
className="pl-now"
onClick={() => dispatch(toggleNowPlaying())}
@@ -49,19 +49,18 @@ export function PersistentPlayer() {
style={{ color: onStream ? 'var(--fg-3)' : 'var(--lime)' }}
>
<Icon name={onStream ? 'cloud' : 'check-circle'} fill={!onStream} />
{onStream ? 'Streaming · 320 kbps' : 'Local · FLAC'}
{onStream ? t('player.streaming') : t('player.local')}
</div>
</div>
</div>
{/* transport + scrubber */}
<div className="pl-center">
<div className="pl-transport">
<button
type="button"
className={`pl-tbtn${player.shuffle ? ' on' : ''}`}
onClick={() => dispatch(toggleShuffle())}
title="Shuffle"
title={t('player.shuffle')}
>
<Icon name="shuffle" />
</button>
@@ -69,7 +68,7 @@ export function PersistentPlayer() {
type="button"
className="pl-tbtn"
onClick={playPrev}
title="Previous"
title={t('player.previous')}
>
<Icon name="skip-back" fill />
</button>
@@ -79,7 +78,7 @@ export function PersistentPlayer() {
onClick={() =>
player.isPlaying ? dispatch(pause()) : dispatch(resume())
}
title={player.isPlaying ? 'Pause' : 'Play'}
title={player.isPlaying ? t('player.pause') : t('player.play')}
>
<Icon name={player.isPlaying ? 'pause' : 'play'} fill />
</button>
@@ -87,7 +86,7 @@ export function PersistentPlayer() {
type="button"
className="pl-tbtn"
onClick={playNext}
title="Next"
title={t('player.next')}
>
<Icon name="skip-forward" fill />
</button>
@@ -105,7 +104,7 @@ export function PersistentPlayer() {
),
)
}
title={`Repeat: ${player.repeat}`}
title={t('player.repeat', { mode: player.repeat })}
>
<Icon name="repeat" />
</button>
@@ -121,7 +120,7 @@ export function PersistentPlayer() {
step={1}
value={[player.position]}
onValueChange={([v]) => seek(v)}
aria-label="Seek"
aria-label={t('player.play')}
/>
<span className="pl-time">
{formatDuration(player.duration * 1000)}
@@ -129,13 +128,12 @@ export function PersistentPlayer() {
</div>
</div>
{/* volume + queue */}
<div className="pl-right">
<button
type="button"
className="pl-tbtn"
onClick={() => dispatch(toggleMute())}
title={player.muted ? 'Unmute' : 'Mute'}
title={player.muted ? t('player.unmute') : t('player.mute')}
>
<Icon name={player.muted ? 'speaker-x' : 'speaker-high'} />
</button>
@@ -154,7 +152,7 @@ export function PersistentPlayer() {
type="button"
className={`iconbtn sm${player.isQueueOpen ? ' on' : ''}`}
onClick={() => dispatch(toggleQueue())}
title="Play queue"
title={t('player.queue')}
>
<Icon name="queue" />
</button>