スキップしてメイン コンテンツに移動

独自の日本語化スクリプトのプロトタイプ完成

MylingualでCodecademyの日本語化を行っていたが、Mylingualはシステムメッセージの翻訳ツールでドキュメント翻訳ツールとしては無理があることがわかった。

それならば、と独自で日本語化出来るようスクリプトを書いてみた。

prototype.ja.www.codecademy.com.user.js

// ==UserScript==
// @name            Codecademy for Japanese
// @namespace       Codecademy4Japanese
// @author          Ryo Link
// @include         http://*.codecademy.com/*
// @description     Translates http://www.codecademy.com/ to Japanese. 
// ==/UserScript==

(function(){

// start custom code.

    var translation = [
        {
            xpath:'/html/body/div[3]/div/div/div/section/div[2]/h1',
            original:/Learn to code/,
            newtext:'コードを学ぼう'
        }
        ,
        {
            xpath:'/html/body/div[3]/div/div/div/section/div/div[2]/pre/span[@class="log"]',
            original:/Hey! .*? keyboard\./,
            newtext:'はじめまして、Ryanです。あなたのお名前は何ですか?あなたのお名前を<strong>"Ryan"</strong>のように引用符で括り入力してください。入力できたらキーボードの <strong>Enter</strong> と書かれたエンターキーを押してください。<span style="font-size:small;">注意:引用符「<strong>"</strong>」は必ず半角で入力します。</span>'
        }
        ,
        {
            xpath:'/html/body/div[3]/div/div/div/section/div/div[2]/pre/span[@class="log"]',
            original:/Well done! .*?<code>"Ryan".length<\/code>/,
            newtext:'よろしく!お名前は何文字ですか?あなたのお名前を引用符で括り、そのまま続けて<code class="ace-tm"><span class="ace_line"><span class="ace_punctuation ace_operator">.</span><span class="ace_identifier">length</span></span></code>と入力しエンターキーを押してください。例: <code class="ace-tm"><span class="ace_line"><span class="ace_string">"Ryan"</span><span class="ace_punctuation ace_operator">.</span><span class="ace_identifier">length</span></span></code>'
        }
        ,
        {
            xpath:'/html/body/div[3]/div/div/div/section/div/div[2]/pre/span[@class="log"]',
            original:/Well done! .*? <code class="ace-tm"><span class="ace_line"><span class="ace_string">"Ryan"<\/span><span class="ace_punctuation ace_operator">\.<\/span><span class="ace_identifier">length<\/span><\/span><\/code>/,
            newtext:'よろしく!お名前は何文字ですか?あなたのお名前を引用符で括り、そのまま続けて<code class="ace-tm"><span class="ace_line"><span class="ace_punctuation ace_operator">.</span><span class="ace_identifier">length</span></span></code>と入力しエンターキーを押してください。例: <code class="ace-tm"><span class="ace_line"><span class="ace_string">"Ryan"</span><span class="ace_punctuation ace_operator">.</span><span class="ace_identifier">length</span></span></code>'
        }
    ];

    function do_platypus_script() {
        for(var i in translation){
            var o = translation[i];
            translate(o.xpath, o.original, o.newtext);
        }
    };

    function getElementsByXPath(xpath, node) {
        var node = node || document;
        var doc = node.ownerDocument ? node.ownerDocument : node;
        var nodesSnapshot = doc.evaluate(xpath, node, null,
            XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        var data = [];
        for (var i = 0; i < nodesSnapshot.snapshotLength; i++) {
            data.push(nodesSnapshot.snapshotItem(i));
        }
        return (data.length >= 1) ? data : null;
    }

    function translate(xpath, original, newtext) {
        var elements = getElementsByXPath(xpath, document);
        for(var e in elements){
            do_modify_html_it(window.document, elements[e], original, newtext, null);
        }
    }

    function loadHandler(){
      var handler = function (evt) {
        if (handler.underOperation) {
          return;
        }
        setTimeout(
          function () {
            handler.underOperation = true;
            do_platypus_script();
            handler.underOperation = false;
          },
          1);
      };
      document.addEventListener("DOMNodeInserted", handler, false);
      document.addEventListener("DOMCharacterDataModified", handler, false);
    }

    window.addEventListener("load", loadHandler, false);

// end custom code.

    var gplatypusBundle = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService);
    var mystrings = gplatypusBundle.createBundle("chrome://platypus/locale/platypusCore.properties");
    var platypusplatypuscouldntfi1 = mystrings.GetStringFromName("platypusplatypuscouldntfi1");
    var platypusthisusuallyhappens = mystrings.GetStringFromName("platypusthisusuallyhappens");

    function walk_down(node, func) {
      if (node.nodeType == 1) {
        if (node.tagName != "IMG") func(node);
        if (node.childNodes.length != 0)
          for (var i=0; i<node.childNodes.length; i++)
    walk_down(node.childNodes.item(i),func);
      }
    }
    function make_bw(doc, node) {
      walk_down(node,
                function (node) {
          if (node.tagName != 'A') {
      node.bgcolor = "white";
      node.color = "black";
      node.style.backgroundColor = "white";
      node.style.color = "black";
      node.style.backgroundImage = "";
          }});
    }
    function center_it(doc, node) {
      var center_node = doc.createElement ("CENTER");
      node.parentNode.insertBefore(center_node, node);
      node.parentNode.removeChild(node);  
      center_node.appendChild(node);
      return center_node;
    };
    function erase_it(doc, node) {
      var offset_height = node.offsetHeight;
      var offset_width = node.offsetWidth;
      var replacement_div = doc.createElement ("DIV");
      replacement_div.setAttribute('style',
           "height: "+offset_height+"; width: "+offset_width+";");
      node.parentNode.insertBefore(replacement_div, node);
      node.style.display = "none";
      return replacement_div;
    };
    function smart_remove(doc, node) {
        if (node.parentNode.childNodes.length == 1) {
    smart_remove(doc, node.parentNode);
        } else {
    remove_it(doc, node);
        };
    };
    function remove_it(doc, node) {
      if (doc == null || node == null) return;
      if (!node.parentNode) return;
      node.style.display = "none";
      doc.last_removed_node = node;
    };
    function script_paste(doc, where, what) {
        var new_node = what.cloneNode(true);
        new_node.style.display = "";
        where.parentNode.insertBefore(new_node, where);
    };
    function isolate(doc, node) {
      if (!node.parentNode) return;
      node.parentNode.removeChild(node);
      while (doc.body.childNodes.length > 0) {
        doc.body.removeChild(doc.body.childNodes[0]);
      };
      var replacement_div = doc.createElement ("DIV");
      replacement_div.setAttribute('style',
           "margin: 0 2%; text-align: left");
      replacement_div.appendChild(node);
      doc.body.appendChild(replacement_div);
    };
    function set_style_script(doc, element, new_style) {
        element.setAttribute('style', new_style);
    };
    function modify_single_url(doc, match_re, replace_string, node) {
        if (node.href) {
    node.href = node.href.replace(match_re, replace_string);
        };
    };
    function do_modify_url_it(doc, node, match_re, replace_string, global_flag) {
        match_re = new RegExp(match_re);
        if (global_flag) {
    var allurls = doc.getElementsByTagName('A');
    for(var i = 0, url; url = allurls[i]; i++)
      modify_single_url(doc, match_re, replace_string, url);
        } else {
    modify_single_url(doc, match_re, replace_string, node);
        };
    };
    function do_modify_html_it(doc, element, match_re, replace_string) {
        match_re = new RegExp(match_re);
        if (element.innerHTML) {
            element.innerHTML = element.innerHTML.replace(match_re, replace_string);
        };
    };
    function relax(doc, node) {
      walk_down(node, function (node) {
          node.style.width = 'auto';
          node.style.marginLeft = '0pt';
          node.style.marginRight = '0pt';
          if (node.width) node.width = null; });
    }
    function fix_page_it(doc, node) {
        doc.background = null;
        doc.bgColor = "white";
        if (doc.style) {
          doc.style.backgroundColor = "white";
          doc.style.backgroundImage = "none";
          if (doc.style.color == "white") {
    doc.style.color = "black";
          };
          if (doc.text == "white") {
    doc.text = "black";
          };
        };
        doc.body.background = null;
        doc.body.bgColor = "white";
        if (doc.body.style) {
          doc.body.style.backgroundColor = "white";
          doc.body.style.backgroundImage = "none";
          if (doc.body.style.color == "white") {
    doc.body.style.color = "black";
          };
          if (doc.body.text == "white") {
    doc.body.text = "black";
          };
        };
    };
    function insertAfter(newNode, target) {
        var parent = target.parentNode;
        var refChild = target.nextSibling;
        if(refChild != null)
    parent.insertBefore(newNode, refChild);
        else
    parent.appendChild(newNode);
    };
    function html_insert_it(doc, element, new_html, before, insert_as_block) {
      var new_element;
      if (insert_as_block) {
        new_element = doc.createElement ("DIV");
      } else {
        new_element = doc.createElement ("SPAN");
      };
      new_element.innerHTML = new_html;
      if (before) {
          element.parentNode.insertBefore(new_element, element);
      } else {
          insertAfter(new_element, element);
      };
    };
    function auto_repair_it(doc, node) {
      Dump("In auto_repair_it...");
      var biggest_elem = find_biggest_elem(doc);
      Dump("biggest_elem = "+biggest_elem);
      isolate(doc, biggest_elem);
      Dump("After isolate.");
      relax(doc, biggest_elem);
      Dump("After relax.");
      make_bw(doc, biggest_elem);
      Dump("After make_bw.");
      fix_page_it(doc, biggest_elem);
      Dump("After fix_page_it.");
    };
    function find_biggest_elem(doc) {
      const big_element_limit = 0.25;
      var size_of_doc = doc.documentElement.offsetHeight *
          doc.documentElement.offsetWidth;
      var body = doc.body;
      var size_of_body = body.offsetHeight * body.offsetWidth;
      if (size_of_body < (0.80 * size_of_doc)) {
          size_of_body = size_of_doc;
      };
      var max_size = 0;
      var max_elem = doc;
      /*  
      var allElems = doc("//*",
         doc, null,
         XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
         null);
      Dump("allElems = "+allElems);
      Dump("allElems.snapshotLength = "+allElems.snapshotLength);
      for (var i = 0; i < allElems.snapshotLength; i++) {
        var thisElem = allElems.snapshotItem(i);
      */
        
      var allElems = doc.getElementsByTagName("*");
      Dump("allElems = "+allElems);
      Dump("allElems.snapshotLength = "+allElems.length);
      for (var i = 0; i < allElems.length; i++) {
          var thisElem = allElems[i];
          var thisElem_size = thisElem.offsetHeight * thisElem.offsetWidth;

          if (thisElem_size < size_of_body &&
      thisElem_size > max_size &&
      !contains_big_element(thisElem, size_of_body * big_element_limit)) {
      max_size = thisElem_size;
      max_elem = thisElem;
          };
      };
      Dump("Max elem = "+max_elem.tagName);
      return max_elem;
    };

    function contains_big_element(node, limit) {
        if (node.childNodes.length != 0)
    for (var i=0; i<node.childNodes.length; i++) {
        var child = node.childNodes.item(i);
        var child_size = child.offsetHeight * child.offsetWidth;
        if (child_size > limit) return true;
    };
        return false;
    };

    function platypus_do(win, func_name, o, other, other2, other3) {
        var func = eval(func_name);
        var doc = null;
        if (func == null) return;
        if (!o) {
    Warning(platypusplatypuscouldntfi1+
    func_name+platypusthisusuallyhappens);
        };
        doc = win.document;
        func(doc, o, other, other2, other3);
    };
})();

