Greasy Fork is available in English.

feeder 汉化脚本

feeder.com 汉化脚本

  1. // ==UserScript==
  2. // @name feeder 汉化脚本
  3. // @namespace https://github.com/98zi/MyTampermonkey
  4. // @version 0.0.2
  5. // @author 98zi
  6. // @description feeder.com 汉化脚本
  7. // @include *://feeder.co/*
  8. // @license MIT
  9. // @icon https://feeder.co/favicon.ico
  10. // @grant none
  11. // @run-at document-start
  12. // ==/UserScript==
  13.  
  14. const zh_Hans = [
  15. ['All feeds', '全部订阅'],
  16. ['Unread', '未读'],
  17. ['Starred', '星标'],
  18. ['Home', '首页'],
  19. ['Dashboard', '图表'],
  20. ['Rules', '规则'],
  21. ['Team', '团队'],
  22. ['Library', '列表'],
  23. ['Account', '账户'],
  24. ['Help', '帮助'],
  25. ['Collapse', '收起'],
  26. ['Reload feeds', '重新加载源数据'],
  27. ['Open all unread...', '打开所有未读'],
  28. ['Mark all as read', '全部标为已读'],
  29. ['Mark feed as read', '将 feed 标为已读'],
  30. ['Export posts', '导出帖子'],
  31. ['Go to page', '跳转到网站'],
  32. ['Edit feed', '编辑 feed'],
  33. ['Delete feed', '删除 feed'],
  34. ['Filters', '过滤'],
  35. ['Display', '展示'],
  36. ['Order', '排序'],
  37. ['Newest first', '最新'],
  38. ['Oldest first', '最旧'],
  39. ['Realtime', '实时'],
  40. ['Auto-update', '自动更新'],
  41. ['Content style', '风格'],
  42. ['Timestamp', '时间戳'],
  43. ['Previous post', '上一篇'],
  44. ['Next post', '下一篇'],
  45. ['Share post', '分享'],
  46. ['Mark as unread', '标记为已读'],
  47. ['Star post', '收藏'],
  48. ['Toggle collections', '切换集合'],
  49. ['Simple', '简单'],
  50. ['Full', '源站'],
  51. ['Free', '免费'],
  52. ['My account', '我的账户'],
  53. ['Settings', '设置'],
  54. ['Share', '分享'],
  55. ['Add to collections', '添加到收藏夹'],
  56. ['Appearance', '外观'],
  57. ['Log out', '退出'],
  58. ['Change display mode', '更改显示模式'],
  59. ['Minimal', '极小'],
  60. ['Reader', '读者'],
  61. ['3-pane', '3 窗格'],
  62. ['Reader', '读者'],
  63. ['Two panes with collapsing folders', '两个包含折叠文件夹的窗格'],
  64. ['Read posts inline for fast consuming', '内联阅读帖子,快速查看'],
  65. ['The entire hierarchy for the best overview', '最佳预览的层次结构'],
  66. ['Change theme', '更改主题'],
  67. ['Light', '明亮'],
  68. ['Dark', '暗黑'],
  69. ['Holiday', '假期'],
  70. ['Orange', '橙色'],
  71. ['Holiday', '假期'],
  72. ['Sand', '沙滩'],
  73. ['Grey', '灰色'],
  74. ['Follow OS setting', '跟随操作系统设置'],
  75. ['Reload', '重新加载'],
  76. ['Expanded', '放大'],
  77. ['Collapsed', '缩小'],
  78. ['Column width', '全宽'],
  79. ['Notifications', '通知'],
  80. ['Sound', '声音'],
  81. ['iOS/Android', 'iOS/Android'],
  82. ['Get Feeder Plus', '获取 Feeder Plus'],
  83. ['Toggle all feeds', '选择全部'],
  84. ['Email summaries', '邮件摘要'],
  85. ['Connections', '连接'],
  86. ['Advanced', '高级'],
  87. ['Global', '全球'],
  88. ['Ask before marking many as read', '在标记已读前询问'],
  89. ['Track unread posts -', '跟踪未读帖子'],
  90. ['When OFF stops marking incoming posts as unread', '当 OFF 时,停止将收到的帖子标记为未读'],
  91. ['Notify me when my feeds stop working', '当我的 Feed 停止工作时通知我'],
  92. ['Connected', '连接'],
  93. ['Receive e-mail summaries', '接受邮件订阅'],
  94. ['Get a periodical with the latest posts from your feeds. Daily, weekly or monthly. You decide.', '每天、每周或每月获取包含源中最新帖子的期刊。'],
  95. ['Enable e-mail summaries', '开始邮箱订阅'],
  96. ['Plan', '计划'],
  97. ['Email and password', '邮箱和密码'],
  98. ['Invoices', '发票'],
  99. ['Billing address', '账单'],
  100. ['Your plan', '你的计划'],
  101. ['Change plan', '更改计划'],
  102. ['Payment details', '付款明细'],
  103. ['No payment method', '没有付款计划'],
  104. ['Upgrade for more', '升级获取更多'],
  105. ['Change email', '修改邮箱'],
  106. ['Current email address is', '当前邮箱地址为'],
  107. ['New email', '新邮箱'],
  108. ['Repeat new email', '重复新邮箱'],
  109. ['Update email', '更新邮箱'],
  110. ['Change password', '修改密码'],
  111. ['Current password', '当前密码'],
  112. ['New password', '新密码'],
  113. ['Update password', '更新密码'],
  114. ['Delete your account', '删除你的账户'],
  115. ['All your data will be deleted, and there will be no way to retrieve them.', '您的所有数据都将被删除,并且无法检索它们。'],
  116. ['Current email address', '当前邮箱'],
  117. ['Remove your account', '确认删除账户'],
  118. ['Invoice date', '发票日期'],
  119. ['VAT', '增值税'],
  120. ['Total', '总计'],
  121. ['You have no invoices', '您没有发票'],
  122. ['Once your first payment is generated it will be shown here', '如果你付款成功,它将会显示在这里。'],
  123. ['Your billing address', '你的账单邮寄地址'],
  124. ['This information will be on your invoices.', '此信息将出现在您的发票上'],
  125. ['Your name', '你的姓名'],
  126. ['Company name', '公司名称'],
  127. ['Address', '地址'],
  128. ['Address (line 2)', '地址(第二行)'],
  129. ['Zip code', '邮政编码'],
  130. ['VAT Number', '增值税号'],
  131. ['Country', '国家'],
  132. ['Additional information', '其他信息'],
  133. ['Update billing address', '更新账单邮寄地址'],
  134. ['Help center', '帮助中心'],
  135. ["What's new?", "最新新闻"],
  136. ['Keyboard navigation', '键盘导航'],
  137. ['Contact support', '联系支持人员'],
  138. ['Downloads', '下载安装'],
  139. ['Automate your workflow with Rules', '使用规则自动执行工作流程'],
  140. ['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 团队等。'],
  141. ['Upgrade to Feeder Plus', '升级到 Feeder Plus'],
  142. ['Collaborate with your team', '与你的团队协作'],
  143. ['Share feeds, manage users in one place and work together.', '共享,在一个地方管理用户并协同工作。'],
  144. ['Get started', '开始使用'],
  145. ['Want to try it first?', '想先试试嘛?'],
  146. ['Feeds', '订阅'],
  147. ['Collections', '收集'],
  148. ['Sources', '来源'],
  149. ['Import & Export', '导入导出'],
  150. ];
  151.  
  152. class ReplaceText {
  153. constructor(i18n, mode = 'equal') {
  154. this.W = typeof unsafeWindow === 'undefined' ? window : unsafeWindow;
  155. this.done = new Set();
  156. this.alert = this.W.alert.bind(this.W);
  157. this.confirm = this.W.confirm.bind(this.W);
  158. this.prompt = this.W.prompt.bind(this.W);
  159. const i18nMap = new Map(i18n);
  160. const i18nArr = i18n.map(value => value[0]);
  161. if (mode === 'regexp') {
  162. this.textReplace = (text) => {
  163. if (i18nMap.has(text))
  164. text = i18nMap.get(text);
  165. else {
  166. const key = i18nArr.find(key => (key instanceof RegExp && text.match(key) !== null));
  167. if (key !== undefined)
  168. text = text.replace(key, i18nMap.get(key));
  169. }
  170. return text;
  171. };
  172. } else if (mode === 'match') {
  173. this.textReplace = (text) => {
  174. const key = i18nArr.find(key => (text.match(key) !== null));
  175. if (key !== undefined)
  176. text = text.replace(key, i18nMap.get(key));
  177. return text;
  178. };
  179. } else {
  180. this.textReplace = (text) => {
  181. if (i18nMap.has(text))
  182. text = i18nMap.get(text);
  183. return text;
  184. };
  185. }
  186. this.replaceAlert();
  187. this.replaceObserver();
  188. }
  189. replaceAlert() {
  190. this.W.alert = (message) => this.alert(this.textReplace(message));
  191. this.W.confirm = (message) => this.confirm(this.textReplace(message));
  192. this.W.prompt = (message, _default) => this.prompt(this.textReplace(message), _default);
  193. }
  194. replaceNode(node, self = false) {
  195. const list = this.getReplaceList(node, self);
  196. for (let index in list) {
  197. list[index].forEach(node => {
  198. if (this.done.has(node[index]))
  199. return;
  200. const newText = this.textReplace(node[index]);
  201. if (node[index] !== newText) {
  202. this.done.add(newText);
  203. node[index] = newText;
  204. }
  205. });
  206. }
  207. }
  208. replaceObserver() {
  209. const bodyObserver = new MutationObserver(mutations => {
  210. mutations.forEach(mutation => {
  211. if (mutation.type === 'attributes' || mutation.type === 'characterData')
  212. this.replaceNode(mutation.target, true);
  213. else if (mutation.type === 'childList') {
  214. mutation.addedNodes.forEach(addedNode => this.replaceNode(addedNode));
  215. }
  216. });
  217. });
  218. document.addEventListener('readystatechange', () => {
  219. bodyObserver.observe(document.body, {
  220. attributes: true,
  221. characterData: true,
  222. childList: true,
  223. subtree: true
  224. });
  225. this.replaceNode(document.body);
  226. }, {
  227. capture: true,
  228. once: true
  229. });
  230. }
  231. getReplaceList(node, self = false) {
  232. const list = {
  233. data: new Set(),
  234. placeholder: new Set(),
  235. title: new Set(),
  236. value: new Set(),
  237. };
  238. const nodeList = self ? [node] : this.nodeForEach(node);
  239. nodeList.forEach(node => {
  240. if (node.parentElement instanceof HTMLScriptElement || node.parentElement instanceof HTMLStyleElement)
  241. return;
  242. if (node instanceof HTMLElement && node.title !== '')
  243. list.title.add(node);
  244. if (node instanceof HTMLInputElement && ['button', 'reset', 'submit'].includes(node.type) && node.value !== '')
  245. list.value.add(node);
  246. else if (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement && node.placeholder !== '')
  247. list.placeholder.add(node);
  248. else if (node instanceof Text)
  249. list.data.add(node);
  250. });
  251. return list;
  252. }
  253. nodeForEach(node) {
  254. const list = [];
  255. list.push(node);
  256. if (node.hasChildNodes())
  257. node.childNodes.forEach(child => list.push(...this.nodeForEach(child)));
  258. return list;
  259. }
  260. }
  261.  
  262. new ReplaceText(zh_Hans, 'regexp');