Greasy Fork is available in English.

feeder 汉化脚本

feeder.com 汉化脚本

// ==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');