use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Locale {
En,
Zh,
}
pub struct I18n {
pub locale: Locale,
pub tab_overview: &'static str,
pub tab_monthly: &'static str,
pub tab_yearly: &'static str,
pub tab_llm: &'static str,
pub title_dashboard: &'static str,
pub title_all_time: &'static str,
pub title_llm_summary: &'static str,
pub status_scroll: &'static str,
pub status_tab_focus: &'static str,
pub status_navigate: &'static str,
pub status_copy: &'static str,
pub status_refresh: &'static str,
pub status_quit: &'static str,
pub status_loading: &'static str,
pub status_parse_errors: &'static str,
pub status_llm_hint: &'static str,
pub card_projects: &'static str,
pub card_sessions: &'static str,
pub card_messages: &'static str,
pub card_total_tokens: &'static str,
pub card_cache_hit_rate: &'static str,
pub card_tool_calls: &'static str,
pub card_longest_session: &'static str,
pub card_avg_session: &'static str,
pub card_peak_period: &'static str,
pub chart_model_usage: &'static str,
pub chart_language_dist: &'static str,
pub chart_daily_activity: &'static str,
pub chart_tool_calls_model: &'static str,
pub chart_mcp_usage: &'static str,
pub chart_project_breakdown: &'static str,
pub chart_model_usage_month: &'static str,
pub chart_model_usage_year: &'static str,
pub chart_project_month: &'static str,
pub chart_project_year: &'static str,
pub msg_no_data: &'static str,
pub msg_scanning: &'static str,
pub msg_no_mcp: &'static str,
pub msg_no_tool_calls: &'static str,
pub heatmap_less: &'static str,
pub heatmap_more: &'static str,
pub llm_btn_all: &'static str,
pub llm_btn_monthly: &'static str,
pub llm_btn_yearly: &'static str,
pub llm_thinking_on: &'static str,
pub llm_thinking_off: &'static str,
pub llm_report_title: &'static str,
pub llm_thinking_header: &'static str,
pub llm_empty_hint: &'static str,
pub llm_generating: &'static str,
pub llm_generating_thinking: &'static str,
pub llm_cached: &'static str,
pub llm_disconnected: &'static str,
pub loading_generating: &'static str,
pub focus_model_bars: &'static str,
pub focus_language_bars: &'static str,
pub focus_tool_calls: &'static str,
pub focus_project_table: &'static str,
pub th_model: &'static str,
pub th_tool_calls: &'static str,
pub th_web_search: &'static str,
pub th_web_fetch: &'static str,
pub th_total: &'static str,
pub th_project: &'static str,
pub th_tokens: &'static str,
pub th_percent: &'static str,
pub more_models_fmt: &'static str,
pub project_unknown: &'static str,
pub legend_input: &'static str,
pub legend_output: &'static str,
pub legend_cache: &'static str,
}
static I18N: OnceLock<I18n> = OnceLock::new();
fn detect_locale() -> Locale {
for var in &["LC_ALL", "LC_MESSAGES", "LANG"] {
if let Ok(val) = std::env::var(var) {
let lower = val.to_lowercase();
if lower.starts_with("zh") {
return Locale::Zh;
}
}
}
#[cfg(windows)]
{
let lang_id = unsafe { win32_get_user_default_ui_language() };
if (lang_id & 0x03FF) == 0x04 {
return Locale::Zh;
}
}
Locale::En
}
#[cfg(windows)]
extern "system" {
fn GetUserDefaultUILanguage() -> u16;
}
#[cfg(windows)]
unsafe fn win32_get_user_default_ui_language() -> u16 {
GetUserDefaultUILanguage()
}
pub fn init() {
let locale = detect_locale();
let i18n = match locale {
Locale::Zh => I18n {
locale,
tab_overview: "[1] 概览",
tab_monthly: "[2] 月报",
tab_yearly: "[3] 年报",
tab_llm: "[4] LLM 总结",
title_dashboard: "Claude Code 仪表盘",
title_all_time: "全部统计",
title_llm_summary: "LLM 总结",
status_scroll: "↑↓ 滚动",
status_tab_focus: "Tab 聚焦",
status_navigate: "←→ 导航",
status_copy: "y 复制",
status_refresh: "r 刷新",
status_quit: "q 退出",
status_loading: "加载中...",
status_parse_errors: "解析错误",
status_llm_hint: "a/m/Y=报告 | ←→ 切换月/年",
card_projects: "项目数",
card_sessions: "会话数",
card_messages: "消息数",
card_total_tokens: "总 Token",
card_cache_hit_rate: "缓存命中率",
card_tool_calls: "工具调用",
card_longest_session: "最长会话",
card_avg_session: "平均会话",
card_peak_period: "高峰时段",
chart_model_usage: "模型 Token 使用 (堆叠)",
chart_language_dist: "语言分布",
chart_daily_activity: "每日活动",
chart_tool_calls_model: "各模型工具调用",
chart_mcp_usage: "MCP 工具使用",
chart_project_breakdown: "项目 Token 分布 (全部)",
chart_model_usage_month: "模型 Token 使用 (月)",
chart_model_usage_year: "模型 Token 使用 (年)",
chart_project_month: "项目 Token 分布 (月)",
chart_project_year: "项目 Token 分布 (年)",
msg_no_data: "暂无数据",
msg_scanning: "扫描中...",
msg_no_mcp: "暂无 MCP 工具使用记录",
msg_no_tool_calls: "暂无工具调用记录",
heatmap_less: "少",
heatmap_more: "多",
llm_btn_all: "全部 (A)ll",
llm_btn_monthly: "月报 (M)onthly",
llm_btn_yearly: "年报 (Y)early",
llm_thinking_on: "思考 [T]: ✓",
llm_thinking_off: "思考 [T]: ✗",
llm_report_title: "LLM 分析报告",
llm_thinking_header: "━━━━━━━━━━ 💭 思考过程 ━━━━━━━━━━",
llm_empty_hint: "按 a/m/Y 或 Enter 生成报告 | [T] 切换思考模式",
llm_generating: "正在生成报告...",
llm_generating_thinking: "正在生成报告 (思考模式)...",
llm_cached: "已从缓存加载",
llm_disconnected: "LLM 线程异常断开",
loading_generating: "正在生成 LLM 报告...",
focus_model_bars: "模型柱图",
focus_language_bars: "语言柱图",
focus_tool_calls: "工具调用",
focus_project_table: "项目表格",
th_model: "模型",
th_tool_calls: "工具调用",
th_web_search: "Web 搜索",
th_web_fetch: "Web 获取",
th_total: "总计",
th_project: "项目",
th_tokens: "Tokens",
th_percent: "%",
more_models_fmt: "... 还有 {} 个模型",
project_unknown: "[未知]",
legend_input: "▊Input ",
legend_output: "▊Output ",
legend_cache: "▊Cache",
},
Locale::En => I18n {
locale,
tab_overview: "[1] Overview",
tab_monthly: "[2] Monthly Report",
tab_yearly: "[3] Yearly Report",
tab_llm: "[4] LLM Summary",
title_dashboard: "Claude Code Dashboard",
title_all_time: "All-Time Summary",
title_llm_summary: "LLM Summary",
status_scroll: "↑↓ scroll",
status_tab_focus: "Tab focus",
status_navigate: "←→ navigate",
status_copy: "y copy",
status_refresh: "r refresh",
status_quit: "q quit",
status_loading: "Loading...",
status_parse_errors: "Parse errors",
status_llm_hint: "a/m/Y=report | ←→ switch month/year",
card_projects: "Projects",
card_sessions: "Sessions",
card_messages: "Messages",
card_total_tokens: "Total Tokens",
card_cache_hit_rate: "Cache Hit Rate",
card_tool_calls: "Tool Calls",
card_longest_session: "Longest Session",
card_avg_session: "Avg Session",
card_peak_period: "Peak Period",
chart_model_usage: "Model Token Usage (Stacked)",
chart_language_dist: "Language Distribution",
chart_daily_activity: "Daily Activity",
chart_tool_calls_model: "Tool Calls by Model",
chart_mcp_usage: "MCP Tool Usage",
chart_project_breakdown: "Project Token Breakdown (All-Time)",
chart_model_usage_month: "Model Token Usage (Month)",
chart_model_usage_year: "Model Token Usage (Year)",
chart_project_month: "Project Token Breakdown (Month)",
chart_project_year: "Project Token Breakdown (Year)",
msg_no_data: "No data",
msg_scanning: "Scanning...",
msg_no_mcp: "No MCP tool usage recorded",
msg_no_tool_calls: "No tool calls recorded",
heatmap_less: "Less",
heatmap_more: "More",
llm_btn_all: "All (A)ll",
llm_btn_monthly: "Monthly (M)onthly",
llm_btn_yearly: "Yearly (Y)early",
llm_thinking_on: "Think [T]: ✓",
llm_thinking_off: "Think [T]: ✗",
llm_report_title: "LLM Analysis Report",
llm_thinking_header: "━━━━━━━━━━ 💭 Thinking Process ━━━━━━━━━━",
llm_empty_hint: "Press a/m/Y or Enter to generate report | [T] Toggle thinking mode",
llm_generating: "Generating report...",
llm_generating_thinking: "Generating report (thinking mode)...",
llm_cached: "Loaded from cache",
llm_disconnected: "LLM thread disconnected unexpectedly",
loading_generating: "Generating LLM report...",
focus_model_bars: "Model Bars",
focus_language_bars: "Language Bars",
focus_tool_calls: "Tool Calls",
focus_project_table: "Project Table",
th_model: "Model",
th_tool_calls: "Tool Calls",
th_web_search: "Web Search",
th_web_fetch: "Web Fetch",
th_total: "Total",
th_project: "Project",
th_tokens: "Tokens",
th_percent: "%",
more_models_fmt: "... and {} more models",
project_unknown: "[unknown]",
legend_input: "▊Input ",
legend_output: "▊Output ",
legend_cache: "▊Cache",
},
};
let _ = I18N.set(i18n);
}
pub fn t() -> &'static I18n {
I18N.get().expect("i18n::init() not called")
}
pub fn current_locale() -> Locale {
t().locale
}