Greasy Fork

来自缓存

Greasy Fork is available in English.

Pixiv Tag Translation/Replacement

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

当前为 2016-09-11 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Pixiv Tag Translation/Replacement
// @description Shows translations of tags on Pixiv and prompts for untranslated tags.
// @namespace   http://scripts.chris.charabaruk.com/pixiv.net/~tag-translation
// @author      coldacid
// @include     http://www.pixiv.net/
// @include     http://www.pixiv.net/*
// @include     http://pixiv.net/
// @include     http://pixiv.net/*
// @include     https://www.pixiv.net/
// @include     https://www.pixiv.net/*
// @include     https://pixiv.net/
// @include     https://pixiv.net/*
// @version     1.1
// @grant       none
// ==/UserScript==

var TagsCollection;
{
  const USER_DATA_KEY = 'com.charabaruk.chris.pixiv.net.tag-translation';
  let version = 1;
  let map = null;

  let loadMap = function () {
    var userData = window.localStorage[USER_DATA_KEY];
    if (userData) {
      userData = JSON.parse(userData);
    } else {
      userData = {version: 1};
    }

    version = userData.version || 1;

    var tags = userData.tags || {
      "R-18": null,
      "3D": null
    };
    tags[Symbol.iterator] = function* () { for (var tag in this) yield [tag, this[tag]]; }
    map = new Map(tags);
  };
  let saveMap = function () {
    // so we don't overwrite changes made in other tabs, grab the current data first when saving
    var userData = JSON.parse(window.localStorage[USER_DATA_KEY] || `{version: ${version}, tags: {}}`);
    for (var [k, v] of map.entries()) { userData.tags[k] = v; } // yes, overwrite existing tags when merging

    window.localStorage[USER_DATA_KEY] = JSON.stringify(userData);
  };
  
  window.addEventListener('storage', evt => {
    if (evt.key !== USER_DATA_KEY) { return; }
    
    console.info("Another tab has updated tag translations, merging");
    var tags = JSON.parse(evt.newValue || "{tags: null}").tags;
    if (!tags) { return; }
    
    for(var key of Object.getOwnPropertyNames(tags)) {
      map.set(key, tags[key]); // take remove version over existing one
    }
  }, false);

  TagsCollection = function TagsCollection () {
    loadMap();
  };

  Object.defineProperty(TagsCollection.prototype, 'version', {value: version});

  TagsCollection.prototype.has = function (tag) { return map.has(tag); };
  TagsCollection.prototype.get = function (tag) { return map.get(tag) || tag; };
  TagsCollection.prototype.set = function (tag, translation) {
    if (translation === undefined) {
      if (tag.entries) {
        for (var [key, value] of tag.entries()) {
          map.set(key, value);
        }
      } else if (tag[Symbol.iterator]) {
        for (var [key, value] of tag) {
          map.set(key, value);
        }
      } else if (tag instanceof Object) {
        for (var key of Object.getOwnPropertyNames(tag)) {
          map.set(key, tag[key]);
        }
      } else {
        throw new Error('missing translation');
      }
    } else {
      map.set(tag, translation);
    }

    saveMap();
  };

  TagsCollection.prototype.tags = function* () { for (var entry in map.entries()) yield entry; }
  TagsCollection.prototype.translations = function () {
    var reversed = {};
    reversed[Symbol.iterator] = function* () { for (var key in this) yield [key, this[key]]; }

    for (var [key, value] of map.entries()) {
      reversed[value] = reversed[value] || [];
      reversed[value].push(key);
    }

    return reversed;
  };

  TagsCollection.prototype.translatedAs = function (translation) {
    translation = translation || '';

    var tags = [];
    for (var [key, value] of map.entries()) {
      if ((value || '').toLowerCase() === translation.toLowerCase())
        tags.push(key);
    }
    return tags;
  };
}

function setTagText($element, tag) {
  var originalTag = $element.text(),
      newText = `${tag} (${originalTag})`;
  if ($element[0].nodeType == Node.TEXT_NODE) {
    $element[0].textContent = newText;
  } else {
    $element.text(newText);
  }
}

function GM_main ($) {
  var tags = new TagsCollection();
  window.translatedTags = tags;

  var tagSelectors = [
    'li.tag > a:not([class~="portal"])',
    'div.tag-name',
    'section.favorite-tag > ul.favorite-tags > li > a',
    'nav.breadcrumb > span a[href^="/tags.php?tag="] > span[itemprop="title"]',
    'ul.tagCloud > li > a',
    'ul.tags > li > a:not([class~="tag-icon"])',
    'table.ws_table td.td2 > a[href^="personal_tags.php?tag="]'
  ].join(', ');

  var untranslated = new Map();
  // content page regular tags, home page featured tags, home page favorite tags
  $(tagSelectors)
    .contents()
    .filter((i, n) => n.nodeType == Node.TEXT_NODE) // only get the text nodes within the selected elements
    .each((idx, el) => {
      var $el = $(el),
          tag = $el.text();

      if (/^[\x20-\x7e]*$/.test(tag)) {
        console.log(`"${tag}" only uses ASCII printable characters, skipping`);
        return;
      }

      if (tags.has(tag)) {
        setTagText($el, tags.get(tag));
      } else {
        let elList = untranslated.has(tag) ? untranslated.get(tag) : [];
        elList.push($el);
        untranslated.set(tag, elList);
      }
    });

  if (untranslated.size > 0) {
    var taglist = Array.from(untranslated.keys()).join(', '),
        tagcount = untranslated.size;
    if (window.confirm(`There are ${tagcount} untranslated tags. Want to translate?\n\nTags: ${taglist}`)) {
      var translations = new Map(), i = 1;
      for (var [tag, $els] of untranslated.entries()) {
        // try getting a translated version anyway, just in case it got translated on another tab
        var translated = window.prompt(
          `Translation for: ${tag}\n\nLeave empty to cancel translating, leave as-is to skip [${i++}/${tagcount}]`,
          tags.get(tag));
        if (!translated) { break; }

        // only save if the translation is different from the original tag
        if (tag !== translated) {
          translations.set(tag, translated);
          $els.forEach($el => setTagText($el, translated));
        }
      }
      tags.set(translations);
    }
  }
}

if (typeof jQuery === 'function') {
  console.log(`Using local jQuery, version ${jQuery.fn.jquery}`);
  GM_main(jQuery);
} else {
  console.log('Loading jQuery from Google CDN');
  add_jQuery(GM_main, '1.11.1');
}

function add_jQuery(callbackFn, jqVersion) {
  jqVersion      = jqVersion || "1.11.1";
  var D          = document,
      targ       = D.getElementsByTagName('head')[0] || D.body || D.documentElement,
      scriptNode = D.createElement('script');

  scriptNode.src = `//ajax.googleapis.com/ajax/libs/jquery/${jqVersion}/jquery.min.js`;
  scriptNode.addEventListener('load', function () {
    var scriptNode         = D.createElement('script');
    scriptNode.textContent =
      'var gm_jQuery = jquery.noConflict(true);\n'
      + '(' + callbackFn.toString() + ')(gm_jQuery);';
    targ.appendChild(scriptNode);
  }, false);
  targ.appendChild(scriptNode);
}