add i18n to frontend

This commit is contained in:
Eduard Gert
2026-05-15 16:22:14 +02:00
parent cccb0e9230
commit 5bdccfe8f4
44 changed files with 1953 additions and 932 deletions
@@ -1,4 +1,5 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/Button";
import { useClientVersion } from "@/modules/auto-update/ClientVersionContext";
import { cn } from "@/lib/cn";
@@ -9,6 +10,7 @@ import { cn } from "@/lib/cn";
// tray menu instead; the force-install branch (installing=true) takes over
// with the full-screen UpdatingOverlay.
export const UpdateAvailableBanner = () => {
const { t } = useTranslation();
const { updateVersion, enforced, installing, triggerUpdate } = useClientVersion();
const [dismissed, setDismissed] = useState(false);
@@ -26,14 +28,14 @@ export const UpdateAvailableBanner = () => {
)}
>
<p className={"text-sm text-nb-gray-900 pr-4 pl-2 font-medium"}>
NetBird {updateVersion} is ready to install.
{t("update.banner.message", { version: updateVersion })}
</p>
<div className={"flex gap-2"}>
<Button variant={"subtle"} size={"xs"} onClick={() => setDismissed(true)}>
Later
{t("update.banner.later")}
</Button>
<Button variant={"primary"} size={"xs"} onClick={triggerUpdate}>
Install now
{t("update.banner.installNow")}
</Button>
</div>
</div>
@@ -1,17 +1,19 @@
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { ArrowUpCircleIcon } from "lucide-react";
import { IconButton } from "@/components/IconButton.tsx";
import { Tooltip } from "@/components/Tooltip.tsx";
import { useClientVersion } from "@/modules/auto-update/ClientVersionContext";
export const UpdateHeaderTrigger = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const { updateAvailable } = useClientVersion();
if (!updateAvailable) return null;
return (
<Tooltip content={"Update Available"}>
<Tooltip content={t("update.header.tooltip")}>
<div className={"relative h-11 w-11 flex items-center justify-center"}>
<span
className={
@@ -1,4 +1,5 @@
import { ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { Browser } from "@wailsio/runtime";
import { Button } from "@/components/Button";
import { useClientVersion } from "@/modules/auto-update/ClientVersionContext";
@@ -10,8 +11,8 @@ function openUrl(url: string) {
void Browser.OpenURL(url).catch(() => window.open(url, "_blank"));
}
function formatLastChecked(date: Date) {
return date.toLocaleString(undefined, {
function formatLastChecked(date: Date, locale?: string) {
return date.toLocaleString(locale, {
month: "short",
day: "numeric",
hour: "2-digit",
@@ -20,22 +21,23 @@ function formatLastChecked(date: Date) {
}
export function UpdateVersionCard() {
const { t, i18n } = useTranslation();
const { updateVersion, enforced, triggerUpdate } = useClientVersion();
if (updateVersion) {
return (
<Card>
<div>
<Title>Version {updateVersion} is available.</Title>
<Title>{t("update.card.versionAvailable", { version: updateVersion })}</Title>
<Link
url={`https://github.com/netbirdio/netbird/releases/tag/v${updateVersion}`}
>
What's new?
{t("update.card.whatsNew")}
</Link>
</div>
{enforced ? (
<Button variant={"primary"} size={"xs"} onClick={triggerUpdate}>
Install now
{t("update.card.installNow")}
</Button>
) : (
<Button
@@ -43,7 +45,7 @@ export function UpdateVersionCard() {
size={"xs"}
onClick={() => openUrl(GITHUB_RELEASES)}
>
Get installer
{t("update.card.getInstaller")}
</Button>
)}
</Card>
@@ -53,11 +55,15 @@ export function UpdateVersionCard() {
return (
<Card className={"max-w-md"}>
<div>
<Title>Last checked on {formatLastChecked(new Date())}</Title>
<Link url={"https://github.com/netbirdio/netbird/releases/latest"}>Changelog</Link>
<Title>
{t("update.card.lastChecked", {
date: formatLastChecked(new Date(), i18n.language),
})}
</Title>
<Link url={GITHUB_RELEASES}>{t("update.card.changelog")}</Link>
</div>
<Button variant={"primary"} size={"xs"} onClick={triggerUpdate}>
Check for updates
{t("update.card.checkForUpdates")}
</Button>
</Card>
);
@@ -1,3 +1,4 @@
import { useTranslation } from "react-i18next";
import { Loader2, XCircle } from "lucide-react";
import { Button } from "@/components/Button";
@@ -13,31 +14,38 @@ type Variant = {
message?: string;
};
function classifyError(msg: string, version: string | null): Variant {
function classifyError(
msg: string,
version: string | null,
t: (key: string, options?: Record<string, unknown>) => string,
): Variant {
const lower = msg.toLowerCase();
const target = version ? `v${version}` : "the new version";
const target = version
? t("update.overlay.error.targetVersion", { version })
: t("update.overlay.error.targetFallback");
if (lower.includes("timeout") || lower.includes("timed out")) {
return {
title: "Update Is Taking Too Long",
description: `Installing ${target} took too long and didn't finish.`,
title: t("update.overlay.error.timeoutTitle"),
description: t("update.overlay.error.timeoutDescription", { target }),
};
}
if (lower.includes("cancel")) {
return {
title: "Update Was Stopped",
description: `The update to ${target} was canceled before it finished.`,
title: t("update.overlay.error.canceledTitle"),
description: t("update.overlay.error.canceledDescription", { target }),
};
}
return {
title: "Couldn't Install the Update",
description: `${target} couldn't be installed.`,
message: msg || "unknown error",
title: t("update.overlay.error.failTitle"),
description: t("update.overlay.error.failDescription", { target }),
message: msg || t("update.overlay.error.unknownMessage"),
};
}
export const UpdatingOverlay = ({ version, error, onDismiss }: Props) => {
const { t } = useTranslation();
const isError = Boolean(error);
const errorInfo = error ? classifyError(error, version) : null;
const errorInfo = error ? classifyError(error, version, t) : null;
return (
<div
@@ -75,8 +83,8 @@ export const UpdatingOverlay = ({ version, error, onDismiss }: Props) => {
{isError
? errorInfo!.title
: version
? `Updating NetBird to v${version}`
: "Updating NetBird"}
? t("update.overlay.updatingVersion", { version })
: t("update.overlay.updating")}
</p>
<p className={"text-sm text-nb-gray-300"}>
{isError ? (
@@ -92,7 +100,7 @@ export const UpdatingOverlay = ({ version, error, onDismiss }: Props) => {
)}
</>
) : (
"A newer version is available and is being installed. NetBird will restart automatically once the update is finished."
t("update.overlay.description")
)}
</p>
</div>
@@ -100,7 +108,7 @@ export const UpdatingOverlay = ({ version, error, onDismiss }: Props) => {
{isError && (
<div className={"wails-no-draggable"}>
<Button variant={"secondary"} size={"xs"} onClick={onDismiss}>
Close
{t("common.close")}
</Button>
</div>
)}