feat: better translation

This commit is contained in:
hamster1963 2024-09-26 11:16:37 +08:00
parent 3234f325e1
commit 3de5a8193b
20 changed files with 373 additions and 180 deletions

View File

@ -1,16 +1,13 @@
"use client";
import { useTranslations } from 'next-intl';
import { ServerApi } from "../../types/nezha-api";
import ServerCard from "../../../../components/ServerCard";
import { nezhaFetcher } from "../../../../lib/utils";
import useSWR from "swr";
import getEnv from "../../../../lib/env-entry";
export default function ServerListClient() {
const {
data
} = useSWR<ServerApi>("/api/server", nezhaFetcher, {
refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000
const { data } = useSWR<ServerApi>("/api/server", nezhaFetcher, {
refreshInterval: Number(getEnv("NEXT_PUBLIC_NezhaFetchInterval")) || 2000,
});
if (!data) return null;
const sortedServers = data.result.sort((a, b) => {
@ -21,7 +18,11 @@ export default function ServerListClient() {
if (b.display_index) return 1;
return a.id - b.id;
});
return <section className="grid grid-cols-1 gap-2 md:grid-cols-2">
{sortedServers.map(serverInfo => <ServerCard key={serverInfo.id} serverInfo={serverInfo} />)}
</section>;
return (
<section className="grid grid-cols-1 gap-2 md:grid-cols-2">
{sortedServers.map((serverInfo) => (
<ServerCard key={serverInfo.id} serverInfo={serverInfo} />
))}
</section>
);
}

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,25 @@
import { useTranslations } from 'next-intl';
import { useTranslations } from "next-intl";
export default function Footer() {
const t = useTranslations("Footer");
return <footer className="mx-auto w-full max-w-5xl">
return (
<footer className="mx-auto w-full max-w-5xl">
<section className="flex flex-col">
<p className="mt-3 flex gap-1 text-[13px] font-light tracking-tight text-neutral-600/50 dark:text-neutral-300/50">{t('p_146-598_Findthecodeon')}{" "}
<a href="https://github.com/hamster1963/nezha-dash" target="_blank" className="cursor-pointer font-normal underline decoration-yellow-500 decoration-2 underline-offset-2 dark:decoration-yellow-500/50">{t('a_303-585_GitHub')}</a>
<p className="mt-3 flex gap-1 text-[13px] font-light tracking-tight text-neutral-600/50 dark:text-neutral-300/50">
{t("p_146-598_Findthecodeon")}{" "}
<a
href="https://github.com/hamster1963/nezha-dash"
target="_blank"
className="cursor-pointer font-normal underline decoration-yellow-500 decoration-2 underline-offset-2 dark:decoration-yellow-500/50"
>
{t("a_303-585_GitHub")}
</a>
</p>
<section className="mt-1 flex items-center gap-2 text-[13px] font-light tracking-tight text-neutral-600/50 dark:text-neutral-300/50">{t('section_607-869_2020')}{new Date().getFullYear()}{" "}
<a href={"https://buycoffee.top"}>{t('a_800-850_Hamster1963')}</a>
<section className="mt-1 flex items-center gap-2 text-[13px] font-light tracking-tight text-neutral-600/50 dark:text-neutral-300/50">
{t("section_607-869_2020")}
{new Date().getFullYear()}{" "}
<a href={"https://buycoffee.top"}>{t("a_800-850_Hamster1963")}</a>
</section>
</section>
</footer>;
</footer>
);
}

View File

@ -1,6 +1,6 @@
"use client";
import { useTranslations } from 'next-intl';
import { useTranslations } from "next-intl";
import React, { useEffect, useRef, useState } from "react";
import Image from "next/image";
import { Separator } from "../../../components/ui/separator";
@ -8,20 +8,34 @@ import { DateTime } from "luxon";
import { ModeToggle } from "../../../components/ThemeSwitcher";
function Header() {
const t = useTranslations("Header");
return <div className="mx-auto w-full max-w-5xl">
return (
<div className="mx-auto w-full max-w-5xl">
<section className="flex items-center justify-between">
<section className="flex items-center text-base font-medium">
<div className="mr-1 flex flex-row items-center justify-start">
<Image width={40} height={40} unoptimized alt="apple-touch-icon" src={"/apple-touch-icon.png"} className="relative !m-0 h-6 w-6 border-2 border-white object-cover object-top !p-0 transition duration-500 group-hover:z-30 group-hover:scale-105" />
<Image
width={40}
height={40}
unoptimized
alt="apple-touch-icon"
src={"/apple-touch-icon.png"}
className="relative !m-0 h-6 w-6 border-2 border-white object-cover object-top !p-0 transition duration-500 group-hover:z-30 group-hover:scale-105"
/>
</div>
NezhaDash
<Separator orientation="vertical" className="mx-2 hidden h-4 w-[1px] md:block" />
<p className="hidden text-sm font-medium opacity-40 md:block">{t('p_1079-1199_Simpleandbeautifuldashbo')}</p>
<Separator
orientation="vertical"
className="mx-2 hidden h-4 w-[1px] md:block"
/>
<p className="hidden text-sm font-medium opacity-40 md:block">
{t("p_1079-1199_Simpleandbeautifuldashbo")}
</p>
</section>
<ModeToggle />
</section>
<Overview />
</div>;
</div>
);
}
// https://github.com/streamich/react-use/blob/master/src/useInterval.ts
@ -46,16 +60,24 @@ function Overview() {
}, []);
const timeOption = DateTime.TIME_SIMPLE;
timeOption.hour12 = true;
const [timeString, setTimeString] = useState(DateTime.now().setLocale("en-US").toLocaleString(timeOption));
const [timeString, setTimeString] = useState(
DateTime.now().setLocale("en-US").toLocaleString(timeOption),
);
useInterval(() => {
setTimeString(DateTime.now().setLocale("en-US").toLocaleString(timeOption));
}, 1000);
return <section className={"mt-10 flex flex-col md:mt-16"}>
<p className="text-base font-semibold">{t('p_2277-2331_Overview')}</p>
return (
<section className={"mt-10 flex flex-col md:mt-16"}>
<p className="text-base font-semibold">{t("p_2277-2331_Overview")}</p>
<div className="flex items-center gap-1.5">
<p className="text-sm font-medium opacity-50">{t('p_2390-2457_wherethetimeis')}</p>
{mouted && <p className="opacity-1 text-sm font-medium">{timeString}</p>}
<p className="text-sm font-medium opacity-50">
{t("p_2390-2457_wherethetimeis")}
</p>
{mouted && (
<p className="opacity-1 text-sm font-medium">{timeString}</p>
)}
</div>
</section>;
</section>
);
}
export default Header;

View File

@ -1,18 +1,18 @@
import { useTranslations } from 'next-intl';
import { useTranslations } from "next-intl";
import React from "react";
import Header from "@/app/[locale]/(main)/header";
import Footer from "./footer";
type DashboardProps = {
children: React.ReactNode;
};
export default function MainLayout({
children
}: DashboardProps) {
return <div className="flex min-h-screen w-full flex-col">
export default function MainLayout({ children }: DashboardProps) {
return (
<div className="flex min-h-screen w-full flex-col">
<main className="flex min-h-[calc(100vh_-_theme(spacing.16))] flex-1 flex-col gap-4 bg-muted/40 p-4 md:p-10 md:pt-8">
<Header />
{children}
<Footer />
</main>
</div>;
</div>
);
}

View File

@ -1,20 +1,26 @@
import { useTranslations } from 'next-intl';
import { useTranslations } from "next-intl";
import ServerList from "../../../components/ServerList";
import ServerOverview from "../../../components/ServerOverview";
import getEnv from "../../../lib/env-entry";
import { GetNezhaData } from "../../../lib/serverFetch";
import { SWRConfig } from "swr";
const disablePrefetch = getEnv("ServerDisablePrefetch") === "true";
const fallback = disablePrefetch ? {} : {
"/api/server": GetNezhaData()
};
const fallback = disablePrefetch
? {}
: {
"/api/server": GetNezhaData(),
};
export default function Home() {
return <SWRConfig value={{
fallback: fallback
}}>
return (
<SWRConfig
value={{
fallback: fallback,
}}
>
<div className="mx-auto grid w-full max-w-5xl gap-4 md:gap-6">
<ServerOverview />
<ServerList />
</div>
</SWRConfig>;
</SWRConfig>
);
}

View File

@ -1,13 +1,14 @@
// @auto-i18n-check. Please do not delete the line.
import React from "react";
import {NextIntlClientProvider, useMessages} from 'next-intl';
import { NextIntlClientProvider, useMessages } from "next-intl";
export default function LocaleLayout({
children,
params: {locale}
}: {
children: React.ReactNode; params: { locale: string };
params: { locale },
}: {
children: React.ReactNode;
params: { locale: string };
}) {
const messages = useMessages();
return (

View File

@ -1,16 +1,30 @@
import { useTranslations } from 'next-intl';
import { useTranslations } from "next-intl";
import Image from "next/image";
import Link from "next/link";
export default function NotFoundPage() {
const t = useTranslations("NotFoundPage");
return <main className="relative h-screen w-full">
return (
<main className="relative h-screen w-full">
<div className="absolute inset-0 m-4 flex items-center justify-center">
<Image priority className="rounded-3xl object-cover" src="/tardis.jpg" fill={true} alt="TARDIS" />
<Image
priority
className="rounded-3xl object-cover"
src="/tardis.jpg"
fill={true}
alt="TARDIS"
/>
<div className="text-container absolute right-4 p-4 md:right-20">
<h1 className="text-2xl font-bold opacity-80 md:text-5xl">{t('h1_490-590_404NotFound')}</h1>
<p className="text-lg opacity-60 md:text-base">{t('p_601-665_TARDISERROR')}</p>
<Link href={"/"} className="text-2xl opacity-80 md:text-3xl">{t('Link_676-775_Doctor')}</Link>
<h1 className="text-2xl font-bold opacity-80 md:text-5xl">
{t("h1_490-590_404NotFound")}
</h1>
<p className="text-lg opacity-60 md:text-base">
{t("p_601-665_TARDISERROR")}
</p>
<Link href={"/"} className="text-2xl opacity-80 md:text-3xl">
{t("Link_676-775_Doctor")}
</Link>
</div>
</div>
</main>;
</main>
);
}

View File

@ -1,12 +1,7 @@
{
"defaultLang": "en",
"translatorServerName": "azure",
"needLangs": [
"en",
"zh",
"zh-t",
"ja"
],
"needLangs": ["en", "zh", "zh-t", "ja"],
"brandWords": [],
"unMoveToLocaleDirFiles": [],
"enableStaticRendering": false,

View File

@ -1,3 +1,4 @@
import { useTranslations } from "next-intl";
import { NezhaAPISafe } from "../app/[locale]/types/nezha-api";
import ServerUsageBar from "@/components/ServerUsageBar";
import { Card } from "@/components/ui/card";
@ -16,6 +17,7 @@ export default function ServerCard({
}: {
serverInfo: NezhaAPISafe;
}) {
const t = useTranslations("ServerCard");
const { name, country_code, online, cpu, up, down, mem, stg, ...props } =
formatNezhaInfo(serverInfo);
@ -55,36 +57,46 @@ export default function ServerCard({
</PopoverContent>
</Popover>
<section className={"grid grid-cols-5 items-center gap-3"}>
<div className={"flex flex-col"}>
<p className="text-xs text-muted-foreground">CPU</p>
<div className={"flex w-14 flex-col"}>
{" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("CPU")}</p>
<div className="flex items-center text-xs font-semibold">
{cpu.toFixed(2)}%
</div>
<ServerUsageBar value={cpu} />
</div>
<div className={"flex flex-col"}>
<p className="text-xs text-muted-foreground">Mem</p>
<div className={"flex w-14 flex-col"}>
{" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("Mem")}</p>
<div className="flex items-center text-xs font-semibold">
{mem.toFixed(2)}%
</div>
<ServerUsageBar value={mem} />
</div>
<div className={"flex flex-col"}>
<p className="text-xs text-muted-foreground">STG</p>
<div className={"flex w-14 flex-col"}>
{" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("STG")}</p>
<div className="flex items-center text-xs font-semibold">
{stg.toFixed(2)}%
</div>
<ServerUsageBar value={stg} />
</div>
<div className={"flex flex-col"}>
<p className="text-xs text-muted-foreground">Upload</p>
<div className={"flex w-14 flex-col"}>
{" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("Upload")}</p>
<div className="flex items-center text-xs font-semibold">
{up.toFixed(2)}
Mb/s
</div>
</div>
<div className={"flex flex-col"}>
<p className="text-xs text-muted-foreground">Download</p>
<div className={"flex w-14 flex-col"}>
{" "}
{/* 设置固定宽度 */}
<p className="text-xs text-muted-foreground">{t("Download")}</p>
<div className="flex items-center text-xs font-semibold">
{down.toFixed(2)}
Mb/s
@ -122,7 +134,7 @@ export default function ServerCard({
</section>
</PopoverTrigger>
<PopoverContent className="w-fit p-2" side="top">
<p className="text-sm text-muted-foreground">Offline</p>
<p className="text-sm text-muted-foreground">{t("Offline")}</p>
</PopoverContent>
</Popover>
</Card>

View File

@ -1,3 +1,4 @@
import { useTranslations } from "next-intl";
import { NezhaAPISafe } from "../app/[locale]/types/nezha-api";
import { cn, formatBytes } from "@/lib/utils";
@ -31,39 +32,40 @@ export default function ServerCardPopover({
host: NezhaAPISafe["host"];
status: NezhaAPISafe["status"];
}) {
const t = useTranslations("ServerCardPopover");
return (
<section className="max-w-[300px]">
<ServerCardPopoverCard
title="System"
title={t("System")}
content={`${host.Platform}-${host.PlatformVersion} [${host.Virtualization}: ${host.Arch}]`}
/>
<ServerCardPopoverCard
title="CPU"
title={t("CPU")}
content={`${host.CPU.map((item) => item).join(", ")}`}
/>
<ServerCardPopoverCard
title="Mem"
title={t("Mem")}
content={`${formatBytes(status.MemUsed)} / ${formatBytes(host.MemTotal)}`}
/>
<ServerCardPopoverCard
title="STG"
title={t("STG")}
content={`${formatBytes(status.DiskUsed)} / ${formatBytes(host.DiskTotal)}`}
/>
<ServerCardPopoverCard
title="Swap"
title={t("Swap")}
content={`${formatBytes(status.SwapUsed)} / ${formatBytes(host.SwapTotal)}`}
/>
<ServerCardPopoverCard
title="Network"
title={t("Network")}
content={`${formatBytes(status.NetOutTransfer)} / ${formatBytes(status.NetInTransfer)}`}
/>
<ServerCardPopoverCard
title="Load"
title={t("Load")}
content={`${status.Load1.toFixed(2)} / ${status.Load5.toFixed(2)} / ${status.Load15.toFixed(2)}`}
/>
<ServerCardPopoverCard
className="mb-0"
title="Online"
title={t("Online")}
content={`${(status.Uptime / 86400).toFixed(0)} Days`}
/>
</section>

View File

@ -1,5 +1,6 @@
"use client";
import { useTranslations } from "next-intl";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import * as React from "react";
@ -14,7 +15,7 @@ import {
export function ModeToggle() {
const { setTheme } = useTheme();
const t = useTranslations("ThemeSwitcher");
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@ -26,13 +27,13 @@ export function ModeToggle() {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
{t("Light")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
{t("Dark")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
{t("System")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View File

@ -1,24 +1,24 @@
// @auto-i18n-check. Please do not delete the line.
export const localeItems = [
{code: 'en', name: 'English'},
{code: 'ja', name: '日本語'},
{code: 'zh-t', name: '中文繁體'},
{code: 'zh', name: '中文简体'},
//{code: 'ar', name: 'العربية'},
//{code: 'de', name: 'Deutsch'},
//{code: 'es', name: 'Español'},
//{code: 'fr', name: 'Français'},
//{code: 'hi', name: 'हिन्दी'},
//{code: 'id', name: 'Bahasa Indonesia'},
//{code: 'it', name: 'Italiano'},
//{code: 'ko', name: '한국어'},
//{code: 'ms', name: 'Bahasa Melayu'},
//{code: 'pt', name: 'Português'},
//{code: 'ru', name: 'Русский'},
//{code: 'th', name: 'ไทย'},
//{code: 'vi', name: 'Tiếng Việt'},
{ code: "en", name: "English" },
{ code: "ja", name: "日本語" },
{ code: "zh-t", name: "中文繁體" },
{ code: "zh", name: "中文简体" },
//{code: 'ar', name: 'العربية'},
//{code: 'de', name: 'Deutsch'},
//{code: 'es', name: 'Español'},
//{code: 'fr', name: 'Français'},
//{code: 'hi', name: 'हिन्दी'},
//{code: 'id', name: 'Bahasa Indonesia'},
//{code: 'it', name: 'Italiano'},
//{code: 'ko', name: '한국어'},
//{code: 'ms', name: 'Bahasa Melayu'},
//{code: 'pt', name: 'Português'},
//{code: 'ru', name: 'Русский'},
//{code: 'th', name: 'ไทย'},
//{code: 'vi', name: 'Tiếng Việt'},
];
export const locales = localeItems.map((item) => item.code);
export const defaultLocale = 'en';
export const defaultLocale = "en";

10
i18n.ts
View File

@ -1,14 +1,14 @@
// @auto-i18n-check. Please do not delete the line.
import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';
import {locales} from "./i18n-metadata";
import { notFound } from "next/navigation";
import { getRequestConfig } from "next-intl/server";
import { locales } from "./i18n-metadata";
export default getRequestConfig(async ({locale}) => {
export default getRequestConfig(async ({ locale }) => {
// Validate that the incoming `locale` parameter is valid
if (!locales.includes(locale as any)) notFound();
return {
messages: (await import(`./messages/${locale}.json`)).default
messages: (await import(`./messages/${locale}.json`)).default,
};
});

View File

@ -5,6 +5,30 @@
"p_2532-2599_Offlineservers": "Offline servers",
"p_3463-3530_Totalbandwidth": "Total bandwidth"
},
"ServerCard": {
"CPU": "CPU",
"Mem": "Mem",
"STG": "STG",
"Upload": "Upload",
"Download": "Download",
"Offline": "Offline"
},
"ServerCardPopover": {
"System": "System",
"CPU": "CPU",
"Mem": "Mem",
"STG": "STG",
"Swap": "Swap",
"Network": "Network",
"Load": "Load",
"Online": "Online",
"Offline": "Offline"
},
"ThemeSwitcher": {
"Light": "Light",
"Dark": "Dark",
"System": "System"
},
"Footer": {
"p_146-598_Findthecodeon": "Find the code on",
"a_303-585_GitHub": "GitHub",

View File

@ -5,6 +5,30 @@
"p_2532-2599_Offlineservers": "オフラインサーバー",
"p_3463-3530_Totalbandwidth": "総流量"
},
"ServerCard": {
"CPU": "CPU",
"Mem": "Mem",
"STG": "STG",
"Upload": "Upload",
"Download": "Download",
"Offline": "Offline"
},
"ServerCardPopover": {
"System": "システム",
"CPU": "CPU",
"Mem": "メモリ",
"STG": "ストレージ",
"Swap": "スワップ",
"Network": "ネットワーク",
"Load": "負荷",
"Online": "オンライン時間",
"Offline": "オフライン"
},
"ThemeSwitcher": {
"Light": "ライト",
"Dark": "ダーク",
"System": "システム"
},
"Footer": {
"p_146-598_Findthecodeon": "コードのオープンソース",
"a_303-585_GitHub": "GitHub",

View File

@ -5,6 +5,30 @@
"p_2532-2599_Offlineservers": "離線伺服器",
"p_3463-3530_Totalbandwidth": "總流量"
},
"ServerCard": {
"CPU": "CPU",
"Mem": "記憶體",
"STG": "儲存",
"Upload": "上傳",
"Download": "下載",
"Offline": "離線"
},
"ServerCardPopover": {
"System": "系統",
"CPU": "CPU",
"Mem": "記憶體",
"STG": "儲存",
"Swap": "虛擬記憶體",
"Network": "網路",
"Load": "負載",
"Online": "在線時間",
"Offline": "離線"
},
"ThemeSwitcher": {
"Light": "亮色",
"Dark": "暗色",
"System": "系統"
},
"Footer": {
"p_146-598_Findthecodeon": "程式碼開源",
"a_303-585_GitHub": "GitHub",

View File

@ -5,6 +5,30 @@
"p_2532-2599_Offlineservers": "离线服务器",
"p_3463-3530_Totalbandwidth": "总流量"
},
"ServerCard": {
"CPU": "CPU",
"Mem": "内存",
"STG": "存储",
"Upload": "上传",
"Download": "下载",
"Offline": "离线"
},
"ServerCardPopover": {
"System": "系统",
"CPU": "CPU",
"Mem": "内存",
"STG": "存储",
"Swap": "虚拟内存",
"Network": "网络",
"Load": "负载",
"Online": "在线时间",
"Offline": "离线"
},
"ThemeSwitcher": {
"Light": "亮色",
"Dark": "暗色",
"System": "系统"
},
"Footer": {
"p_146-598_Findthecodeon": "代码开源在",
"a_303-585_GitHub": "GitHub",

View File

@ -1,21 +1,21 @@
// @auto-i18n-check. Please do not delete the line.
import createMiddleware from 'next-intl/middleware';
import {locales} from "./i18n-metadata";
import createMiddleware from "next-intl/middleware";
import { locales } from "./i18n-metadata";
export default createMiddleware({
// A list of all locales that are supported
locales: locales,
// Used when no locale matches
defaultLocale: 'en',
defaultLocale: "en",
// 'always': This is the default, The home page will also be redirected to the default language, such as www.abc.com to www.abc.com/en
// 'as-needed': The default page is not redirected. For example, if you open www.abc.com, it is still www.abc.com
localePrefix: 'as-needed',
localePrefix: "as-needed",
});
export const config = {
// Match only internationalized pathnames
matcher: ['/', '/(en|zh|zh-t|ja)/:path*']
matcher: ["/", "/(en|zh|zh-t|ja)/:path*"],
};

View File

@ -1,4 +1,4 @@
import createNextIntlPlugin from 'next-intl/plugin';
import createNextIntlPlugin from "next-intl/plugin";
const withNextIntl = createNextIntlPlugin();
import withPWAInit from "@ducanh2912/next-pwa";
const withPWA = withPWAInit({
@ -8,13 +8,13 @@ const withPWA = withPWAInit({
reloadOnOnline: true,
disable: false,
workboxOptions: {
disableDevLogs: true
}
disableDevLogs: true,
},
});
/** @type {import('next').NextConfig} */
const nextConfig = {
// output: "standalone",
reactStrictMode: true
reactStrictMode: true,
};
export default withPWA(withNextIntl(nextConfig));