feat(i18n): translate remaining components (HU/EN/DE)

Feed, VideoCard, Sidebar, PlayerModal, Channels, Stats, SettingsPanel,
OnboardingWizard, NotificationCenter, Toaster, ErrorBoundary and the relativeTime
helper are now fully translated in Hungarian, English and German, each with its own
locale area file (auto-loaded). Key parity verified across all three languages.
This commit is contained in:
npeter83 2026-06-15 00:47:04 +02:00
parent 941fb7d756
commit ea317c0009
45 changed files with 1522 additions and 355 deletions

View file

@ -1,4 +1,5 @@
import { memo } from "react";
import { useTranslation } from "react-i18next";
import {
Bookmark,
Check,
@ -23,6 +24,7 @@ function Actions({
onState: (id: string, status: string) => void;
onChannelFilter?: (channelId: string, channelName: string) => void;
}) {
const { t } = useTranslation();
const act = (status: string) => (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
@ -32,7 +34,7 @@ function Actions({
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition">
<button
onClick={act("watched")}
title={video.status === "watched" ? "Watched — click to unmark" : "Mark watched"}
title={video.status === "watched" ? t("card.watchedUnmark") : t("card.markWatched")}
className={clsx(
"p-1.5 rounded-md hover:bg-surface text-muted hover:text-fg",
video.status === "watched" && "text-accent"
@ -46,7 +48,7 @@ function Actions({
</button>
<button
onClick={act("saved")}
title={video.status === "saved" ? "Saved — click to remove" : "Save for later"}
title={video.status === "saved" ? t("card.savedRemove") : t("card.saveForLater")}
className={clsx(
"p-1.5 rounded-md hover:bg-surface text-muted hover:text-fg",
video.status === "saved" && "fill-current text-accent"
@ -56,7 +58,7 @@ function Actions({
</button>
<button
onClick={act("hidden")}
title={video.status === "hidden" ? "Unhide" : "Hide"}
title={video.status === "hidden" ? t("card.unhide") : t("card.hide")}
className={clsx(
"p-1.5 rounded-md hover:bg-surface text-muted hover:text-fg",
video.status === "hidden" && "text-accent"
@ -73,9 +75,9 @@ function Actions({
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onChannelFilter(video.channel_id, video.channel_title ?? "This channel");
onChannelFilter(video.channel_id, video.channel_title ?? t("card.thisChannel"));
}}
title="Only this channel"
title={t("card.onlyThisChannel")}
className="p-1.5 rounded-md hover:bg-surface text-muted hover:text-fg"
>
<ListFilter className="w-4 h-4" />
@ -106,6 +108,7 @@ function Thumb({
className?: string;
onOpen?: (v: Video, startAt?: number | null) => void;
}) {
const { t } = useTranslation();
// A non-zero saved position on an unfinished video = "in progress": show a resume
// progress bar and offer Continue / Restart instead of a plain Play.
const inProgress = video.position_seconds > 0 && video.status !== "watched";
@ -149,7 +152,7 @@ function Thumb({
)}
{video.live_status === "was_live" && (
<span className="absolute top-1.5 left-1.5 bg-accent text-accent-fg text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded">
stream
{t("card.stream")}
</span>
)}
{video.status === "saved" && (
@ -164,25 +167,25 @@ function Thumb({
<>
<button
onClick={open(null)}
title="Continue where you left off"
title={t("card.continueTitle")}
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-semibold bg-accent text-accent-fg shadow-lg hover:opacity-90 transition"
>
<Play className="w-4 h-4 fill-current" />
Continue
{t("card.continue")}
</button>
<button
onClick={open(0)}
title="Play from the beginning"
title={t("card.restartTitle")}
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-medium bg-white/15 text-white backdrop-blur-sm hover:bg-white/25 transition"
>
<RotateCcw className="w-4 h-4" />
Restart
{t("card.restart")}
</button>
</>
) : (
<button
onClick={open(null)}
title="Play"
title={t("card.play")}
className="inline-flex items-center justify-center w-12 h-12 rounded-full bg-accent text-accent-fg shadow-lg hover:scale-105 transition"
>
<Play className="w-5 h-5 fill-current translate-x-[1px]" />
@ -213,10 +216,15 @@ function VideoCard({
onChannelFilter?: (channelId: string, channelName: string) => void;
onOpen?: (v: Video, startAt?: number | null) => void;
}) {
const { t } = useTranslation();
const watched = video.status === "watched";
const meta = (
<>
{video.view_count != null && <>{formatViews(video.view_count)} views · </>}
{video.view_count != null && (
<>
{formatViews(video.view_count)} {t("card.views")} ·{" "}
</>
)}
{relativeTime(video.published_at)}
</>
);