GitHub汉化插件

GitHub翻译插件

安装此脚本?
作者推荐脚本

您可能也喜欢爱发电链接自动跳转

安装此脚本
  1. // ==UserScript==
  2. // @name GitHub Internationalization
  3. // @name:zh-CN GitHub汉化插件
  4. // @namespace https://github.com/xyz8848/GitHub-i18n-Plugin
  5. // @namespace https://gf.qytechs.cn/zh-CN/scripts/448667-github-internationalization
  6. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAACEUExURUxpcRgWFhsYGBgWFhcWFh8WFhoYGBgWFiUlJRcVFRkWFhgVFRgWFhgVFRsWFhgWFigeHhkWFv////////////r6+h4eHv///xcVFfLx8SMhIUNCQpSTk/r6+jY0NCknJ97e3ru7u+fn51BOTsPCwqGgoISDg6empmpoaK2srNDQ0FhXV3eXcCcAAAAXdFJOUwCBIZXMGP70BuRH2Ze/LpIMUunHkpQR34sfygAAAVpJREFUOMt1U+magjAMDAVb5BDU3W25b9T1/d9vaYpQKDs/rF9nSNJkArDA9ezQZ8wPbc8FE6eAiQUsOO1o19JolFibKCdHGHC0IJezOMD5snx/yE+KOYYr42fPSufSZyazqDoseTPw4lGJNOu6LBXVUPBG3lqYAOv/5ZwnNUfUifzBt8gkgfgINmjxOpgqUA147QWNaocLniqq3QsSVbQHNp45N/BAwoYQz9oUJEiE4GMGfoBSMj5gjeWRIMMqleD/CAzUHFqTLyjOA5zjNnwa4UCEZ2YK3khEcBXHjVBtEFeIZ6+NxYbPqWp1DLKV42t6Ujn2ydyiPi9nX0TTNAkVVZ/gozsl6FbrktkwaVvL2TRK0C8Ca7Hck7f5OBT6FFbLATkL2ugV0tm0RLM9fedDvhWstl8Wp9AFDjFX7yOY/lJrv8AkYuz7fuP8dv9izCYH+x3/LBnj9fYPBTpJDNzX+7cAAAAASUVORK5CYII=
  7. // @supportURL https://github.com/xyz8848/GitHub-i18n-Plugin/issues
  8. // @version 1.1.0
  9. // @description Translate GitHub
  10. // @description:zh GitHub翻译插件
  11. // @description:zh-CN GitHub翻译插件
  12. // @author xyz8848
  13. // @match https://github.com/*
  14. // @match https://gist.github.com/*
  15. // @grant GM_xmlhttpRequest
  16. // @grant GM_getResourceText
  17. // @resource zh-CN https://gitee.com/xyz8848/GitHub-i18n-Plugin/raw/main/langs/zh-CN.json
  18. // @require https://cdn.staticfile.org/timeago.js/4.0.2/timeago.min.js
  19. // @require https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js
  20. // @connect transmart.qq.com
  21. // ==/UserScript==
  22.  
  23. (function() {
  24. 'use strict';
  25.  
  26. const SUPPORT_LANG = ["zh-CN"];
  27. const lang = (navigator.language || navigator.userLanguage);
  28. const locales = getLocales(lang)
  29.  
  30. translateByCssSelector();
  31. translateTime();
  32. traverseElement(document.body);
  33. watchUpdate();
  34.  
  35. // 翻译描述
  36. if(window.location.pathname.split('/').length == 3) {
  37. translateDesc(".repository-content .f4"); //仓库简介翻译
  38. translateDesc(".gist-content [itemprop='about']"); // Gist 简介翻译
  39. }
  40.  
  41.  
  42. function getLocales(lang) {
  43. if(lang.startsWith("zh")) { // zh zh-TW --> zh-CN
  44. lang = "zh-CN";
  45. }
  46. if(SUPPORT_LANG.includes(lang)) {
  47. return JSON.parse(GM_getResourceText(lang));
  48. }
  49. return {
  50. css: [],
  51. dict: {}
  52. };
  53. }
  54.  
  55. function translateRelativeTimeEl(el) {
  56. if(!$(el).attr('translated')) {
  57. const datetime = $(el).attr('datetime');
  58. $(el).attr('translated', true);
  59. let humanTime = timeago.format(datetime, lang.replace('-', '_'));
  60. el.shadowRoot.textContent = humanTime;
  61. }
  62. }
  63.  
  64. function translateElement(el) {
  65. // Get the text field name
  66. let k;
  67. if(el.tagName === "INPUT") {
  68. if (el.type === 'button' || el.type === 'submit') {
  69. k = 'value';
  70. } else {
  71. k = 'placeholder';
  72. }
  73. } else {
  74. k = 'data';
  75. }
  76.  
  77. if (isNaN(el[k])){
  78. const txtSrc = el[k].trim();
  79. const key = txtSrc.toLowerCase()
  80. .replace(/\xa0/g, ' ') // replace ' '
  81. .replace(/\s{2,}/g, ' ');
  82. if (locales.dict[key]) {
  83. el[k] = el[k].replace(txtSrc, locales.dict[key])
  84. }
  85. }
  86. translateElementAriaLabel(el)
  87. }
  88.  
  89. function translateElementAriaLabel(el) {
  90. if (el.ariaLabel) {
  91. const k = 'ariaLabel'
  92. const txtSrc = el[k].trim();
  93. const key = txtSrc.toLowerCase()
  94. .replace(/\xa0/g, ' ') // replace ' '
  95. .replace(/\s{2,}/g, ' ');
  96. if (locales.dict[key]) {
  97. el[k] = el[k].replace(txtSrc, locales.dict[key])
  98. }
  99. }
  100. }
  101.  
  102. function shouldTranslateEl(el) {
  103. const blockIds = [
  104. "readme",
  105. "file-name-editor-breadcrumb", "StickyHeader" // fix repo详情页文件路径breadcrumb
  106. ];
  107. const blockClass = [
  108. "CodeMirror",
  109. "js-navigation-container", // 过滤文件目录
  110. "blob-code",
  111. "topic-tag", // 过滤标签,
  112. // "text-normal", // 过滤repo name, 复现:https://github.com/search?q=explore
  113. "repo-list",//过滤搜索结果项目,解决"text-normal"导致的有些文字不翻译的问题,搜索结果以后可以考虑单独翻译
  114. "js-path-segment","final-path", //过滤目录,文件位置栏
  115. "markdown-body", // 过滤wiki页面,
  116. "search-input-container", //搜索框
  117. "search-match", //fix搜索结果页,repo name被翻译
  118. "cm-editor", //代码编辑框
  119. "PRIVATE_TreeView-item", // 文件树
  120. "repo", // 项目名称
  121. ];
  122. const blockTags = ["CODE", "SCRIPT", "LINK", "IMG", "svg", "TABLE", "ARTICLE", "PRE"];
  123. const blockItemprops = ["name"];
  124.  
  125. if (blockTags.includes(el.tagName)) {
  126. return false;
  127. }
  128.  
  129. if (el.id && blockIds.includes(el.id)) {
  130. return false;
  131. }
  132.  
  133. if (el.classList) {
  134. for (let clazz of blockClass) {
  135. if (el.classList.contains(clazz)) {
  136. return false;
  137. }
  138. }
  139. }
  140.  
  141. if (el.getAttribute) {
  142. let itemprops = el.getAttribute("itemprop");
  143. if (itemprops) {
  144. itemprops = itemprops.split(" ");
  145. for (let itemprop of itemprops) {
  146. if (blockItemprops.includes(itemprop)) {
  147. return false;
  148. }
  149. }
  150. }
  151. }
  152.  
  153. return true;
  154. }
  155.  
  156. function traverseElement(el) {
  157. translateElementAriaLabel(el)
  158. if (!shouldTranslateEl(el)) {
  159. return
  160. }
  161.  
  162. if (el.childNodes.length === 0) {
  163. if (el.nodeType === Node.TEXT_NODE) {
  164. translateElement(el);
  165. return;
  166. }
  167. else if(el.nodeType === Node.ELEMENT_NODE) {
  168. if (el.tagName === "INPUT") {
  169. translateElement(el);
  170. return;
  171. }
  172. }
  173. }
  174.  
  175. for (const child of el.childNodes) {
  176. if (child.nodeType === Node.TEXT_NODE) {
  177. translateElement(child);
  178. }
  179. else if(child.nodeType === Node.ELEMENT_NODE) {
  180. if (child.tagName === "INPUT") {
  181. translateElement(child);
  182. } else {
  183. traverseElement(child);
  184. }
  185. } else {
  186. // pass
  187. }
  188. }
  189. }
  190.  
  191. function watchUpdate() {
  192. const m = window.MutationObserver || window.WebKitMutationObserver;
  193. const observer = new m(function (mutations, observer) {
  194. var reTrans = false;
  195. for(let mutationRecord of mutations) {
  196. if (mutationRecord.addedNodes || mutationRecord.type === 'attributes') {
  197. reTrans = true;
  198. // traverseElement(mutationRecord.target);
  199. }
  200. }
  201. if(reTrans) {
  202. traverseElement(document.body);
  203. }
  204. });
  205.  
  206. observer.observe(document.body, {
  207. subtree: true,
  208. characterData: true,
  209. childList: true,
  210. attributeFilter: ['value', 'placeholder', 'aria-label', 'data', 'data-confirm'], // 仅观察特定属性变化(试验测试阶段,有问题再恢复)
  211. });
  212. }
  213.  
  214. // translate "about"
  215. function translateDesc(el) {
  216. $(el).append("<br/>");
  217. $(el).append("<span id='translate-me' style='font-size: small; display:inline-block; padding: 3px 5px; background-color: #F6F8FA; border-radius: 3px'><a href='#' style='color:rgb(27, 149, 224);font-size: small'><button>翻译</button></a></span>");
  218. $("#translate-me").click(function() {
  219. // get description text
  220. const desc = $(el)
  221. .clone()
  222. .children()
  223. .remove()
  224. .end()
  225. .text()
  226. .trim();
  227.  
  228. if(!desc) {
  229. return;
  230. }
  231.  
  232. let lang = (navigator.userLanguage || navigator.language).toLowerCase();
  233. let data_json = {
  234. header: {
  235. fn: "auto_translation"
  236. },
  237. type: "plain",
  238. source: {
  239. text_list: [
  240. desc
  241. ]
  242. },
  243. target: {
  244. lang: lang == "zh-cn" ? "zh" : lang
  245. }
  246. }
  247. GM_xmlhttpRequest({
  248. method: "POST",
  249. url: "https://transmart.qq.com/api/imt",
  250. header: {
  251. "content-type": "application/json"
  252. },
  253. responseType: "json",
  254. data: JSON.stringify(data_json),
  255. onload: function(res) {
  256. const json = JSON.parse(res.responseText)
  257. if (res.status === 200 && json?.header?.ret_code == "succ") {
  258. $("#translate-me").hide();
  259. const text = json.auto_translation.join(" ");
  260. $(el).append("<span style='font-size: small; display:inline-block; padding: 3px 5px; background-color: #F6F8FA; border-radius: 3px'>" + text + "</span>");
  261. } else {
  262. alert("翻译失败");
  263. }
  264. }
  265. });
  266. });
  267. }
  268.  
  269. function translateByCssSelector() {
  270. if(locales.css) {
  271. for(var css of locales.css) {
  272. if($(css.selector).length > 0) {
  273. if(css.key === '!html') {
  274. $(css.selector).html(css.replacement);
  275. } else {
  276. $(css.selector).attr(css.key, css.replacement);
  277. }
  278. }
  279. }
  280. }
  281. }
  282.  
  283. function translateTime() {
  284. $("relative-time").each(function() {
  285. translateRelativeTimeEl(this);
  286. })
  287. }
  288. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址