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:
parent
b38ae92d8d
commit
9c61bd898d
45 changed files with 1522 additions and 355 deletions
|
|
@ -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)}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue