// ==UserScript==
// @name Maybe 财务软件界面翻译(增强版)
// @namespace http://tampermonkey.net/
// @version 2.35
// @description 翻译 Maybe 网站界面为中文,支持 SPA 页面跳转与日期翻译等增强功能。
// @author ChatGPT
// @match *://*/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
/* -------- ① 早退判断:仅在标题包含“Maybe”时运行 -------- */
if (!/Maybe/i.test(document.title)) return;
/* --------------------------------------------------
静态文本翻译表(完整,不遗漏任何键)
-------------------------------------------------- */
const translations = {
// 常规界面文案 ——————————————————————————————————
"Transaction": "交易",
"Credit Cards": "信用卡",
"New cash": "新增现金账户",
"New crypto": "新增加密货币账户",
"New other liability": "新增其他负债账户",
"Other Liabilities": "其他负债",
"New credit card": "新增信用卡账户",
"Category": "分类",
"Amount": "金额",
"Updated to": "更新为",
"You can create a rule to automatically categorize transactions like this one": "您可以创建规则,自动分类类似的交易",
"Don't show this again": "不再显示此提示",
"Dismiss": "忽略",
"Create rule": "创建规则",
"Assets": "资产",
"New assets": "新增资产账户",
"Cash account": "现金账户",
"Investment account": "投资账户",
"Brokerage account": "经纪账户",
"New investment": "新增投资账户",
"investments": "投资账户",
"Crypto assets": "加密资产",
"Other assets": "其他资产",
"Total value": "总价值",
"Equal to last month": "与上月持平",
"Activity": "活动",
"Holdings": "持仓",
"New trade": "新增交易",
"Total return": "总回报",
"Name": "名称",
"Percentage": "占比",
"Average cost": "平均成本",
"Home": "首页",
"Default": "默认",
"Account": "账户",
"Enter asset details": "输入资产明细",
"Dashboard": "仪表盘",
"Transactions": "交易",
"Budgets": "预算",
"Assets": "资产",
"Debts": "负债",
"All": "全部",
"New debt": "新增负债账户",
"New budget": "新增预算",
"New transaction": "新增交易",
"New account": "新增账户",
"New asset": "新增资产账户",
"New import": "新增导入",
"New rule": "新增规则",
"New tag": "新增标签",
"New category": "新增分类",
"New merchant": "新增商家",
"Import": "导入",
"Type": "类型",
"Buy": "买入",
"Sell": "卖出",
"shares of": "股",
"Trade": "交易",
"New balance": "调整余额",
"Ticker symbol": "股票代码",
"Quantity": "数量",
"Price per share": "每股价格",
"CNY": "人民币",
"New transaction": "新增交易",
"Sell": "卖出",
"Deposit": "存款",
"Withdrawal": "提款",
"Interest": "利息",
"Date": "日期",
"Edit rules": "编辑规则",
"Edit categories": "编辑分类",
"Edit tags": "编辑标签",
"Edit merchants": "编辑商家",
"Edit imports": "编辑导入",
"Total transactions": "交易总数",
"No entries found": "未找到记录",
"Try adding an entry, editing filters or refining your search": "请尝试添加记录、调整筛选或优化搜索",
"Welcome back": "欢迎回来",
"Welcome back, ": "欢迎回来,", // 用户名会跟在后面
"Here's what's happening with your finances": "您的财务概况",
"No accounts yet": "暂无账户",
"Add account": "添加账户",
"Add accounts to display net worth data": "添加账户以显示净资产",
"Preferences": "偏好设置",
"Security": "安全设置",
"Self hosting": "自托管",
"Accounts": "账户",
"Account": "账户",
"Imports": "数据导入",
"Tags": "标签",
"Categories": "分类",
"Rules": "规则",
"Merchants": "商家",
"More": "更多",
"What's new": "更新日志",
"Feedback": "反馈",
"Logout": "退出登录",
"Save": "保存",
"Last Day": "最近1天",
"Current Week": "本周",
"Current Month": "本月",
"Current Year": "本年",
"Reset account": "重置账户",
"Delete account": "删除账户",
"Date format": "日期格式",
"Default Period": "默认周期",
"Country": "国家/地区",
"Language": "语言",
"Timezone": "时区",
"Currency": "货币",
"Chinese Renminbi Yuan (CNY)": "人民币 (CNY)",
"Chinese (Simplified) (zh-CN)": "中文(简体)",
"Today": "今天",
"Oops!": "哎呀!",
"Use defaults (recommended)": "使用默认分类(推荐)",
"Add an account either via connection, importing or entering manually.": "通过连接、导入或手动方式添加账户。",
"AI-enabled rule actions will cost money. Be sure to filter as narrowly as possible to avoid unnecessary costs.": "启用 AI 的规则会产生费用,请尽量精确过滤条件以避免不必要的支出。",
"Set up rules to perform actions to your transactions and other data on every account sync.": "设置规则,在每次账户同步时自动处理交易等数据。",
"Imports · 1": "导入 · 1",
"View": "查看",
"Delete": "删除",
"Require invite code for signup": "注册需邀请码",
"Every new user that joins your instance of Maybe can only do so via an invite code": "新用户注册必须使用邀请码",
"Require email confirmation": "需要邮箱验证",
"Generated codes": "已生成的邀请码",
"Generate new code": "生成新邀请码",
"Clear data cache": "清除数据缓存",
"Clearing the data cache will remove all exchange rates, security prices, account balances, and other data. This will not delete accounts, transactions, categories, or other user-owned data.": "此操作会清除汇率、证券价格、账户余额等缓存,不会删除账户、交易和分类等用户数据。",
"Hostings": "托管",
"Self-Hosting": "自托管",
"General Settings": "通用设置",
"Synth Settings": "Synth 设置",
"Input the API key provided by Synth": "输入 Synth 提供的 API Key",
"API Key": "API Key",
"Enter your API key here": "在此输入 API Key",
"In progress": "进行中",
// 仪表盘 ——————————————————————————
"Net Worth": "净资产",
"vs. last month": "较上月",
"No Liabilities yet": "暂无负债",
"Add your Liabilities accounts to see a full breakdown": "添加负债账户以查看完整明细",
"No cash flow data for this time period": "该周期暂无现金流数据",
"Add transactions to display cash flow data or expand the time period": "添加交易以查看现金流,或扩大时间范围",
"Add transaction": "新增交易",
"Weight": "占比",
"Surplus": "盈余",
"Open matcher": "打开匹配器",
"Settings": "设置",
"Value": "价值",
// 设置页面 — 新的设置项翻译 ——————————————
"One-time Income": "一次性收入",
"One-time transactions will be excluded from certain budgeting calculations and reports to help you see what's really important.": "一次性交易将被排除在某些预算计算和报告之外,帮助你查看最重要的内容。",
"Transfer or Debt Payment?": "转账或债务支付?",
"Transfers and payments are special types of transactions that indicate money movement between 2 accounts.": "转账和支付是特殊类型的交易,表示资金在两个账户之间的流动。",
"Delete transaction": "删除交易",
"This permanently deletes the transaction, affects your historical balances, and cannot be undone.": "此操作会永久删除交易,影响历史余额,且无法恢复。",
// 预算设置 ——————————————————————————
"Setup your budget": "设置预算",
"Enter your monthly earnings and planned spending below to setup your budget.": "请输入您的月收入和计划支出以设置预算。",
"Budgeted spending": "预算支出",
"Expected income": "预期收入",
"Autosuggest income & spending budget": "自动推荐收入与支出预算",
"This will be based on transaction history. AI can make mistakes, verify before continuing.": "该推荐基于交易历史,AI 可能出错,请在继续前核实。",
"Continue": "继续",
// 编辑分类预算 —————————————————————————
"Edit your category budgets": "编辑分类预算",
"Adjust category budgets to set spending limits. Unallocated funds will be automatically assigned as uncategorized.": "调整各分类预算以设置支出限额。未分配资金将自动归为未分类。",
"% set": "% 已设置",
"left to allocate": "剩余可分配",
"Confirm": "确认",
// 时间段控件 ————————————————————————
"1D": "1 天",
"7D": "7 天",
"30D": "30 天",
"90D": "90 天",
"365D": "365 天",
"5Y": "5 年",
"WTD": "本周",
"MTD": "本月",
"YTD": "年初至今",
// AI 提示 ———————————————————————————
"Enable Maybe AI": "启用 Maybe AI",
"AI responses are informational only and are not financial advice.": "AI 回答仅供参考,并非财务建议。",
"To use the AI assistant, you need to set the": "要使用 AI 助手,您需要设置",
"environment variable in your self-hosted instance.": "自托管实例中的环境变量",
"Disable anytime. All data sent to our LLM providers is anonymized.": "可随时停用。发送至大模型服务商的数据均已匿名化。",
// 预算界面 ———————————————————————————
"Budgeted": "预算",
"Actual": "实际",
"Spent": "已支出",
"left": "剩余",
"over": "超支",
"from": "预算",
"Edit": "编辑",
"Categories": "分类",
"Today": "今天",
// 以上词汇在动态翻译中有进一步处理
// 更多界面文本 ———————————————————————
"Filter": "筛选",
"Clear filters": "清空筛选",
"Tag": "标签",
"Merchant": "商户",
// 规则页面 ————————————————————————
"New transaction rule": "新增交易规则",
"Rule name (optional)": "规则名称(可选)",
"Enter a name for this rule": "请输入规则名称",
"IF": "如果",
"Add condition": "添加条件",
"Add condition group": "添加条件组",
"THEN": "那么",
"Add action": "添加动作",
"FOR": "作用范围",
"All past and future transactions": "所有历史及未来交易",
"Starting from": "起始日期",
"yyyy/mm/dd": "年/月/日",
"Greater than": "大于",
"Greater or equal to": "大于或等于",
"Less than": "小于",
"Less than or equal to": "小于或等于",
"Is equal to": "等于",
"Enter...": "输入内容...",
"Add as new category": "新增分类",
"Leave unassigned": "保持未分配",
// 历史数据提示 ————————————————————————
"Missing historical data": "缺少历史数据",
"Maybe uses Synth API to fetch historical exchange rates, security prices, and more. This data is required to calculate accurate historical account balances.": "Maybe 使用 Synth API 获取历史汇率、证券价格等数据。此数据对于准确计算历史账户余额至关重要。",
"Add your Synth API key here.": "在此添加您的 Synth API 密钥。",
// 导入文件分类映射 ————————————————————
"Assign your categories": "分配您的分类",
"Assign all of your imported file's categories to Maybe's existing categories. You can also add new categories or leave them uncategorized.": "将导入文件中的所有类别分配到 Maybe 的现有类别中。您也可以添加新的类别或将其保持为未分类。",
"CATEGORY IN CSV": "CSV 中的分类",
"CATEGORY IN MAYBE": "Maybe 中的分类",
"ROWS": "行数",
"Assign your tags": "分配您的标签",
"Assign all of your imported file's tags to Maybe's existing tags. You can also add new tags or leave them uncategorized.": "将导入文件中的所有标签分配到 Maybe 的现有标签中。您也可以添加新标签或将其保持为未分类。",
"TAG IN CSV": "CSV 中的标签",
"TAG IN MAYBE": "Maybe 中的标签",
"Assign your accounts": "分配您的账户",
"Assign all of your imported file's accounts to Maybe's existing accounts. You can also add new accounts or leave them uncategorized.": "将导入文件中的所有账户分配到 Maybe 的现有账户中。您也可以添加新账户或将其保持为未分类。",
"ACCOUNT IN CSV": "CSV 中的账户",
"ACCOUNT IN MAYBE": "Maybe 中的账户",
"Need to create a new account for unassigned rows?": "需要为未分配的行创建新账户吗?",
"Create account": "创建账户",
// 导入错误提示 ——————————————————————
"Reverting import failed": "撤销导入失败",
"Please try again or contact support": "请重试或联系客服。",
"Try again": "重试",
// 账户余额输入 ——————————————————————
"Account name": "账户名称",
"Current balance": "当前余额",
"Subtype": "子类型",
"Select investment type": "选择投资类型",
"None": "无",
"Brokerage": "经纪账户",
"Pension": "养老金",
"Retirement": "退休账户",
"401(k)": "401(k)账户",
"Roth 401(k)": "Roth 401(k)账户",
"529 Plan": "529计划",
"Health Savings Account (HSA)": "健康储蓄账户 (HSA)",
"Mutual Fund": "共同基金",
"Traditional IRA": "传统 IRA",
"Roth IRA": "Roth IRA",
"Angel": "天使投资",
// 账户类型选择 —————————————————————
"Enter": "输入",
"account balance": "账户余额",
"balance": "余额",
"Select account type": "选择账户类型",
"Select": "选择",
"Checking": "支票账户",
"Savings": "储蓄账户",
"Health Savings Account": "健康储蓄账户 (HSA)",
"Certificate of Deposit": "定期存款 (CD)",
"Money Market": "货币市场账户",
// 新增账户类别 ——————————————————————
"What would you like to add?": "请选择要添加的账户类型",
"Cash": "现金账户",
"Investment": "投资账户",
"Crypto": "加密资产",
"Property": "房产",
"Vehicle": "车辆",
"Credit Card": "信用卡",
"Loan": "贷款",
"Other Asset": "其他资产",
"Other Liability": "其他负债",
"Import accounts": "导入账户",
"Navigate": "导航",
"Close": "关闭",
// 规则设置 ————————————————————————
"Rule": "规则",
"Transaction name": "交易名称",
"Transaction amount": "交易金额",
"Transaction merchant": "交易商户",
"Contains": "包含",
"Equal to": "等于",
"match": "满足",
"all": "全部条件",
"any": "任一条件",
"none": "不包含",
"enter a value": "输入值",
"of the following conditions": "以下条件",
"Set transaction category": "设置交易分类",
"Set transaction tags": "设置交易标签",
"Set transaction merchant": "设置交易商户",
"Set transaction name": "设置交易名称",
// 转账 / 新增交易 ———————————————————
"New transfer": "新增转账",
"Expense": "支出",
"Income": "收入",
"Expenses": "支出",
"Transfer": "转账",
"From": "转出账户",
"To": "转入账户",
"Select account": "选择账户",
"Create transfer": "创建转账",
// 交易字段 ———————————————————————
"Description": "交易说明",
"Description*": "交易说明 *",
"Description *": "交易说明 *",
"Describe transaction": "请输入交易说明",
"Account*": "账户 *",
"Select an Account": "选择账户",
"Category": "分类",
"Select a Category": "选择分类",
"Details": "详细信息",
"Notes": "备注",
"Enter a note": "请输入备注",
"(none)": "无",
// 账户详情 ———————————————————————
"Other Assets": "其他资产",
"Cash Flow": "现金流",
"Balance": "余额",
"no change vs. last month": "与上月持平",
"Import transactions": "导入交易",
"Activity": "活动",
"Search entries by name": "按名称搜索条目",
"New": "新增",
"Delete Account?": "删除账户?",
"Are you sure you want to delete account?": "确定要删除账户吗?",
"This is not reversible.": "此操作不可撤销。",
"Delete Account": "删除账户",
"BALANCE": "余额",
"Enter credit card details": "输入信用卡信息",
"Account name": "账户名称",
"Available credit": "可用信用额度",
"Minimum payment": "最低还款金额",
"APR": "年利率",
"Expiration date": "到期日期",
"Annual fee": "年费",
"New Account": "新增账户",
// 房产相关 ———————————————————————
"Select property type": "选择房产类型",
"Single Family Home": "独立住宅",
"Multi-Family Home": "多户住宅",
"Condominium": "公寓",
"Townhouse": "联排别墅",
"Investment Property": "投资房产",
"Second Home": "第二住宅",
"Year built": "建造年份",
"Living area": "居住面积",
"Street address": "街道地址",
"City": "城市",
"ZIP/Postal code": "邮政编码",
"Unit of measurement": "计量单位",
"Square Feet": "平方英尺",
"State/Province": "州/省",
// 其他 ———————————————————————————
"Liabilities": "负债",
"Cashflow": "现金流",
"Confirm": "确认",
// 导入界面 ———————————————————————
"No imports yet.": "尚未导入数据。",
"New CSV Import": "新的 CSV 导入",
"You can manually import various types of data via CSV or use one of our import templates like Mint.": "您可以通过 CSV 手动导入各种数据,或使用我们的导入模板(如 Mint)。",
"SOURCES": "来源",
"Import investments": "导入投资",
"Import from Mint": "从 Mint 导入",
"Import your data": "导入您的数据",
"Paste or upload your CSV file below. Please review the instructions in the table below before beginning.": "在下方粘贴或上传您的 CSV 文件。请在开始之前查看下表中的说明。",
"Upload CSV Copy & Paste": "上传 CSV / 复制粘贴",
"Browse to add your CSV file here": "点击浏览以添加您的 CSV 文件",
"Upload CSV": "上传 CSV",
"Download a sample CSV to see the required CSV format": "下载 CSV 示例文件,查看所需的 CSV 格式",
"Resume Account Import": "恢复账户导入",
"Please finalize your file upload.": "请完成您的文件上传。",
"Account (optional)": "账户(可选)",
"Multi-account import": "多账户导入",
"Paste your CSV file contents here": "在此粘贴您的 CSV 文件内容",
"Configure your import": "配置您的导入",
"Select the columns that correspond to each field in your CSV.": "选择与 CSV 中每个字段对应的列。",
"Custom column": "自定义列",
"Amount type strategy": "金额类型策略",
"Signed amount": "已带正负号金额",
"Incomes are positive": "收入为正值",
"Incomes are negative": "收入为负值",
"Leave empty": "留空",
"Apply configuration": "应用配置",
"Sample data from your uploaded CSV": "您上传的 CSV 示例数据",
"Please configure your import before proceeding": "请在继续之前完成导入配置",
"as amount type column": "作为金额类型列",
"Set": "设置",
"Select column": "选择列",
"Upload": "上传",
"Configure": "配置",
"Clean": "清理",
"Map": "映射",
"Comma (,)": "逗号 (,)",
"Semicolon (;)": "分号 (;)",
"Col sep": "列分隔符",
"IMPORTS": "导入",
"Import": "导入",
"Transaction": "交易",
"Account": "账户",
"Revert import?": "撤销导入?",
"This will delete transactions that were imported, but you will still be able to review and re-import your data at any time.": "这将删除已导入的交易,但您仍可以随时查看并重新导入数据。",
"Revert": "撤销",
"Template configuration found": "检测到模板配置",
"We found a configuration from a previous import for this account. Would you like to apply it to this import?": "检测到此前为该账户保存的导入配置,是否将其应用到本次导入?",
"Manually configure": "手动配置",
"Apply template": "应用模板",
// 页面中的动态日期翻译占位文本 ———————————
"Depositories": "存款账户",
"Current Balance": "当前余额",
"Initial Balance": "初始余额",
"Remaining": "剩余金额",
"Search entries by name": "按名称搜索记录",
// 分类弹窗 ———————————————————————
"Uncategorized": "未分类",
"OVERVIEW": "概览",
"Monthly average spending": "平均月支出",
"Monthly median spending": "月支出中位数",
"RECENT TRANSACTIONS": "最近交易",
"No transactions found for this budget period.": "此预算周期内未找到交易。"
};
/* --------------------------------------------------
月份 & 星期映射(含缩写)用于日期翻译
-------------------------------------------------- */
const monthMap = {
January: '1月', Jan: '1月',
February: '2月', Feb: '2月',
March: '3月', Mar: '3月',
April: '4月', Apr: '4月',
May: '5月',
June: '6月', Jun: '6月',
July: '7月', Jul: '7月',
August: '8月', Aug: '8月',
September: '9月',Sep: '9月', Sept: '9月',
October: '10月', Oct: '10月',
November: '11月',Nov: '11月',
December: '12月',Dec: '12月'
};
const weekdayMap = {
Monday: '星期一', Mon: '星期一',
Tuesday: '星期二', Tue: '星期二',
Wednesday: '星期三', Wed: '星期三',
Thursday: '星期四', Thu: '星期四',
Friday: '星期五', Fri: '星期五',
Saturday: '星期六', Sat: '星期六',
Sunday: '星期日', Sun: '星期日'
};
/* --------------------------------------------------
各类动态文本翻译函数
-------------------------------------------------- */
// 1. 动态金额相关短语翻译函数(spent/over/left/earned & of/from)
const translateFinancialPhrase = txt => {
let m;
// e.g. "¥4,805.67 spent" -> "已支出 ¥4,805.67"
if (m = txt.match(/^¥([\d,]+\.\d{2})\s+spent$/i)) {
return `已支出 ¥${m[1]}`;
}
// e.g. "¥2,805.67 over" -> "超出 ¥2,805.67"
if (m = txt.match(/^¥([\d,]+\.\d{2})\s+over$/i)) {
return `超出 ¥${m[1]}`;
}
// e.g. "¥10,000.00 left" -> "¥10,000.00 剩余"
if (m = txt.match(/^¥([\d,]+\.\d{2})\s+left$/i)) {
return `¥${m[1]} 剩余`;
}
// e.g. "¥0.00 earned" -> "¥0.00 已收入"
if (m = txt.match(/^¥([\d,]+\.\d{2})\s+earned$/i)) {
return `¥${m[1]} 已收入`;
}
// e.g. "of ¥2,000.00" -> "预算为 ¥2,000.00"
if (m = txt.match(/^of\s+¥([\d,]+\.\d{2})$/i)) {
return `预算为 ¥${m[1]}`;
}
// e.g. "from ¥150.00" -> "预算 ¥150.00"
if (m = txt.match(/^from\s+¥([\d,]+\.\d{2})$/i)) {
return `预算 ¥${m[1]}`;
}
return null;
};
// 2. 金额 + “avg” 平均值,例如 "¥0.00 avg" -> "¥0.00 平均"
const translateAmountAvg = txt => {
const m = txt.match(/^(.+?)\s+avg$/i);
return m ? `${m[1]} 平均` : null;
};
// 3. 每月平均值,例如 "¥0/m avg" -> "¥0/月 平均"
const translatePerMonthAvg = txt => {
const m = txt.match(/^(.+?)\/m\s+avg$/i);
return m ? `${m[1]}/月 平均` : null;
};
// 4. 完整日期时间,例如 "April 5, 2025 at 3:30 PM" -> "2025年4月5日 下午 3:30"
const translateFullDate = txt => {
const m = txt.match(/^(Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\s+(\d{1,2}),\s+(\d{4})\s+at\s+(\d{1,2}):(\d{2})\s(AM|PM)$/i);
if (m) {
const monthName = m[1];
const day = parseInt(m[2], 10);
const year = m[3];
const hour = m[4];
const minute = m[5];
const ampm = m[6];
return `${year}年${monthMap[monthName]}${day}日 ${ampm === 'AM' ? '上午' : '下午'} ${hour}:${minute}`;
}
return null;
};
// 5. 月份 年 + “spending”,例如 "Jun 2025 spending" -> "2025年6月支出"
const translateMonthlySpending = txt => {
const m = txt.match(/^(Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\s(\d{4})\sspending$/i);
return m ? `${m[2]}年${monthMap[m[1]]}支出` : null;
};
// 6. 月份 年,例如 "May 2025" -> "2025年5月"
const translateYearMonth = txt => {
const m = txt.match(/^(Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\s(\d{4})$/i);
return m ? `${m[2]}年${monthMap[m[1]]}` : null;
};
// 7. 日期范围筛选,例如 "on or after 2025-01-01" -> "起始日期:2025-01-01"
const translateDateRange = txt => {
const after = txt.match(/^on or after\s+(\d{4}-\d{2}-\d{2})$/i);
if (after) return `起始日期:${after[1]}`;
const before = txt.match(/^on or before\s+(\d{4}-\d{2}-\d{2})$/i);
if (before) return `结束日期:${before[1]}`;
return null;
};
// 8. 时间段简写,例如 "30D" -> "30天", "12M" -> "12月"
const translateShorthandPeriod = txt => {
const m = txt.match(/^(\d+)([DWMY])$/);
if (!m) return null;
const num = m[1], unit = m[2];
const unitMap = { D: "天", W: "周", M: "月", Y: "年" };
return `${num}${unitMap[unit]}`;
};
// 9. 最近多少天/周等,例如 "Last 30 days" -> "最近30天"
const translateLastPeriod = txt => {
const m = txt.match(/^Last\s+(\d+)\s+(days?|weeks?|months?|years?)$/i);
if (!m) return null;
const num = m[1];
const unit = m[2].toLowerCase();
let unitCn;
if (unit.startsWith("day")) unitCn = "天";
else if (unit.startsWith("week")) unitCn = "周";
else if (unit.startsWith("month")) unitCn = "个月";
else unitCn = "年";
return `最近${num}${unitCn}`;
};
// 10. 交易导入完成提示,例如 "Transaction Import: Apr 1, 2025 at 3:30 PM" -> "交易导入:2025年4月1日 下午 3:30 完成"
const translateTransactionImport = txt => {
const m = txt.match(/^Transaction Import:\s*(.*)\s+at\s+(\d{1,2}):(\d{2})\s(AM|PM)$/i);
if (m) {
// m[1] 包含类似 "Apr 1, 2025" 的日期部分
const datePart = m[1].replace(/(\d{4})/, '$1年'); // 将年份部分添加“年”
const time = `${m[2]}:${m[3]} ${m[4] === 'AM' ? '上午' : '下午'}`;
return `交易导入:${datePart} ${time} 完成`;
}
return null;
};
// 将所有动态翻译函数收集到数组,按优先顺序依次匹配
const dynamicFns = [
translateFinancialPhrase,
translateTransactionImport,
translatePerMonthAvg,
translateAmountAvg,
translateFullDate,
translateMonthlySpending,
translateYearMonth,
translateDateRange,
translateShorthandPeriod,
translateLastPeriod
];
/* --------------------------------------------------
文本节点翻译主函数
-------------------------------------------------- */
const translateTextNode = node => {
if (!node || node.nodeType !== Node.TEXT_NODE) return;
const raw = node.textContent;
const txt = raw.trim();
if (!txt) return;
// 1) 静态翻译(不区分大小写匹配完整键)
const key = txt in translations ? txt
: txt.toUpperCase() in translations ? txt.toUpperCase()
: txt.toLowerCase() in translations ? txt.toLowerCase()
: null;
if (key) {
const translated = translations[key];
if (translated) {
node.textContent = raw.replace(txt, translated);
return;
}
}
// 2) 动态翻译(遍历动态规则函数)
for (const fn of dynamicFns) {
const result = fn(txt);
if (result) {
node.textContent = raw.replace(txt, result);
return;
}
}
// 3) 月份或星期名称翻译(独立单词)
if (monthMap[txt]) {
node.textContent = raw.replace(txt, monthMap[txt]);
} else if (weekdayMap[txt]) {
node.textContent = raw.replace(txt, weekdayMap[txt]);
}
};
// 遍历页面中的所有文本节点进行初始翻译
const translatePage = () => {
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
let node;
while ((node = walker.nextNode())) {
translateTextNode(node);
}
};
// 页面初始翻译
translatePage();
// 轮询翻译 - 定时检查新节点
const INTERVAL = 300;
setInterval(translatePage, INTERVAL);
// 监听 SPA 单页应用路由变化,在 URL 改变时触发一次翻译
let lastUrl = location.href;
setInterval(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
// 延迟执行以确保新内容加载完成
setTimeout(translatePage, 500);
}
}, 500);
})();