feat(i18n): translate login and app chrome (HU/EN/DE)

Login screen (with a language picker), header, account menu, sync status, About and
Release Notes dialogs, and the version banner are now fully translated in Hungarian,
English and German.
This commit is contained in:
npeter83 2026-06-15 00:30:34 +02:00
parent c165c3f274
commit b38ae92d8d
18 changed files with 279 additions and 57 deletions

View file

@ -1,4 +1,5 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import { Database, History, Loader2, Pause, Play } from "lucide-react";
import { api } from "../lib/api";
import { formatViews } from "../lib/format";
@ -14,6 +15,7 @@ export default function SyncStatus({
isAdmin: boolean;
onGoToFullHistory: () => void;
}) {
const { t } = useTranslation();
const qc = useQueryClient();
const { data } = useQuery({
queryKey: ["my-status"],
@ -34,37 +36,40 @@ export default function SyncStatus({
return (
<div className="hidden lg:flex items-center gap-2 text-xs text-muted">
<Tooltip hint="Videos from your subscriptions, against the total in the shared catalog that every user's subscriptions have pulled in.">
<Tooltip hint={t("header.sync.countTooltip")}>
<span className="flex items-center gap-1.5 cursor-default">
<Database className="w-3.5 h-3.5" />
<span>
<span className="text-fg font-medium">{formatViews(data.my_videos)}</span> yours
<span className="text-fg font-medium">{formatViews(data.my_videos)}</span>{" "}
{t("header.sync.yours")}
</span>
<span className="opacity-40">/</span>
<span>{formatViews(data.total_videos)} total</span>
<span>
{formatViews(data.total_videos)} {t("header.sync.total")}
</span>
</span>
</Tooltip>
<span className="opacity-40">·</span>
{data.paused ? (
<span className="text-accent font-medium">paused</span>
<span className="text-accent font-medium">{t("header.sync.paused")}</span>
) : syncing > 0 ? (
<span className="flex items-center gap-1">
<Loader2 className="w-3.5 h-3.5 animate-spin" />
{syncing} syncing
{t("header.sync.syncing", { count: syncing })}
</span>
) : (
<span>all synced</span>
<span>{t("header.sync.allSynced")}</span>
)}
{notFull > 0 && (
<>
<span className="opacity-40">·</span>
<Tooltip hint="Some of your channels only have their recent uploads. Click to open the channel manager (filtered to these) and request their full back-catalog.">
<Tooltip hint={t("header.sync.fullHistoryTooltip")}>
<button
onClick={onGoToFullHistory}
className="flex items-center gap-1 hover:text-fg underline decoration-dotted decoration-muted/40 underline-offset-4 transition cursor-pointer"
>
<History className="w-3.5 h-3.5" />
{notFull} without full history
{t("header.sync.withoutFullHistory", { count: notFull })}
</button>
</Tooltip>
</>
@ -75,7 +80,7 @@ export default function SyncStatus({
<button
onClick={() => toggle.mutate()}
disabled={toggle.isPending}
title={data.paused ? "Resume background sync" : "Pause background sync"}
title={data.paused ? t("header.sync.resume") : t("header.sync.pause")}
className="ml-1 p-1 rounded-md hover:bg-card hover:text-fg transition"
>
{data.paused ? <Play className="w-3.5 h-3.5" /> : <Pause className="w-3.5 h-3.5" />}