Improved user interface translations and clarity in Simplified

This commit is contained in:
oiov
2025-06-07 22:04:46 +08:00
parent 7d629e9cd4
commit 657cd32aaa
20 changed files with 202 additions and 70 deletions
@@ -1,6 +1,7 @@
"use client";
import { TrendingUp } from "lucide-react";
import { useTranslations } from "next-intl";
import {
Label,
PolarGrid,
@@ -29,6 +30,7 @@ export function RadialShapeChart({
total: number;
totalUser: number;
}) {
const t = useTranslations("Components");
const chartData = [
{ browser: "safari", actived: total, fill: "var(--color-safari)" },
];
@@ -94,7 +96,7 @@ export function RadialShapeChart({
<TrendingUp className="size-4" />
</div>
<div className="leading-none text-muted-foreground">
Cumulative proportion of activated <strong>Api Key</strong> users
{t("Activated Api Key users")}
</div>
</CardFooter>
</Card>
@@ -9,7 +9,7 @@ import { toast } from "sonner";
import useSWR, { useSWRConfig } from "swr";
import { DomainFormData } from "@/lib/dto/domains";
import { fetcher, timeAgo } from "@/lib/utils";
import { fetcher } from "@/lib/utils";
import { useMediaQuery } from "@/hooks/use-media-query";
import { Button } from "@/components/ui/button";
import {
@@ -35,6 +35,7 @@ import { FormType } from "@/components/forms/record-form";
import { EmptyPlaceholder } from "@/components/shared/empty-placeholder";
import { Icons } from "@/components/shared/icons";
import { PaginationWrapper } from "@/components/shared/pagination";
import { TimeAgoIntl } from "@/components/shared/time-ago";
export interface DomainListProps {
user: Pick<User, "id" | "name" | "email" | "apiKey" | "role" | "team">;
@@ -291,7 +292,7 @@ export default function DomainList({ user, action }: DomainListProps) {
/>
</TableCell>
<TableCell className="col-span-1 flex items-center truncate">
{timeAgo(domain.updatedAt as Date)}
<TimeAgoIntl date={domain.updatedAt as Date} />
</TableCell>
<TableCell className="col-span-1 flex items-center gap-1">
<Button
@@ -1,6 +1,7 @@
"use client";
import { ScrapeMeta } from "@prisma/client";
import { useTranslations } from "next-intl";
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
import { useElementSize } from "@/hooks/use-element-size";
@@ -58,6 +59,8 @@ export function LineChartMultiple({
const { ref: wrapperRef, width: wrapperWidth } = useElementSize();
const processedData = processChartData(chartData, type1, type2);
const t = useTranslations("Components");
const chartConfig = {
source1: {
label: type1,
@@ -69,13 +72,14 @@ export function LineChartMultiple({
},
} satisfies ChartConfig;
const message = type2
? t("total-requests-two-types", { type1, type2 })
: t("total-requests-one-type", { type1 });
return (
<Card>
<CardHeader>
<CardDescription>
Total requests of {type1}
{type2 && ` and ${type2}`}.
</CardDescription>
<CardDescription>{message}.</CardDescription>
</CardHeader>
<CardContent ref={wrapperRef}>
<ChartContainer config={chartConfig}>
+2 -6
View File
@@ -1,5 +1,6 @@
import { Suspense } from "react";
import { redirect } from "next/navigation";
import { useTranslations } from "next-intl";
import { getUserRecordCount } from "@/lib/dto/cloudflare-dns-record";
import {
@@ -134,7 +135,6 @@ async function RequestStatsSection() {
return hasStats ? (
<>
<h2 className="my-1 text-xl font-semibold">Request Statistics</h2>
<DailyPVUVChart
data={screenshot_stats
.concat(meta_stats)
@@ -200,7 +200,6 @@ async function MarkdownTextChartSection() {
async function LogsSection({ userId }: { userId: string }) {
return (
<>
<h2 className="my-1 text-xl font-semibold">Request Logs</h2>
<LogsTable userId={userId} target={"/api/v1/scraping/admin/logs"} />
</>
);
@@ -213,10 +212,7 @@ export default async function AdminPage() {
return (
<>
<DashboardHeader
heading="Admin Panel"
text="Access only for users with ADMIN role"
/>
<DashboardHeader heading="Admin Panel" text="" />
<div className="flex flex-col gap-5">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3 xl:grid-cols-3">
<ErrorBoundary
+3 -2
View File
@@ -5,7 +5,7 @@ import { User } from "@prisma/client";
import { PenLine, RefreshCwIcon } from "lucide-react";
import useSWR, { useSWRConfig } from "swr";
import { fetcher, timeAgo } from "@/lib/utils";
import { fetcher } from "@/lib/utils";
import { useMediaQuery } from "@/hooks/use-media-query";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -37,6 +37,7 @@ import { UserForm } from "@/components/forms/user-form";
import { EmptyPlaceholder } from "@/components/shared/empty-placeholder";
import { Icons } from "@/components/shared/icons";
import { PaginationWrapper } from "@/components/shared/pagination";
import { TimeAgoIntl } from "@/components/shared/time-ago";
import CountUpFn from "../../../../components/dashboard/count-up";
@@ -250,7 +251,7 @@ export default function UsersList({ user }: UrlListProps) {
<Switch defaultChecked={user.active === 1} />
</TableCell>
<TableCell className="col-span-1 hidden justify-center sm:flex">
{timeAgo(user.createdAt || "")}
<TimeAgoIntl date={user.updatedAt as Date} />
</TableCell>
<TableCell className="col-span-1 flex justify-center">
<Button
@@ -10,7 +10,7 @@ import useSWR, { useSWRConfig } from "swr";
import { UserRecordFormData } from "@/lib/dto/cloudflare-dns-record";
import { TTL_ENUMS } from "@/lib/enums";
import { fetcher, timeAgo } from "@/lib/utils";
import { fetcher } from "@/lib/utils";
import { useMediaQuery } from "@/hooks/use-media-query";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -43,6 +43,7 @@ import { EmptyPlaceholder } from "@/components/shared/empty-placeholder";
import { Icons } from "@/components/shared/icons";
import { LinkInfoPreviewer } from "@/components/shared/link-previewer";
import { PaginationWrapper } from "@/components/shared/pagination";
import { TimeAgoIntl } from "@/components/shared/time-ago";
export interface RecordListProps {
user: Pick<User, "id" | "name" | "apiKey" | "email" | "role">;
@@ -341,7 +342,9 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
</TooltipProvider>
</TableCell>
<TableCell className="col-span-1 hidden justify-center sm:flex">
{timeAgo(record.modified_on as unknown as Date)}
<TimeAgoIntl
date={record.modified_on as unknown as Date}
/>
</TableCell>
<TableCell className="col-span-1 flex justify-center">
{record.active !== 2 ? (
+1 -5
View File
@@ -20,10 +20,7 @@ export default async function DashboardScrapeCharts({ id }: { id: string }) {
return (
<>
{all_user_logs && all_user_logs.length > 0 && (
<>
<h2 className="my-1 text-xl font-semibold">Request Statistics</h2>
<DailyPVUVChart data={all_user_logs} />
</>
<DailyPVUVChart data={all_user_logs} />
)}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
{(screenshot_stats.length > 0 || meta_stats.length > 0) && (
@@ -42,7 +39,6 @@ export default async function DashboardScrapeCharts({ id }: { id: string }) {
)}
</div>
<h2 className="my-1 text-xl font-semibold">Request Logs</h2>
<LogsTable userId={id} target={"/api/v1/scraping/logs"} />
</>
);
@@ -3,6 +3,7 @@
import * as React from "react";
import Link from "next/link";
import { ScrapeMeta } from "@prisma/client";
import { useTranslations } from "next-intl";
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
import { isLink, nFormatter, removeUrlSuffix, timeAgo } from "@/lib/utils";
@@ -18,7 +19,6 @@ import {
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import CountUp from "@/components/dashboard/count-up";
const chartConfig = {
request: {
@@ -105,14 +105,19 @@ export function DailyPVUVChart({ data }: { data: ScrapeMeta[] }) {
const latestDate = timeAgo(latestEntry.updatedAt);
const latestFrom = latestEntry.type;
const t = useTranslations("Components");
return (
<Card>
<CardHeader className="flex flex-col items-stretch space-y-0 border-b p-0 sm:flex-row">
<div className="flex flex-1 flex-col justify-center gap-1 px-5 py-4">
<CardTitle>Total Requests of APIs in Last 30 Days</CardTitle>
<CardTitle>{t("Total Requests of APIs in Last 30 Days")}</CardTitle>
<CardDescription>
Last request from <strong>{latestFrom}</strong> api about{" "}
{latestDate}.
{t("Last request from {latestFrom} api about {latestDate}", {
latestFrom,
latestDate,
})}
.
</CardDescription>
</div>
<div className="flex">
@@ -125,8 +130,8 @@ export function DailyPVUVChart({ data }: { data: ScrapeMeta[] }) {
className="relative z-30 flex flex-1 flex-col items-center justify-center gap-1 border-t px-6 py-2 text-left even:border-l data-[active=true]:bg-muted/50 sm:border-l sm:border-t-0 sm:px-8 sm:py-3"
onClick={() => setActiveChart(chart)}
>
<span className="text-xs text-muted-foreground">
{chartConfig[chart].label}
<span className="text-nowrap text-xs text-muted-foreground">
{t(chartConfig[chart].label)}
</span>
<span className="text-lg font-bold leading-none">
{nFormatter(dataTotal[key])}
+11 -4
View File
@@ -2,6 +2,7 @@
import { useState } from "react";
import { RefreshCwIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import useSWR, { useSWRConfig } from "swr";
import { nFormatter } from "@/lib/utils";
@@ -49,6 +50,8 @@ const LogsTable = ({ userId, target }) => {
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
const t = useTranslations("Components");
const [filters, setFilters] = useState({
type: "",
ip: "",
@@ -141,13 +144,17 @@ const LogsTable = ({ userId, target }) => {
<TableHeader className="bg-muted">
<TableRow className="grid grid-cols-5 items-center sm:grid-cols-6">
<TableHead className="hidden items-center justify-start px-2 sm:flex">
Date
{t("Date")}
</TableHead>
<TableHead className="flex items-center px-2">
{t("Type")}
</TableHead>
<TableHead className="flex items-center px-2">Type</TableHead>
<TableHead className="col-span-3 flex items-center px-2">
Link
{t("Link")}
</TableHead>
<TableHead className="flex items-center px-2">
{t("User")}
</TableHead>
<TableHead className="flex items-center px-2">User</TableHead>
</TableRow>
</TableHeader>
<TableBody>
+24 -17
View File
@@ -7,6 +7,7 @@ import { UrlMeta, User } from "@prisma/client";
import { VisSingleContainer, VisTooltip, VisTopoJSONMap } from "@unovis/react";
import { TopoJSONMap } from "@unovis/ts";
import { WorldMapTopoJSON } from "@unovis/ts/maps";
import { useTranslations } from "next-intl";
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
import { TeamPlanQuota } from "@/config/team";
@@ -175,6 +176,8 @@ export function DailyPVUVChart({
const [activeChart, setActiveChart] =
React.useState<keyof typeof chartConfig>("pv");
const t = useTranslations("Components");
const processedData = processUrlMeta(data).map((entry) => ({
date: entry.date,
pv: entry.clicks,
@@ -242,9 +245,13 @@ export function DailyPVUVChart({
<Card>
<CardHeader className="flex flex-col items-stretch space-y-0 border-b p-0 sm:flex-row">
<div className="flex flex-1 flex-col justify-center gap-1 px-6 py-2 sm:py-3">
<CardTitle>Link Analytics</CardTitle>
<CardTitle>{t("Link Analytics")}</CardTitle>
<CardDescription>
Last visitor from {latestFrom} about {latestDate}.
{t("Last visitor from {latestFrom} about {latestDate}", {
latestFrom,
latestDate,
})}
.
</CardDescription>
</div>
<div className="flex items-center">
@@ -291,8 +298,8 @@ export function DailyPVUVChart({
className="relative z-30 flex flex-1 flex-col items-center justify-center gap-1 border-t px-6 py-2 text-left even:border-l data-[active=true]:bg-muted/50 sm:border-l sm:border-t-0 sm:px-8 sm:py-3"
onClick={() => setActiveChart(chart)}
>
<span className="text-sm font-semibold text-muted-foreground">
{chartConfig[chart].label}
<span className="text-nowrap text-sm font-semibold text-muted-foreground">
{t(chartConfig[chart].label)}
</span>
<span className="text-lg font-bold leading-none">
{dataTotal[key as keyof typeof dataTotal].toLocaleString()}
@@ -403,8 +410,8 @@ export function DailyPVUVChart({
{/* Referrers、isBotStats */}
<Tabs defaultValue="referrer">
<TabsList>
<TabsTrigger value="referrer">Referrers</TabsTrigger>
<TabsTrigger value="isBot">Traffic Type</TabsTrigger>
<TabsTrigger value="referrer">{t("Referrers")}</TabsTrigger>
<TabsTrigger value="isBot">{t("Traffic Type")}</TabsTrigger>
</TabsList>
<TabsContent className="h-[calc(100%-40px)]" value="referrer">
{refererStats.length > 0 && (
@@ -420,8 +427,8 @@ export function DailyPVUVChart({
{/* 国家、城市 */}
<Tabs defaultValue="country">
<TabsList>
<TabsTrigger value="country">Country</TabsTrigger>
<TabsTrigger value="city">City</TabsTrigger>
<TabsTrigger value="country">{t("Country")}</TabsTrigger>
<TabsTrigger value="city">{t("City")}</TabsTrigger>
</TabsList>
<TabsContent className="h-[calc(100%-40px)]" value="country">
{countryStats.length > 0 && (
@@ -437,8 +444,8 @@ export function DailyPVUVChart({
{/* browserStats、engineStats */}
<Tabs defaultValue="browser">
<TabsList>
<TabsTrigger value="browser">Browser</TabsTrigger>
<TabsTrigger value="engine">Browser Engine</TabsTrigger>
<TabsTrigger value="browser">{t("Browser")}</TabsTrigger>
<TabsTrigger value="engine">{t("Engine")}</TabsTrigger>
</TabsList>
<TabsContent className="h-[calc(100%-40px)]" value="browser">
{browserStats.length > 0 && (
@@ -455,8 +462,8 @@ export function DailyPVUVChart({
{/* Languages、regionStats */}
<Tabs className="h-full" defaultValue="language">
<TabsList>
<TabsTrigger value="language">Language</TabsTrigger>
<TabsTrigger value="region">Region</TabsTrigger>
<TabsTrigger value="language">{t("Language")}</TabsTrigger>
<TabsTrigger value="region">{t("Region")}</TabsTrigger>
</TabsList>
<TabsContent className="h-[calc(100%-40px)]" value="language">
{languageStats.length > 0 && (
@@ -472,8 +479,8 @@ export function DailyPVUVChart({
{/* deviceStats、osStats、cpuStats */}
<Tabs defaultValue="device">
<TabsList>
<TabsTrigger value="device">Device</TabsTrigger>
<TabsTrigger value="os">OS</TabsTrigger>
<TabsTrigger value="device">{t("Device")}</TabsTrigger>
<TabsTrigger value="os">{t("OS")}</TabsTrigger>
<TabsTrigger value="cpu">CPU</TabsTrigger>
</TabsList>
<TabsContent className="h-[calc(100%-40px)]" value="device">
@@ -497,12 +504,12 @@ export function DailyPVUVChart({
export function StatsList({ data, title }: { data: Stat[]; title: string }) {
const [showAll, setShowAll] = useState(false);
const displayedData = showAll ? data.slice(0, 50) : data.slice(0, 8);
const t = useTranslations("Components");
return (
<div className="h-full rounded-lg border">
<div className="flex items-center justify-between border-b px-5 py-2 text-xs font-medium text-muted-foreground">
<span></span>
<span className=""></span>
<span>{t("Name")}</span>
<span className="">{t("Visitors")}</span>
</div>
<div
className={`scrollbar-hidden overflow-hidden overflow-y-auto px-4 pb-4 pt-2 transition-all duration-500 ease-in-out`}
+5 -5
View File
@@ -17,7 +17,6 @@ import {
fetcher,
nFormatter,
removeUrlSuffix,
timeAgo,
} from "@/lib/utils";
import { useMediaQuery } from "@/hooks/use-media-query";
import { Button } from "@/components/ui/button";
@@ -58,6 +57,7 @@ import { Icons } from "@/components/shared/icons";
import { LinkInfoPreviewer } from "@/components/shared/link-previewer";
import { PaginationWrapper } from "@/components/shared/pagination";
import QRCodeEditor from "@/components/shared/qr";
import { TimeAgoIntl } from "@/components/shared/time-ago";
import Globe from "./globe";
import LiveLog from "./live-logs";
@@ -396,7 +396,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
</div>
</TableCell>
<TableCell className="col-span-1 hidden truncate sm:flex">
{timeAgo(short.updatedAt as Date)}
<TimeAgoIntl date={short.updatedAt as Date} />
</TableCell>
<TableCell className="col-span-1 flex items-center gap-1 sm:col-span-2">
<Button
@@ -563,7 +563,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
}}
>
<Icons.lineChart className="size-4" />
Analytics
{t("Analytics")}
</Button>
</DropdownMenuItem>
<DropdownMenuSeparator />
@@ -580,7 +580,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
}}
>
<PenLine className="size-4" />
Edit URL
{t("Edit URL")}
</Button>
</DropdownMenuItem>
</DropdownMenuContent>
@@ -614,7 +614,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
></Separator>
</>
)}
{timeAgo(short.updatedAt as Date)}
<TimeAgoIntl date={short.updatedAt as Date} />
<Switch
className="scale-[0.6]"
defaultChecked={short.active === 1}
+6 -3
View File
@@ -2,6 +2,7 @@
import * as React from "react";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
import useSWR from "swr";
@@ -68,6 +69,8 @@ export function InteractiveBarChart() {
const [activeChart, setActiveChart] =
React.useState<keyof typeof chartConfig>("users");
const t = useTranslations("Components");
const { data, isLoading } = useSWR<{
list: [
{
@@ -110,8 +113,8 @@ export function InteractiveBarChart() {
<CardHeader className="flex flex-col items-stretch space-y-0 border-b p-0 sm:flex-row">
<div className="flex w-full flex-1 justify-between gap-2 px-6 py-5 sm:flex-col sm:py-6">
<div className="flex flex-col justify-center gap-1">
<CardTitle>Data Increase</CardTitle>
<CardDescription>Showing data increase in:</CardDescription>
<CardTitle>{t("Data Increase")}</CardTitle>
<CardDescription>{t("Showing data increase in")}:</CardDescription>
</div>
<Select
onValueChange={(value: string) => {
@@ -150,7 +153,7 @@ export function InteractiveBarChart() {
onClick={() => setActiveChart(chart)}
>
<span className="text-xs text-muted-foreground">
{chartConfig[chart].label}
{t(chartConfig[chart].label)}
</span>
<span className="text-base font-bold leading-none sm:text-lg">
{nFormatter(data.total[key])}
+5 -2
View File
@@ -7,11 +7,12 @@ import { useTranslations } from "next-intl";
import { toast } from "sonner";
import useSWR from "swr";
import { cn, fetcher, htmlToText, timeAgo } from "@/lib/utils";
import { cn, fetcher, htmlToText } from "@/lib/utils";
import BlurImage from "../shared/blur-image";
import { Icons } from "../shared/icons";
import { PaginationWrapper } from "../shared/pagination";
import { TimeAgoIntl } from "../shared/time-ago";
// import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
import { Checkbox } from "../ui/checkbox";
@@ -278,7 +279,9 @@ export default function EmailList({
{email.fromName || email.subject || "Untitled"}
</span>
<span className="ml-auto text-xs text-neutral-600 dark:text-neutral-400">
{timeAgo((email.date as any) || email.createdAt)}
<TimeAgoIntl
date={(email.date as any) || email.createdAt}
/>
</span>
{email.readAt && (
<Icons.checkCheck className="ml-2 size-3 text-green-600" />
+3 -2
View File
@@ -17,13 +17,14 @@ import useSWR from "swr";
import { UserEmailList } from "@/lib/dto/email";
import { reservedAddressSuffix } from "@/lib/enums";
import { cn, fetcher, nFormatter, timeAgo } from "@/lib/utils";
import { cn, fetcher, nFormatter } from "@/lib/utils";
import { useMediaQuery } from "@/hooks/use-media-query";
import { CopyButton } from "../shared/copy-button";
import { EmptyPlaceholder } from "../shared/empty-placeholder";
import { Icons } from "../shared/icons";
import { PaginationWrapper } from "../shared/pagination";
import { TimeAgoIntl } from "../shared/time-ago";
import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
@@ -539,7 +540,7 @@ export default function EmailSidebar({
{isAdminModel
? `Created by ${email.user || email.email.slice(0, 5)} at`
: ""}{" "}
{timeAgo(email.createdAt)}
<TimeAgoIntl date={email.createdAt} />
</span>
</div>
)}
+10
View File
@@ -0,0 +1,10 @@
import { useLocale } from "next-intl";
import TimeAgo from "timeago-react";
export function TimeAgoIntl({ date }: { date: Date }) {
const locale = useLocale();
return (
<TimeAgo datetime={date} locale={locale === "zh" ? "zh_CN" : locale} />
);
}
+37 -2
View File
@@ -137,7 +137,9 @@
"Associate with 'Email Service' status": "Associate with 'Email Service' status",
"API Key": "API Key",
"send email service": "send email service",
"How to get resend api key?": "How to get resend api key?"
"How to get resend api key?": "How to get resend api key?",
"Analytics": "Analytics",
"Edit URL": "Edit URL"
},
"Components": {
"Dashboard": "Dashboard",
@@ -198,7 +200,40 @@
"Convert website content to text": "Convert website content to text",
"Emails": "Emails",
"Inbox": "Inbox",
"Users": "Users"
"Users": "Users",
"Total Requests of APIs in Last 30 Days": "Total Requests of APIs in Last 30 Days",
"Last request from {latestFrom} api about {latestDate}": "Last request from {latestFrom} api about {latestDate}",
"Requests": "Requests",
"IP": "IP",
"Date": "Date",
"Type": "Type",
"Link": "Link",
"User": "User",
"Data Increase": "Data Increase",
"Showing data increase in": "Showing data increase in",
"Records": "Records",
"URLs": "URLs",
"Sends": "Sends",
"Link Analytics": "Link Analytics",
"Last visitor from {latestFrom} about {latestDate}": "Last visitor from {latestFrom} about {latestDate}",
"Views": "Views",
"Visits": "Visits",
"Referrers": "Referrers",
"Traffic Type": "Traffic Type",
"Country": "Country",
"City": "City",
"Browser": "Browser",
"Engine": "Engine",
"Language": "Language",
"Region": "Region",
"Device": "Device",
"OS": "OS",
"CPU": "CPU",
"Visitors": "Visitors",
"Name": "Name",
"Activated Api Key users": "Activated Api Key users",
"total-requests-one-type": "Total requests of {type1}.",
"total-requests-two-types": "Total requests of {type1} and {type2}."
},
"Landing": {
"settings": "Settings",
+37 -2
View File
@@ -137,7 +137,9 @@
"Associate with 'Email Service' status": "与 '邮件服务' 启用状态关联",
"API Key": "API 密钥",
"send email service": "用于发送邮件服务",
"How to get resend api key?": "如何获取 Resend API 密钥?"
"How to get resend api key?": "如何获取 Resend API 密钥?",
"Analytics": "访客分析",
"Edit URL": "编辑短链"
},
"Components": {
"Dashboard": "用户面板",
@@ -198,7 +200,40 @@
"Convert website content to text": "使用 API 将网页内容转换为文本",
"Emails": "邮箱",
"Inbox": "收件箱",
"Users": "用户"
"Users": "用户",
"Total Requests of APIs in Last 30 Days": "过去 30 天的 API 请求总量",
"Last request from {latestFrom} api about {latestDate}": "最近的请求来自 {latestFrom} 的 API 于 {latestDate}",
"Requests": "请求量",
"IP": "IP 数",
"Date": "日期",
"Type": "类型",
"Link": "链接",
"User": "用户",
"Data Increase": "数据增长",
"Showing data increase in": "Showing data increase in",
"Records": "子域名",
"URLs": "短链接",
"Sends": "发件箱",
"Link Analytics": "访客分析",
"Last visitor from {latestFrom} about {latestDate}": "最近的访客来自 {latestFrom} 于 {latestDate}",
"Views": "浏览量",
"Visits": "访问次数",
"Referrers": "来源域名",
"Traffic Type": "流量类型",
"Country": "国家",
"City": "城市",
"Browser": "浏览器",
"Engine": "引擎",
"Language": "语言",
"Region": "地区",
"Device": "设备",
"OS": "操作系统",
"CPU": "CPU",
"Visitors": "访客",
"Name": "名称",
"Activated Api Key users": "已启用 API Key 的用户",
"total-requests-one-type": "Open API 的 {type1} 的总请求数。",
"total-requests-two-types": "Open API 的 {type1} 和 {type2} 的总请求数。"
},
"Landing": {
"settings": "设置",
+2
View File
@@ -65,6 +65,7 @@
"@types/d3-scale": "^4.0.9",
"@types/d3-scale-chromatic": "^3.1.0",
"@types/lodash-es": "^4.17.12",
"@types/ms": "^2.1.0",
"@types/next-pwa": "^5.6.9",
"@types/three": "^0.176.0",
"@typescript-eslint/parser": "^7.16.1",
@@ -127,6 +128,7 @@
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"three": "^0.176.0",
"timeago-react": "^3.0.7",
"turndown": "^7.2.0",
"ua-parser-js": "^1.0.38",
"vaul": "^0.9.1",
+25 -4
View File
@@ -128,6 +128,9 @@ importers:
'@types/lodash-es':
specifier: ^4.17.12
version: 4.17.12
'@types/ms':
specifier: ^2.1.0
version: 2.1.0
'@types/next-pwa':
specifier: ^5.6.9
version: 5.6.9(@babel/core@7.24.5)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -314,6 +317,9 @@ importers:
three:
specifier: ^0.176.0
version: 0.176.0
timeago-react:
specifier: ^3.0.7
version: 3.0.7(react@18.3.1)
turndown:
specifier: ^7.2.0
version: 7.2.0
@@ -3319,8 +3325,8 @@ packages:
'@types/minimatch@5.1.2':
resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
'@types/ms@0.7.34':
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
'@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
'@types/next-pwa@5.6.9':
resolution: {integrity: sha512-KcymH+MtFYB5KVKIOH1DMqd0wUb8VLCxzHtsaRQQ7S8sGOaTH24Lo2vGZf6/0Ok9e+xWCKhqsSt6cgDJTk91Iw==}
@@ -7445,6 +7451,14 @@ packages:
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
timeago-react@3.0.7:
resolution: {integrity: sha512-5LSQuq+mzfEpMHkJQgMWnOs27dGh25aUQhffQpAW6q431vVVmHP194KYsM4bhzli0XfohUgN8+r3Pq6GVprN4A==}
peerDependencies:
react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
timeago.js@4.0.2:
resolution: {integrity: sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w==}
tiny-inflate@1.0.3:
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
@@ -11236,7 +11250,7 @@ snapshots:
'@types/debug@4.1.12':
dependencies:
'@types/ms': 0.7.34
'@types/ms': 2.1.0
'@types/eslint-scope@3.7.7':
dependencies:
@@ -11301,7 +11315,7 @@ snapshots:
'@types/minimatch@5.1.2': {}
'@types/ms@0.7.34': {}
'@types/ms@2.1.0': {}
'@types/next-pwa@5.6.9(@babel/core@7.24.5)(@opentelemetry/api@1.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
@@ -16396,6 +16410,13 @@ snapshots:
through@2.3.8: {}
timeago-react@3.0.7(react@18.3.1):
dependencies:
react: 18.3.1
timeago.js: 4.0.2
timeago.js@4.0.2: {}
tiny-inflate@1.0.3: {}
tiny-invariant@1.3.3: {}
+1 -1
View File
File diff suppressed because one or more lines are too long