Greasy Fork镜像 支持简体中文。

Pixiv Tag Translation/Replacement

Shows translations of tags on Pixiv and prompts for untranslated tags.

目前為 2016-09-11 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Pixiv Tag Translation/Replacement
  3. // @description Shows translations of tags on Pixiv and prompts for untranslated tags.
  4. // @namespace http://scripts.chris.charabaruk.com/pixiv.net/~tag-translation
  5. // @author coldacid
  6. // @include http://www.pixiv.net/
  7. // @include http://www.pixiv.net/*
  8. // @include http://pixiv.net/
  9. // @include http://pixiv.net/*
  10. // @include https://www.pixiv.net/
  11. // @include https://www.pixiv.net/*
  12. // @include https://pixiv.net/
  13. // @include https://pixiv.net/*
  14. // @version 1.2
  15. // @grant none
  16. // ==/UserScript==
  17.  
  18. var TagsCollection;
  19. {
  20. const USER_DATA_KEY = 'com.charabaruk.chris.pixiv.net.tag-translation';
  21. let version = 1;
  22. let map = null;
  23.  
  24. let loadMap = function () {
  25. var userData = window.localStorage[USER_DATA_KEY];
  26. if (userData) {
  27. userData = JSON.parse(userData);
  28. } else {
  29. userData = {version: 1};
  30. }
  31.  
  32. version = userData.version || 1;
  33.  
  34. var tags = userData.tags || {
  35. "R-18": null,
  36. "3D": null
  37. };
  38. tags[Symbol.iterator] = function* () { for (var tag in this) yield [tag, this[tag]]; }
  39. map = new Map(tags);
  40. };
  41. let saveMap = function () {
  42. // so we don't overwrite changes made in other tabs, grab the current data first when saving
  43. var userData = JSON.parse(window.localStorage[USER_DATA_KEY] || `{version: ${version}, tags: {}}`);
  44. for (var [k, v] of map.entries()) { userData.tags[k] = v; } // yes, overwrite existing tags when merging
  45.  
  46. window.localStorage[USER_DATA_KEY] = JSON.stringify(userData);
  47. };
  48. window.addEventListener('storage', evt => {
  49. if (evt.key !== USER_DATA_KEY) { return; }
  50. console.info("Another tab has updated tag translations, merging");
  51. var tags = JSON.parse(evt.newValue || "{tags: null}").tags;
  52. if (!tags) { return; }
  53. for(var key of Object.getOwnPropertyNames(tags)) {
  54. map.set(key, tags[key]); // take remove version over existing one
  55. }
  56. }, false);
  57.  
  58. TagsCollection = function TagsCollection () {
  59. loadMap();
  60. };
  61.  
  62. Object.defineProperty(TagsCollection.prototype, 'version', {value: version});
  63.  
  64. TagsCollection.prototype.has = function (tag) { return map.has(tag); };
  65. TagsCollection.prototype.get = function (tag) { return map.get(tag) || tag; };
  66. TagsCollection.prototype.set = function (tag, translation) {
  67. if (translation === undefined) {
  68. if (tag.entries) {
  69. for (var [key, value] of tag.entries()) {
  70. map.set(key, value);
  71. }
  72. } else if (tag[Symbol.iterator]) {
  73. for (var [key, value] of tag) {
  74. map.set(key, value);
  75. }
  76. } else if (tag instanceof Object) {
  77. for (var key of Object.getOwnPropertyNames(tag)) {
  78. map.set(key, tag[key]);
  79. }
  80. } else {
  81. throw new Error('missing translation');
  82. }
  83. } else {
  84. map.set(tag, translation);
  85. }
  86.  
  87. saveMap();
  88. };
  89. TagsCollection.prototype.delete = function (tag) { map.delete(tag); }
  90.  
  91. TagsCollection.prototype.tags = function* () { for (var entry of map.entries()) yield entry; }
  92. TagsCollection.prototype.translations = function () {
  93. var reversed = {};
  94. reversed[Symbol.iterator] = function* () { for (var key in this) yield [key, this[key]]; }
  95.  
  96. for (var [key, value] of map.entries()) {
  97. reversed[value] = reversed[value] || [];
  98. reversed[value].push(key);
  99. }
  100.  
  101. return reversed;
  102. };
  103.  
  104. TagsCollection.prototype.translatedAs = function (translation) {
  105. translation = translation || '';
  106.  
  107. var tags = [];
  108. for (var [key, value] of map.entries()) {
  109. if ((value || '').toLowerCase() === translation.toLowerCase())
  110. tags.push(key);
  111. }
  112. return tags;
  113. };
  114. }
  115.  
  116. function GM_main ($) {
  117. function setTagText(node, tag) {
  118. if (Array.isArray(node) || node instanceof jQuery) {
  119. node = node[0];
  120. }
  121. var $element = $(node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement),
  122. originalTag = $element.attr('data-tag-translator-original') || $element.text(),
  123. tagLabel = `${tag} (${originalTag})`;
  124. if (node.nodeType === Node.TEXT_NODE) {
  125. node.textContent = tagLabel;
  126. } else {
  127. $element.text(tagLabel);
  128. }
  129.  
  130. $element.attr('data-tag-translator-current', tag);
  131. }
  132.  
  133. var tags = new TagsCollection();
  134. window.translatedTags = tags;
  135.  
  136. var tagSelectors = [
  137. 'li.tag > a:not([class~="tag-icon"]):not([class~="portal"])',
  138. 'div.tag-name',
  139. 'section.favorite-tag > ul.favorite-tags > li > a:not([class~="tag-icon"]):not([class~="portal"])',
  140. 'nav.breadcrumb > span a[href^="/tags.php?tag="] > span[itemprop="title"]',
  141. 'ul.tagCloud > li > a:not([class~="tag-icon"]):not([class~="portal"])',
  142. 'ul.tags > li > a:not([class~="tag-icon"]):not([class~="portal"])',
  143. 'table.ws_table td.td2 > a[href^="personal_tags.php?tag="]',
  144. 'div.bookmark-list-unit ul.tag-cloud > li > span.tag[data-tag]'
  145. ].join(', ');
  146.  
  147. var untranslated = new Map();
  148. // content page regular tags, home page featured tags, home page favorite tags
  149. $(tagSelectors)
  150. .contents()
  151. .filter((i, n) => n.nodeType === Node.TEXT_NODE) // only get the text nodes within the selected elements
  152. .each((i, n) => {
  153. var $node = $(n),
  154. tag = $node.text();
  155.  
  156. // save original tag value, add edit translation button
  157. $(n.nodeType === Node.ELEMENT_NODE ? n : n.parentElement)
  158. .attr('data-tag-translator-original', tag)
  159. .append('<span class="tags tag-translator-added" style="position:relative;"><span class="portal retranslate">j</span></span>');
  160.  
  161. if (tags.has(tag)) {
  162. // if we have a translation for the tag, update the text for it
  163. setTagText($node, tags.get(tag));
  164. } else {
  165. if (/^[\x20-\x7e]*$/.test(tag)) {
  166. // tag is entirely ASCII, so skip it and go onto the next node for processing
  167. console.log(`"${tag}" only uses ASCII printable characters, skipping`);
  168. return;
  169. }
  170.  
  171. // if we don't have a translation and the tag isn't ASCII text, add to the untranslated list
  172. let nodes = untranslated.has(tag) ? untranslated.get(tag) : [];
  173. nodes.push($node);
  174. untranslated.set(tag, nodes);
  175. }
  176. });
  177.  
  178. // prompt for translations
  179. if (untranslated.size > 0) {
  180. var taglist = Array.from(untranslated.keys()).join(', '),
  181. tagcount = untranslated.size;
  182. if (window.confirm(`There are ${tagcount} untranslated tags. Want to translate?\n\nTags: ${taglist}`)) {
  183. var translations = new Map(), i = 1;
  184. for (var [tag, $nodes] of untranslated.entries()) {
  185. // try getting a translated version anyway, just in case it got translated on another tab
  186. var translated = window.prompt(
  187. `Translation for: ${tag}\n\nLeave empty to cancel translating, leave as-is to skip [${i++}/${tagcount}]`,
  188. tags.get(tag));
  189. if (!translated) { break; }
  190.  
  191. // only save if the translation is different from the original tag
  192. if (tag !== translated) {
  193. translations.set(tag, translated);
  194. $nodes.forEach($n => setTagText($n, translated));
  195. }
  196. }
  197. tags.set(translations);
  198. }
  199. }
  200.  
  201. // set up translation editing
  202. $('[data-tag-translator-original] .retranslate').click(function (evt) { // has to be function for proper `this`
  203. evt.stopPropagation();
  204. evt.preventDefault();
  205.  
  206. var $this = $(this),
  207. $parent = $($this.parents('[data-tag-translator-original]')[0]),
  208. tag = $parent.attr('data-tag-translator-original'),
  209. translation = $parent.attr('data-tag-translator-current') || tags.get(tag),
  210. $matching = $(`[data-tag-translator-original="${tag}"]`).contents().filter((i, n) => n.nodeType === Node.TEXT_NODE);
  211.  
  212. var translated = window.prompt(
  213. `Translation for: ${tag}\n\nLeave as-is to cancel, clear text to remove translation`,
  214. translation);
  215. if (translated === translation) {
  216. console.log(`Translation for "${tag}" unchanged`);
  217. return; // nothing to do
  218. } else if (!translated) {
  219. console.log(`Deleting translation for "${tag}"`);
  220. tags.delete(tag);
  221. translated = tag;
  222. }
  223.  
  224. console.log(`Updating translation for "${tag}" from "${translation}" to "${translated}"`)
  225. tags.set(tag, translated);
  226. $matching.each((i, n) => setTagText(n, translated));
  227. });
  228. }
  229.  
  230. if (typeof jQuery === 'function') {
  231. console.log(`Using local jQuery, version ${jQuery.fn.jquery}`);
  232. GM_main(jQuery);
  233. } else {
  234. console.log('Loading jQuery from Google CDN');
  235. add_jQuery(GM_main, '1.11.1');
  236. }
  237.  
  238. function add_jQuery(callbackFn, jqVersion) {
  239. jqVersion = jqVersion || "1.11.1";
  240. var D = document,
  241. targ = D.getElementsByTagName('head')[0] || D.body || D.documentElement,
  242. scriptNode = D.createElement('script');
  243.  
  244. scriptNode.src = `//ajax.googleapis.com/ajax/libs/jquery/${jqVersion}/jquery.min.js`;
  245. scriptNode.addEventListener('load', function () {
  246. var scriptNode = D.createElement('script');
  247. scriptNode.textContent =
  248. 'var gm_jQuery = jquery.noConflict(true);\n'
  249. + '(' + callbackFn.toString() + ')(gm_jQuery);';
  250. targ.appendChild(scriptNode);
  251. }, false);
  252. targ.appendChild(scriptNode);
  253. }

QingJ © 2025

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