feat: add i18n support
@ -1,32 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { ServerApi } from "@/app/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,
|
||||
});
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
const sortedServers = data.result.sort((a, b) => {
|
||||
if (a.display_index && b.display_index) {
|
||||
return b.display_index - a.display_index;
|
||||
}
|
||||
if (a.display_index) return -1;
|
||||
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>
|
||||
);
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
export default function Footer() {
|
||||
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">
|
||||
Find the code on{" "}
|
||||
<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"
|
||||
>
|
||||
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">
|
||||
© 2020-{new Date().getFullYear()}{" "}
|
||||
<a href={"https://buycoffee.top"}>@Hamster1963</a>
|
||||
</section>
|
||||
</section>
|
||||
</footer>
|
||||
);
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import Header from "@/app/(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">
|
||||
<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>
|
||||
);
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
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(),
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<SWRConfig
|
||||
value={{
|
||||
fallback: fallback,
|
||||
}}
|
||||
>
|
||||
<div className="mx-auto grid w-full max-w-5xl gap-4 md:gap-6">
|
||||
<ServerOverview />
|
||||
<ServerList />
|
||||
</div>
|
||||
</SWRConfig>
|
||||
);
|
||||
}
|
27
app/[locale]/(main)/ClientComponents/ServerListClient.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
"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
|
||||
});
|
||||
if (!data) return null;
|
||||
const sortedServers = data.result.sort((a, b) => {
|
||||
if (a.display_index && b.display_index) {
|
||||
return b.display_index - a.display_index;
|
||||
}
|
||||
if (a.display_index) return -1;
|
||||
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>;
|
||||
}
|
14
app/[locale]/(main)/footer.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { useTranslations } from 'next-intl';
|
||||
export default function Footer() {
|
||||
const t = useTranslations("Footer");
|
||||
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>
|
||||
<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>;
|
||||
}
|
@ -1,86 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Separator } from "../../../components/ui/separator";
|
||||
import { DateTime } from "luxon";
|
||||
import { ModeToggle } from "@/components/ThemeSwitcher";
|
||||
|
||||
import { ModeToggle } from "../../../components/ThemeSwitcher";
|
||||
function Header() {
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-5xl">
|
||||
const t = useTranslations("Header");
|
||||
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">
|
||||
Simple and beautiful dashboard
|
||||
</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
|
||||
const useInterval = (callback: Function, delay?: number | null) => {
|
||||
const savedCallback = useRef<Function>(() => {});
|
||||
|
||||
useEffect(() => {
|
||||
savedCallback.current = callback;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (delay !== null) {
|
||||
const interval = setInterval(() => savedCallback.current(), delay || 0);
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [delay]);
|
||||
};
|
||||
|
||||
function Overview() {
|
||||
const t = useTranslations("Overview");
|
||||
const [mouted, setMounted] = useState(false);
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
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">👋 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">where the time is</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;
|
18
app/[locale]/(main)/layout.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
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">
|
||||
<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>;
|
||||
}
|
20
app/[locale]/(main)/page.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
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()
|
||||
};
|
||||
export default function Home() {
|
||||
return <SWRConfig value={{
|
||||
fallback: fallback
|
||||
}}>
|
||||
<div className="mx-auto grid w-full max-w-5xl gap-4 md:gap-6">
|
||||
<ServerOverview />
|
||||
<ServerList />
|
||||
</div>
|
||||
</SWRConfig>;
|
||||
}
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 263 B After Width: | Height: | Size: 263 B |
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 324 B |
22
app/[locale]/layout.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
// @auto-i18n-check. Please do not delete the line.
|
||||
|
||||
import React from "react";
|
||||
import {NextIntlClientProvider, useMessages} from 'next-intl';
|
||||
|
||||
export default function LocaleLayout({
|
||||
children,
|
||||
params: {locale}
|
||||
}: {
|
||||
children: React.ReactNode; params: { locale: string };
|
||||
}) {
|
||||
const messages = useMessages();
|
||||
return (
|
||||
<html lang={locale}>
|
||||
<body>
|
||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
16
app/[locale]/not-found.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
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">
|
||||
<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" />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</main>;
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function NotFoundPage() {
|
||||
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"
|
||||
/>
|
||||
<div className="text-container absolute right-4 p-4 md:right-20">
|
||||
<h1 className="text-2xl font-bold opacity-80 md:text-5xl">
|
||||
404 Not Found
|
||||
</h1>
|
||||
<p className="text-lg opacity-60 md:text-base">TARDIS ERROR!</p>
|
||||
<Link href={"/"} className="text-2xl opacity-80 md:text-3xl">
|
||||
Doctor?
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
15
auto-i18n-config.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"defaultLang": "en",
|
||||
"translatorServerName": "azure",
|
||||
"needLangs": [
|
||||
"en",
|
||||
"zh",
|
||||
"zh-t",
|
||||
"ja"
|
||||
],
|
||||
"brandWords": [],
|
||||
"unMoveToLocaleDirFiles": [],
|
||||
"enableStaticRendering": false,
|
||||
"enableSubPageRedirectToLocale": false,
|
||||
"disableDefaultLangRedirect": true
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { NezhaAPISafe } from "@/app/types/nezha-api";
|
||||
import { NezhaAPISafe } from "../app/[locale]/types/nezha-api";
|
||||
import ServerUsageBar from "@/components/ServerUsageBar";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { NezhaAPISafe } from "@/app/types/nezha-api";
|
||||
import { NezhaAPISafe } from "../app/[locale]/types/nezha-api";
|
||||
import { cn, formatBytes } from "@/lib/utils";
|
||||
|
||||
export function ServerCardPopoverCard({
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import ServerListClient from "@/app/(main)/ClientComponents/ServerListClient";
|
||||
import ServerListClient from "../app/[locale]/(main)/ClientComponents/ServerListClient";
|
||||
|
||||
export default async function ServerList() {
|
||||
return <ServerListClient />;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import ServerOverviewClient from "@/app/(main)/ClientComponents/ServerOverviewClient";
|
||||
import ServerOverviewClient from "../app/[locale]/(main)/ClientComponents/ServerOverviewClient";
|
||||
|
||||
export default async function ServerOverview() {
|
||||
return <ServerOverviewClient />;
|
||||
|
24
i18n-metadata.ts
Normal file
@ -0,0 +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'},
|
||||
];
|
||||
|
||||
export const locales = localeItems.map((item) => item.code);
|
||||
export const defaultLocale = 'en';
|
14
i18n.ts
Normal file
@ -0,0 +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";
|
||||
|
||||
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
|
||||
};
|
||||
});
|
@ -1,7 +1,7 @@
|
||||
"use server";
|
||||
|
||||
import { NezhaAPI, ServerApi } from "@/app/types/nezha-api";
|
||||
import { MakeOptional } from "@/app/types/utils";
|
||||
import { NezhaAPI, ServerApi } from "../app/[locale]/types/nezha-api";
|
||||
import { MakeOptional } from "../app/[locale]/types/utils";
|
||||
import { error } from "console";
|
||||
import { unstable_noStore as noStore } from "next/cache";
|
||||
import getEnv from "./env-entry";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { NezhaAPISafe } from "@/app/types/nezha-api";
|
||||
import { NezhaAPISafe } from "../app/[locale]/types/nezha-api";
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
|
26
messages/en.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"ServerOverviewClient": {
|
||||
"p_816-881_Totalservers": "Total servers",
|
||||
"p_1610-1676_Onlineservers": "Online servers",
|
||||
"p_2532-2599_Offlineservers": "Offline servers",
|
||||
"p_3463-3530_Totalbandwidth": "Total bandwidth"
|
||||
},
|
||||
"Footer": {
|
||||
"p_146-598_Findthecodeon": "Find the code on",
|
||||
"a_303-585_GitHub": "GitHub",
|
||||
"section_607-869_2020": "© 2020-",
|
||||
"a_800-850_Hamster1963": "@Hamster1963"
|
||||
},
|
||||
"Header": {
|
||||
"p_1079-1199_Simpleandbeautifuldashbo": "Simple and beautiful dashboard"
|
||||
},
|
||||
"Overview": {
|
||||
"p_2277-2331_Overview": "👋 Overview",
|
||||
"p_2390-2457_wherethetimeis": "where the time is"
|
||||
},
|
||||
"NotFoundPage": {
|
||||
"h1_490-590_404NotFound": "404 Not Found",
|
||||
"p_601-665_TARDISERROR": "TARDIS ERROR!",
|
||||
"Link_676-775_Doctor": "Doctor?"
|
||||
}
|
||||
}
|
26
messages/ja.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"ServerOverviewClient": {
|
||||
"p_816-881_Totalservers": "サーバーの総数",
|
||||
"p_1610-1676_Onlineservers": "オンラインサーバー",
|
||||
"p_2532-2599_Offlineservers": "オフラインサーバー",
|
||||
"p_3463-3530_Totalbandwidth": "合計帯域幅"
|
||||
},
|
||||
"Footer": {
|
||||
"p_146-598_Findthecodeon": "でコードを見つけます",
|
||||
"a_303-585_GitHub": "GitHubの",
|
||||
"section_607-869_2020": "© 2020年〜",
|
||||
"a_800-850_Hamster1963": "@Hamster1963"
|
||||
},
|
||||
"Header": {
|
||||
"p_1079-1199_Simpleandbeautifuldashbo": "シンプルで美しいダッシュボード"
|
||||
},
|
||||
"Overview": {
|
||||
"p_2277-2331_Overview": "👋 概要",
|
||||
"p_2390-2457_wherethetimeis": "時間はどこにあるのか"
|
||||
},
|
||||
"NotFoundPage": {
|
||||
"h1_490-590_404NotFound": "404 見つかりませんでした",
|
||||
"p_601-665_TARDISERROR": "ターディスエラー!",
|
||||
"Link_676-775_Doctor": "医者。"
|
||||
}
|
||||
}
|
26
messages/zh-t.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"ServerOverviewClient": {
|
||||
"p_816-881_Totalservers": "伺服器總數",
|
||||
"p_1610-1676_Onlineservers": "在線伺服器",
|
||||
"p_2532-2599_Offlineservers": "離線伺服器",
|
||||
"p_3463-3530_Totalbandwidth": "總頻寬"
|
||||
},
|
||||
"Footer": {
|
||||
"p_146-598_Findthecodeon": "查找代碼",
|
||||
"a_303-585_GitHub": "GitHub的",
|
||||
"section_607-869_2020": "© 2020 年-",
|
||||
"a_800-850_Hamster1963": "@Hamster1963"
|
||||
},
|
||||
"Header": {
|
||||
"p_1079-1199_Simpleandbeautifuldashbo": "簡單美觀的儀錶板"
|
||||
},
|
||||
"Overview": {
|
||||
"p_2277-2331_Overview": "👋 概述",
|
||||
"p_2390-2457_wherethetimeis": "時間在哪裡"
|
||||
},
|
||||
"NotFoundPage": {
|
||||
"h1_490-590_404NotFound": "404 未找到",
|
||||
"p_601-665_TARDISERROR": "TARDIS 錯誤!",
|
||||
"Link_676-775_Doctor": "醫生?"
|
||||
}
|
||||
}
|
26
messages/zh.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"ServerOverviewClient": {
|
||||
"p_816-881_Totalservers": "服务器总数",
|
||||
"p_1610-1676_Onlineservers": "在线服务器",
|
||||
"p_2532-2599_Offlineservers": "离线服务器",
|
||||
"p_3463-3530_Totalbandwidth": "总带宽"
|
||||
},
|
||||
"Footer": {
|
||||
"p_146-598_Findthecodeon": "查找代码",
|
||||
"a_303-585_GitHub": "GitHub的",
|
||||
"section_607-869_2020": "© 2020 年-",
|
||||
"a_800-850_Hamster1963": "@Hamster1963"
|
||||
},
|
||||
"Header": {
|
||||
"p_1079-1199_Simpleandbeautifuldashbo": "简单美观的仪表板"
|
||||
},
|
||||
"Overview": {
|
||||
"p_2277-2331_Overview": "👋 概述",
|
||||
"p_2390-2457_wherethetimeis": "时间在哪里"
|
||||
},
|
||||
"NotFoundPage": {
|
||||
"h1_490-590_404NotFound": "404 未找到",
|
||||
"p_601-665_TARDISERROR": "TARDIS 错误!",
|
||||
"Link_676-775_Doctor": "医生?"
|
||||
}
|
||||
}
|
21
middleware.ts
Normal file
@ -0,0 +1,21 @@
|
||||
// @auto-i18n-check. Please do not delete the line.
|
||||
|
||||
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',
|
||||
|
||||
// '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',
|
||||
});
|
||||
|
||||
export const config = {
|
||||
// Match only internationalized pathnames
|
||||
matcher: ['/', '/(en|zh|zh-t|ja)/:path*']
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
import createNextIntlPlugin from 'next-intl/plugin';
|
||||
const withNextIntl = createNextIntlPlugin();
|
||||
import withPWAInit from "@ducanh2912/next-pwa";
|
||||
|
||||
const withPWA = withPWAInit({
|
||||
dest: "public",
|
||||
cacheOnFrontEndNav: true,
|
||||
@ -7,14 +8,13 @@ const withPWA = withPWAInit({
|
||||
reloadOnOnline: true,
|
||||
disable: false,
|
||||
workboxOptions: {
|
||||
disableDevLogs: true,
|
||||
},
|
||||
disableDevLogs: true
|
||||
}
|
||||
});
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "standalone",
|
||||
reactStrictMode: true,
|
||||
// output: "standalone",
|
||||
reactStrictMode: true
|
||||
};
|
||||
|
||||
export default withPWA(nextConfig);
|
||||
export default withPWA(withNextIntl(nextConfig));
|
@ -6,7 +6,9 @@
|
||||
"dev": "next dev -p 3020",
|
||||
"start": "node .next/standalone/server.js",
|
||||
"lint": "next lint",
|
||||
"build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/"
|
||||
"build": "next build && cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/",
|
||||
"build-dev": "next build",
|
||||
"start-dev": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ducanh2912/next-pwa": "^10.2.9",
|
||||
@ -28,6 +30,7 @@
|
||||
"lucide-react": "^0.414.0",
|
||||
"luxon": "^3.5.0",
|
||||
"next": "^14.2.13",
|
||||
"next-intl": "^3.20.0",
|
||||
"next-runtime-env": "^3.2.2",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18.3.1",
|
||||
|