feat(panel): i18n

This commit is contained in:
Lemon-miaow committed 2026-06-27 18:31:26 +08:00
1 parent a5a64821d8
commit dd2fc6fa82
47 files changed
+898 -231

No files matched your search

+18 -16
View File
@@ -9,6 +9,7 @@ import type {
WhitelistImage,
} from "./types";
import { loadConfig } from "./config";
import i18next from "i18next";
// Typed client for the felis-api external face (spec §7). Credentials are sent so
// the upstream Zero-Trust / Access cookie rides along; the panel never holds a
@@ -126,37 +127,38 @@ export function consoleStreamURL(apiBase: string, name: string): string {
/** humanizeError turns the stable error code into a user-facing line. */
export function humanizeError(e: unknown): string {
const err = e as Partial<ApiError>;
const t = i18next.getFixedT(null, "errors");
switch (err.code) {
// Local-password auth (spec §B1).
case "local_auth_disabled":
return "Password sign-in is turned off here — reach this console through your organization's secure access.";
return t("local_auth_disabled");
case "invalid_credentials":
return "Incorrect username or password.";
return t("invalid_credentials");
case "weak_password":
return "Pick a password between 8 and 72 characters.";
return t("weak_password");
case "password_unchanged":
return "Your new password must differ from the current one.";
return t("password_unchanged");
case "not_linked":
return "Link your Minecraft account before claiming (Account → Link).";
return t("not_linked");
case "invalid_code":
return "That link code is invalid or expired — run /link again in-game for a fresh code.";
return t("invalid_code");
case "already_linked":
return "That Minecraft account is already linked to another user.";
return t("already_linked");
case "quota_exceeded":
return "You have reached your server quota.";
return t("quota_exceeded");
case "already_claimed":
return "Someone else just claimed this server.";
return t("already_claimed");
case "image_not_whitelisted":
return "That image is not on the whitelist.";
return t("image_not_whitelisted");
case "subdomain_taken":
return "That subdomain is already in use.";
return t("subdomain_taken");
case "already_exists":
return "A server with that name already exists.";
return t("already_exists");
case "cooldown":
return "Wake is cooling down — try again shortly.";
return t("cooldown");
default:
if (err.status === 401) return "Your session expired — please sign in again.";
if (err.status === 403) return "You are not allowed to do that.";
return err.message ?? "Something went wrong.";
if (err.status === 401) return t("session_expired");
if (err.status === 403) return t("forbidden");
return err.message ?? t("generic");
}
}
+14 -14
View File
@@ -23,7 +23,7 @@ import {
export interface NavItem {
to: string;
label: string;
key: string;
icon: LucideIcon;
/** react-router NavLink `end` — exact-match active styling for index routes. */
end?: boolean;
@@ -31,8 +31,8 @@ export interface NavItem {
export interface NavSection {
id: "user" | "admin" | "ops";
/** Section heading shown in the sidebar; null renders no heading (User-Side). */
title: string | null;
/** Section heading key; null renders no heading (User-Side). */
titleKey: string | null;
/** When true the section is shown only to admins (is_admin === true). */
adminOnly: boolean;
items: NavItem[];
@@ -41,31 +41,31 @@ export interface NavSection {
export const NAV_SECTIONS: NavSection[] = [
{
id: "user",
title: null,
titleKey: null,
adminOnly: false,
items: [
{ to: "/", label: "Dashboard", icon: LayoutDashboard, end: true },
{ to: "/servers", label: "My servers", icon: Server },
{ to: "/account", label: "Account", icon: UserRound },
{ to: "/", key: "dashboard", icon: LayoutDashboard, end: true },
{ to: "/servers", key: "my_servers", icon: Server },
{ to: "/account", key: "account", icon: UserRound },
],
},
{
id: "admin",
title: "Admin",
titleKey: "admin_section",
adminOnly: true,
items: [
{ to: "/admin", label: "Overview", icon: ShieldCheck, end: true },
{ to: "/admin/servers", label: "Servers", icon: ServerCog },
{ to: "/admin/images", label: "Images", icon: Boxes },
{ to: "/admin", key: "admin_overview", icon: ShieldCheck, end: true },
{ to: "/admin/servers", key: "admin_servers", icon: ServerCog },
{ to: "/admin/images", key: "admin_images", icon: Boxes },
],
},
{
id: "ops",
title: "SysAdmin",
titleKey: "sysadmin_section",
adminOnly: true,
items: [
{ to: "/ops", label: "Overview", icon: Gauge, end: true },
{ to: "/ops/fleet", label: "Fleet", icon: Network },
{ to: "/ops", key: "sysadmin_overview", icon: Gauge, end: true },
{ to: "/ops/fleet", key: "sysadmin_fleet", icon: Network },
],
},
];