//.user.js

ソースはあるツールを使用して半自動で作成したのであまりよろしくないがとりあえず。

translationが翻訳テーブルとなる。このように特定要素内で正規表現による置換ができると翻訳の自由度がとても高くなり、極端な話なんでもできてしまう。原文に忠実でなくとも超意訳で日本語化できるはず。ページ毎に翻訳の判断をする必要があるかまだわからない。

このソースの正式版をgithubで公開し、userscripts.orgで配布する形になる予定。githubのソースはフォークして独自の物を作成してもよし、追加変更してプルリクエストもよし。手伝ってくれる人がいれば非常に助かる。

課題としてユーザーにスクリプトの更新をどうさせるか。自動で出来るか、何か良い方法があるか調査中。どなたかコメントを待っています。

スクリプトの更新が確実にできるようになれば、Mylingualのようにデータベース化せずスクリプトの配布だけで日本語化が出来ると見込んでいる。

将来の展望としてはブラウザ上で見ているサイトを直接翻訳可能にする機能拡張の作成、それで作成されたスクリプトを各自がgithubで公開できるようになればいいなぁ。



コメント

このブログの人気の投稿

私はcodecademy.comの日本語翻訳を開始した

http://www.codecademy.com/ が  http://codeyear.com/ を軸に動き出したようだ。CodecademyはJavaScriptの初学はもちろんプログラミングの入門にも最適だ。これを英語圏だけにとどめておくにはあまりにもおしい。 そこで勝手ながら Mylingual を利用して日本語訳をあてることにした。自分の時間が許す限り翻訳していこうと思う。

デスクトップアプリの一つの形。 Mozilla Labs >> Chromeless Browser

動画:デスクトップを消し去るウェブブラウザ Webian Shell、Mozilla Chromeless ベース -- Engadget Japanese : "話が複雑になってきましたが、要はウェブアプリ時代を迎え、従来のデスクトップアプリを一掃するハイパーシンプルなフルスクリーンブラウザがリリースされたということ。" 古い記事だが、その当時自分は記事を鵜呑みにしてフルスクリーンブラウザが出来たんだ、くらいにしか思っていなかった。しかしMacGap、Fluidなどweb開発技術でのデスクトップアプリ作成を調べていくうちに、この記事が表面上のことにしか触れていない事がわかった。Web ShellとはChromelessのリファレンス実装でもあったのだ。 ではChromelessとは何か? ChromelessとはXULRunnerアプリをhtml、javascript、cssで書けるフレームワークだ。

XcodeでWebKitアプリ。

昨日のエントリー「 MacGapとUKI。 」が気になってソースを追いかけた。なかなか良かった。もうMacGapを使う事になるだろうけど、独自でWebKitフレームワークを組み込んだアプリを作る時のメモとして簡単な手順をブログに残しておく。