feat: API analytics dashboard with i18n and theme support
Next.js full-stack analytics dashboard for new-api. - Direct PostgreSQL readonly queries on logs table - 5 pages: Dashboard, Rankings, Aggregation, Logs, Detail - Dark/Light/System theme with CSS variables - Chinese/English i18n (default Chinese) - Recharts with dual Y-axis for input/output tokens - Lucide icons + Motion animations - Docker + docker-compose with external sinobridge network, port 8019
This commit is contained in:
190
lib/i18n.tsx
Normal file
190
lib/i18n.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useState, useEffect, type ReactNode } from "react";
|
||||
|
||||
export type Locale = "zh" | "en";
|
||||
|
||||
const translations = {
|
||||
zh: {
|
||||
// nav
|
||||
"nav.overview": "总览",
|
||||
"nav.rankings": "排名",
|
||||
"nav.aggregation": "聚合",
|
||||
"nav.logs": "日志",
|
||||
// common
|
||||
"common.loading": "加载中...",
|
||||
"common.noData": "暂无数据",
|
||||
"common.records": "条记录",
|
||||
"common.systemOnline": "系统在线",
|
||||
"common.back": "返回",
|
||||
"common.backToRankings": "返回排名",
|
||||
"common.prevPage": "上一页",
|
||||
"common.nextPage": "下一页",
|
||||
"common.share": "占比",
|
||||
// time range
|
||||
"time.today": "今日",
|
||||
"time.7d": "7 天",
|
||||
"time.30d": "30 天",
|
||||
"time.all": "全部",
|
||||
// granularity
|
||||
"gran.day": "日",
|
||||
"gran.week": "周",
|
||||
"gran.month": "月",
|
||||
// metrics
|
||||
"metric.token": "Token",
|
||||
"metric.calls": "调用量",
|
||||
// dashboard
|
||||
"dash.title": "仪表盘",
|
||||
"dash.totalCalls": "调用次数",
|
||||
"dash.tokenUsage": "Token 消耗",
|
||||
"dash.activeUsers": "活跃用户",
|
||||
"dash.activeModels": "活跃模型",
|
||||
"dash.trend": "使用趋势",
|
||||
"dash.userTop10": "用户 Top 10 — Token 消耗",
|
||||
"dash.modelTop10": "模型 Top 10 — Token 消耗",
|
||||
// table headers
|
||||
"th.rank": "#",
|
||||
"th.name": "名称",
|
||||
"th.user": "用户",
|
||||
"th.calls": "调用次数",
|
||||
"th.input": "输入",
|
||||
"th.output": "输出",
|
||||
"th.totalToken": "总 Token",
|
||||
"th.time": "时间",
|
||||
"th.realModel": "真实模型",
|
||||
"th.channel": "渠道",
|
||||
"th.latency": "耗时",
|
||||
// rankings
|
||||
"rank.title": "排名",
|
||||
"rank.user": "用户",
|
||||
"rank.model": "模型",
|
||||
"rank.channel": "渠道",
|
||||
// aggregation
|
||||
"agg.title": "用户聚合",
|
||||
"agg.userCount": "用户数",
|
||||
"agg.totalCalls": "总调用",
|
||||
"agg.totalToken": "总 Token",
|
||||
// logs
|
||||
"logs.title": "日志明细",
|
||||
"logs.filterUser": "用户名",
|
||||
"logs.filterModel": "模型",
|
||||
"logs.filterToken": "Token 名称",
|
||||
// detail
|
||||
"detail.user": "用户",
|
||||
"detail.model": "模型",
|
||||
"detail.channel": "渠道",
|
||||
"detail.trend": "使用趋势",
|
||||
"detail.modelDist": "模型分布",
|
||||
"detail.userDist": "用户分布",
|
||||
// theme
|
||||
"theme.light": "浅色",
|
||||
"theme.dark": "深色",
|
||||
"theme.system": "系统",
|
||||
},
|
||||
en: {
|
||||
"nav.overview": "Overview",
|
||||
"nav.rankings": "Rankings",
|
||||
"nav.aggregation": "Aggregation",
|
||||
"nav.logs": "Logs",
|
||||
"common.loading": "Loading...",
|
||||
"common.noData": "No data",
|
||||
"common.records": "records",
|
||||
"common.systemOnline": "System Online",
|
||||
"common.back": "Back",
|
||||
"common.backToRankings": "Back to Rankings",
|
||||
"common.prevPage": "Previous",
|
||||
"common.nextPage": "Next",
|
||||
"common.share": "Share",
|
||||
"time.today": "Today",
|
||||
"time.7d": "7 Days",
|
||||
"time.30d": "30 Days",
|
||||
"time.all": "All",
|
||||
"gran.day": "Day",
|
||||
"gran.week": "Week",
|
||||
"gran.month": "Month",
|
||||
"metric.token": "Token",
|
||||
"metric.calls": "Calls",
|
||||
"dash.title": "Dashboard",
|
||||
"dash.totalCalls": "Total Calls",
|
||||
"dash.tokenUsage": "Token Usage",
|
||||
"dash.activeUsers": "Active Users",
|
||||
"dash.activeModels": "Active Models",
|
||||
"dash.trend": "Usage Trend",
|
||||
"dash.userTop10": "User Top 10 — Token Usage",
|
||||
"dash.modelTop10": "Model Top 10 — Token Usage",
|
||||
"th.rank": "#",
|
||||
"th.name": "Name",
|
||||
"th.user": "User",
|
||||
"th.calls": "Calls",
|
||||
"th.input": "Input",
|
||||
"th.output": "Output",
|
||||
"th.totalToken": "Total Token",
|
||||
"th.time": "Time",
|
||||
"th.realModel": "Real Model",
|
||||
"th.channel": "Channel",
|
||||
"th.latency": "Latency",
|
||||
"rank.title": "Rankings",
|
||||
"rank.user": "User",
|
||||
"rank.model": "Model",
|
||||
"rank.channel": "Channel",
|
||||
"agg.title": "User Aggregation",
|
||||
"agg.userCount": "Users",
|
||||
"agg.totalCalls": "Total Calls",
|
||||
"agg.totalToken": "Total Token",
|
||||
"logs.title": "Log Details",
|
||||
"logs.filterUser": "Username",
|
||||
"logs.filterModel": "Model",
|
||||
"logs.filterToken": "Token Name",
|
||||
"detail.user": "User",
|
||||
"detail.model": "Model",
|
||||
"detail.channel": "Channel",
|
||||
"detail.trend": "Usage Trend",
|
||||
"detail.modelDist": "Model Distribution",
|
||||
"detail.userDist": "User Distribution",
|
||||
"theme.light": "Light",
|
||||
"theme.dark": "Dark",
|
||||
"theme.system": "System",
|
||||
},
|
||||
} as const;
|
||||
|
||||
type TranslationKey = keyof typeof translations.zh;
|
||||
|
||||
interface I18nContextType {
|
||||
locale: Locale;
|
||||
setLocale: (l: Locale) => void;
|
||||
t: (key: TranslationKey) => string;
|
||||
}
|
||||
|
||||
const I18nContext = createContext<I18nContextType>({
|
||||
locale: "zh",
|
||||
setLocale: () => {},
|
||||
t: (key) => key,
|
||||
});
|
||||
|
||||
export function I18nProvider({ children }: { children: ReactNode }) {
|
||||
const [locale, setLocale] = useState<Locale>("zh");
|
||||
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem("locale") as Locale | null;
|
||||
if (saved && (saved === "zh" || saved === "en")) setLocale(saved);
|
||||
}, []);
|
||||
|
||||
const handleSetLocale = (l: Locale) => {
|
||||
setLocale(l);
|
||||
localStorage.setItem("locale", l);
|
||||
};
|
||||
|
||||
const t = (key: TranslationKey): string => {
|
||||
return translations[locale][key] || key;
|
||||
};
|
||||
|
||||
return (
|
||||
<I18nContext.Provider value={{ locale, setLocale: handleSetLocale, t }}>
|
||||
{children}
|
||||
</I18nContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useI18n() {
|
||||
return useContext(I18nContext);
|
||||
}
|
||||
Reference in New Issue
Block a user