feat: i18n
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { NavLink, useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Icon, type IconName } from '../common/Icon';
|
||||
import { useAppDispatch } from '../../hooks/useAppDispatch';
|
||||
import { usePermissions } from '../../hooks/usePermissions';
|
||||
@@ -9,24 +10,24 @@ import { getActiveInstance } from '../../config/instances';
|
||||
|
||||
interface NavDef {
|
||||
to: string;
|
||||
label: string;
|
||||
labelKey: string;
|
||||
icon: IconName;
|
||||
end?: boolean;
|
||||
}
|
||||
|
||||
const MAIN_NAV: NavDef[] = [
|
||||
{ to: '/', label: 'Home', icon: 'house', end: true },
|
||||
{ to: '/library', label: 'Library', icon: 'vinyl-record' },
|
||||
{ to: '/search', label: 'Search & download', icon: 'magnifying-glass' },
|
||||
{ to: '/downloads', label: 'Downloads', icon: 'arrow-circle-down' },
|
||||
{ to: '/storage', label: 'Storage', icon: 'hard-drives' },
|
||||
{ to: '/', labelKey: 'nav.home', icon: 'house', end: true },
|
||||
{ to: '/library', labelKey: 'nav.library', icon: 'vinyl-record' },
|
||||
{ to: '/search', labelKey: 'nav.search', icon: 'magnifying-glass' },
|
||||
{ to: '/downloads', labelKey: 'nav.downloads', icon: 'arrow-circle-down' },
|
||||
{ to: '/storage', labelKey: 'nav.storage', icon: 'hard-drives' },
|
||||
];
|
||||
|
||||
const CONN_CLASS: Record<string, { cls: string; txt: string }> = {
|
||||
connected: { cls: 'online', txt: 'Connected' },
|
||||
connecting: { cls: 'syncing', txt: 'Connecting…' },
|
||||
disconnected: { cls: 'offline', txt: 'Offline' },
|
||||
error: { cls: 'error', txt: 'Unreachable' },
|
||||
const CONN_KEY: Record<string, { cls: string; txtKey: string }> = {
|
||||
connected: { cls: 'online', txtKey: 'conn.connected' },
|
||||
connecting: { cls: 'syncing', txtKey: 'conn.connecting' },
|
||||
disconnected: { cls: 'offline', txtKey: 'conn.disconnected' },
|
||||
error: { cls: 'error', txtKey: 'conn.error' },
|
||||
};
|
||||
|
||||
function navClass({ isActive }: { isActive: boolean }) {
|
||||
@@ -34,6 +35,7 @@ function navClass({ isActive }: { isActive: boolean }) {
|
||||
}
|
||||
|
||||
export function Sidebar() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
const { user, isAdmin } = usePermissions();
|
||||
@@ -41,7 +43,7 @@ export function Sidebar() {
|
||||
const { data: playlists } = useGetPlaylistsQuery();
|
||||
const instance = getActiveInstance();
|
||||
|
||||
const conn = CONN_CLASS[status] ?? CONN_CLASS.connecting;
|
||||
const conn = CONN_KEY[status] ?? CONN_KEY.connecting;
|
||||
const online = status === 'connected';
|
||||
|
||||
const handleLogout = (e: React.MouseEvent) => {
|
||||
@@ -59,16 +61,16 @@ export function Sidebar() {
|
||||
</div>
|
||||
|
||||
<div className="sb-sec">
|
||||
{MAIN_NAV.map(({ to, label, icon, end }) => (
|
||||
{MAIN_NAV.map(({ to, labelKey, icon, end }) => (
|
||||
<NavLink key={to} to={to} end={end} className={navClass}>
|
||||
<Icon name={icon} />
|
||||
<span>{label}</span>
|
||||
<span>{t(labelKey)}</span>
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="sb-sec">
|
||||
<span className="msk-label">Playlists</span>
|
||||
<span className="msk-label">{t('nav.playlists')}</span>
|
||||
{(playlists?.items ?? []).map((pl) => (
|
||||
<NavLink
|
||||
key={pl.id}
|
||||
@@ -85,27 +87,27 @@ export function Sidebar() {
|
||||
onClick={() => void navigate('/library')}
|
||||
>
|
||||
<Icon name="plus" />
|
||||
<span className="pl-name">New playlist</span>
|
||||
<span className="pl-name">{t('nav.newPlaylist')}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isAdmin ? (
|
||||
<div className="sb-sec">
|
||||
<span className="msk-label">Administration</span>
|
||||
<span className="msk-label">{t('nav.administration')}</span>
|
||||
<NavLink to="/admin" className={navClass}>
|
||||
<Icon name="shield-check" />
|
||||
<span>Admin</span>
|
||||
<span>{t('nav.admin')}</span>
|
||||
</NavLink>
|
||||
<NavLink to="/settings" className={navClass}>
|
||||
<Icon name="gear-six" />
|
||||
<span>Settings</span>
|
||||
<span>{t('nav.settings')}</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
) : (
|
||||
<div className="sb-sec">
|
||||
<NavLink to="/settings" className={navClass}>
|
||||
<Icon name="gear-six" />
|
||||
<span>Settings</span>
|
||||
<span>{t('nav.settings')}</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
)}
|
||||
@@ -116,10 +118,10 @@ export function Sidebar() {
|
||||
type="button"
|
||||
className={`conn ${conn.cls}`}
|
||||
onClick={() => void navigate('/connect')}
|
||||
title="Connection — manage instances"
|
||||
title={t('conn.manage')}
|
||||
>
|
||||
<span className="led" />
|
||||
{conn.txt}
|
||||
{t(conn.txtKey)}
|
||||
</button>
|
||||
{user && (
|
||||
<button
|
||||
@@ -133,10 +135,10 @@ export function Sidebar() {
|
||||
<div className="user-meta">
|
||||
<div className="nm">{user.username}</div>
|
||||
<div className="rl">
|
||||
{user.role} · {online ? 'online' : 'offline'}
|
||||
{user.role} · {online ? t('user.online') : t('user.offline')}
|
||||
</div>
|
||||
</div>
|
||||
<span className="uc-action" onClick={handleLogout} title="Sign out">
|
||||
<span className="uc-action" onClick={handleLogout} title={t('user.signOut')}>
|
||||
<Icon name="sign-out" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user