// ==UserScript==
// @name feeder 汉化脚本
// @namespace https://github.com/98zi/MyTampermonkey
// @version 0.0.2
// @author 98zi
// @description feeder.com 汉化脚本
// @include *://feeder.co/*
// @license MIT
// @icon https://feeder.co/favicon.ico
// @grant none
// @run-at document-start
// ==/UserScript==
const zh_Hans = [
['All feeds', '全部订阅'],
['Unread', '未读'],
['Starred', '星标'],
['Home', '首页'],
['Dashboard', '图表'],
['Rules', '规则'],
['Team', '团队'],
['Library', '列表'],
['Account', '账户'],
['Help', '帮助'],
['Collapse', '收起'],
['Reload feeds', '重新加载源数据'],
['Open all unread...', '打开所有未读'],
['Mark all as read', '全部标为已读'],
['Mark feed as read', '将 feed 标为已读'],
['Export posts', '导出帖子'],
['Go to page', '跳转到网站'],
['Edit feed', '编辑 feed'],
['Delete feed', '删除 feed'],
['Filters', '过滤'],
['Display', '展示'],
['Order', '排序'],
['Newest first', '最新'],
['Oldest first', '最旧'],
['Realtime', '实时'],
['Auto-update', '自动更新'],
['Content style', '风格'],
['Timestamp', '时间戳'],
['Previous post', '上一篇'],
['Next post', '下一篇'],
['Share post', '分享'],
['Mark as unread', '标记为已读'],
['Star post', '收藏'],
['Toggle collections', '切换集合'],
['Simple', '简单'],
['Full', '源站'],
['Free', '免费'],
['My account', '我的账户'],
['Settings', '设置'],
['Share', '分享'],
['Add to collections', '添加到收藏夹'],
['Appearance', '外观'],
['Log out', '退出'],
['Change display mode', '更改显示模式'],
['Minimal', '极小'],
['Reader', '读者'],
['3-pane', '3 窗格'],
['Reader', '读者'],
['Two panes with collapsing folders', '两个包含折叠文件夹的窗格'],
['Read posts inline for fast consuming', '内联阅读帖子,快速查看'],
['The entire hierarchy for the best overview', '最佳预览的层次结构'],
['Change theme', '更改主题'],
['Light', '明亮'],
['Dark', '暗黑'],
['Holiday', '假期'],
['Orange', '橙色'],
['Holiday', '假期'],
['Sand', '沙滩'],
['Grey', '灰色'],
['Follow OS setting', '跟随操作系统设置'],
['Reload', '重新加载'],
['Expanded', '放大'],
['Collapsed', '缩小'],
['Column width', '全宽'],
['Notifications', '通知'],
['Sound', '声音'],
['iOS/Android', 'iOS/Android'],
['Get Feeder Plus', '获取 Feeder Plus'],
['Toggle all feeds', '选择全部'],
['Email summaries', '邮件摘要'],
['Connections', '连接'],
['Advanced', '高级'],
['Global', '全球'],
['Ask before marking many as read', '在标记已读前询问'],
['Track unread posts -', '跟踪未读帖子'],
['When OFF stops marking incoming posts as unread', '当 OFF 时,停止将收到的帖子标记为未读'],
['Notify me when my feeds stop working', '当我的 Feed 停止工作时通知我'],
['Connected', '连接'],
['Receive e-mail summaries', '接受邮件订阅'],
['Get a periodical with the latest posts from your feeds. Daily, weekly or monthly. You decide.', '每天、每周或每月获取包含源中最新帖子的期刊。'],
['Enable e-mail summaries', '开始邮箱订阅'],
['Plan', '计划'],
['Email and password', '邮箱和密码'],
['Invoices', '发票'],
['Billing address', '账单'],
['Your plan', '你的计划'],
['Change plan', '更改计划'],
['Payment details', '付款明细'],
['No payment method', '没有付款计划'],
['Upgrade for more', '升级获取更多'],
['Change email', '修改邮箱'],
['Current email address is', '当前邮箱地址为'],
['New email', '新邮箱'],
['Repeat new email', '重复新邮箱'],
['Update email', '更新邮箱'],
['Change password', '修改密码'],
['Current password', '当前密码'],
['New password', '新密码'],
['Update password', '更新密码'],
['Delete your account', '删除你的账户'],
['All your data will be deleted, and there will be no way to retrieve them.', '您的所有数据都将被删除,并且无法检索它们。'],
['Current email address', '当前邮箱'],
['Remove your account', '确认删除账户'],
['Invoice date', '发票日期'],
['VAT', '增值税'],
['Total', '总计'],
['You have no invoices', '您没有发票'],
['Once your first payment is generated it will be shown here', '如果你付款成功,它将会显示在这里。'],
['Your billing address', '你的账单邮寄地址'],
['This information will be on your invoices.', '此信息将出现在您的发票上'],
['Your name', '你的姓名'],
['Company name', '公司名称'],
['Address', '地址'],
['Address (line 2)', '地址(第二行)'],
['Zip code', '邮政编码'],
['VAT Number', '增值税号'],
['Country', '国家'],
['Additional information', '其他信息'],
['Update billing address', '更新账单邮寄地址'],
['Help center', '帮助中心'],
["What's new?", "最新新闻"],
['Keyboard navigation', '键盘导航'],
['Contact support', '联系支持人员'],
['Downloads', '下载安装'],
['Automate your workflow with Rules', '使用规则自动执行工作流程'],
['Define your keywords and choose what happens next. Filter posts, add them to collections, post content to Slack and Microsoft teams, and more.', '定义关键字并选择接下来要执行的操作。筛选帖子、将其添加到收藏夹、将内容发布到 Slack 和 Microsoft 团队等。'],
['Upgrade to Feeder Plus', '升级到 Feeder Plus'],
['Collaborate with your team', '与你的团队协作'],
['Share feeds, manage users in one place and work together.', '共享,在一个地方管理用户并协同工作。'],
['Get started', '开始使用'],
['Want to try it first?', '想先试试嘛?'],
['Feeds', '订阅'],
['Collections', '收集'],
['Sources', '来源'],
['Import & Export', '导入导出'],
];
class ReplaceText {
constructor(i18n, mode = 'equal') {
this.W = typeof unsafeWindow === 'undefined' ? window : unsafeWindow;
this.done = new Set();
this.alert = this.W.alert.bind(this.W);
this.confirm = this.W.confirm.bind(this.W);
this.prompt = this.W.prompt.bind(this.W);
const i18nMap = new Map(i18n);
const i18nArr = i18n.map(value => value[0]);
if (mode === 'regexp') {
this.textReplace = (text) => {
if (i18nMap.has(text))
text = i18nMap.get(text);
else {
const key = i18nArr.find(key => (key instanceof RegExp && text.match(key) !== null));
if (key !== undefined)
text = text.replace(key, i18nMap.get(key));
}
return text;
};
} else if (mode === 'match') {
this.textReplace = (text) => {
const key = i18nArr.find(key => (text.match(key) !== null));
if (key !== undefined)
text = text.replace(key, i18nMap.get(key));
return text;
};
} else {
this.textReplace = (text) => {
if (i18nMap.has(text))
text = i18nMap.get(text);
return text;
};
}
this.replaceAlert();
this.replaceObserver();
}
replaceAlert() {
this.W.alert = (message) => this.alert(this.textReplace(message));
this.W.confirm = (message) => this.confirm(this.textReplace(message));
this.W.prompt = (message, _default) => this.prompt(this.textReplace(message), _default);
}
replaceNode(node, self = false) {
const list = this.getReplaceList(node, self);
for (let index in list) {
list[index].forEach(node => {
if (this.done.has(node[index]))
return;
const newText = this.textReplace(node[index]);
if (node[index] !== newText) {
this.done.add(newText);
node[index] = newText;
}
});
}
}
replaceObserver() {
const bodyObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'attributes' || mutation.type === 'characterData')
this.replaceNode(mutation.target, true);
else if (mutation.type === 'childList') {
mutation.addedNodes.forEach(addedNode => this.replaceNode(addedNode));
}
});
});
document.addEventListener('readystatechange', () => {
bodyObserver.observe(document.body, {
attributes: true,
characterData: true,
childList: true,
subtree: true
});
this.replaceNode(document.body);
}, {
capture: true,
once: true
});
}
getReplaceList(node, self = false) {
const list = {
data: new Set(),
placeholder: new Set(),
title: new Set(),
value: new Set(),
};
const nodeList = self ? [node] : this.nodeForEach(node);
nodeList.forEach(node => {
if (node.parentElement instanceof HTMLScriptElement || node.parentElement instanceof HTMLStyleElement)
return;
if (node instanceof HTMLElement && node.title !== '')
list.title.add(node);
if (node instanceof HTMLInputElement && ['button', 'reset', 'submit'].includes(node.type) && node.value !== '')
list.value.add(node);
else if (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement && node.placeholder !== '')
list.placeholder.add(node);
else if (node instanceof Text)
list.data.add(node);
});
return list;
}
nodeForEach(node) {
const list = [];
list.push(node);
if (node.hasChildNodes())
node.childNodes.forEach(child => list.push(...this.nodeForEach(child)));
return list;
}
}
new ReplaceText(zh_Hans, 'regexp');