var MYLINGUAL = {
VERSION: "0.7.1",
UID: "kazuho-japanize2@labs.cybozu.co.jp",
getPromptService: function () {
return Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
},
notifyUser: function (message) {
this.getPromptService().alert(
window, this._s('prompt.title'), message);
},
getLang: function () {
return this.getPreference('lang', this._s('defaults.lang'));
},
setLang: function (l) {
this.setPreference('lang', l);
},
getStoreBaseDir: function () {
try {
var profileDir =
Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("ProfD", Components.interfaces.nsILocalFile);
// return the extension directory
var dir = profileDir.clone();
dir.append("extensions");
if (dir.exists() && dir.isDirectory()) {
dir.append(this.UID);
if (dir.exists() && dir.isDirectory()) {
dir.append("data");
return dir;
}
}
// if failed, create our own directory below profile dir
dir = profileDir.clone();
dir.append("mylingual"); // only used by developers
if (! dir.exists()) {
dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
}
return dir;
} catch (e) {
alert("Japanize: " + e.toString());
}
},
getStoreDir: function () {
try {
var dir = this.getStoreBaseDir();
dir.append(this.getLang());
if (! dir.exists()) {
dir.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0755);
}
return dir;
} catch (e) {
alert("Mylingual: " + e.toString());
}
},
saveToStore: function (file, text) {
text = "\ufeff" + text.toString();
var conv =
Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
.getService(Components.interfaces.nsIScriptableUnicodeConverter);
conv.charset = "UTF-8";
var temppath = this.getStoreDir();
temppath.append("t" + Math.floor(Math.random() * 10000000) + ".tmp");
var os = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(Components.interfaces.nsIFileOutputStream);
os.init(temppath, 0x2a, 0644, -1);
for (var offset = 0; offset < text.length; offset += 1024) {
var data =
String.fromCharCode.apply(
null,
conv.convertToByteArray(
text.substring(offset, offset + 1024),
new Number(0)));
os.write(data, data.length);
}
os.close();
try {
temppath.moveTo(this.getStoreDir(), file);
} catch (e) {
try {
temppath.remove(false);
} catch (e2) {
}
throw e;
}
},
getStoreURI: function (file) {
var path = this.getStoreDir();
path.append(file);
return Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService)
.newFileURI(path);
},
getPreferenceService: function () {
return Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService)
.getBranch("");
},
getPreference: function (name, defaultValue) {
var value = null;
try {
value =
this.getPreferenceService().getCharPref(
"extensions.japanize." + name);
} catch (e) {
}
if (value == null) {
value = defaultValue;
}
return value;
},
setPreference: function (name, value) {
this.getPreferenceService().setCharPref(
"extensions.japanize." + name, value.toString());
},
debugAlert: function () {
if (! this.getPreference('debug', '')) {
return;
}
alert.apply(null, arguments);
},
PREF_UPDATEMODE: "updatemode",
UPDATEMODE_PERIODICALLY: "periodically",
UPDATEMODE_EVERYTIME: "everytime",
getUpdateMode: function () {
return this.getPreference(
this.PREF_UPDATEMODE, this.UPDATEMODE_PERIODICALLY);
},
setUpdateMode: function (newMode) {
this.setPreference(this.PREF_UPDATEMODE, newMode);
},
getBaseURL: function () {
return this.getPreference("baseURL", "http://japanize.mylingual.net/");
},
normalizeHost: function (host) {
return host.toString().toLowerCase().replace(/^www\./, "");
},
buildTranslationURL: function (host) {
var url =
this.getBaseURL() + "data/" + this.normalizeHost(host)
+ "/current.txt";
return url;
},
loadLocalizationData: function (doc) {
doc.__MYLINGUAL_DONE = true;
var browser = this.findBrowser(doc);
if (! this.getMode()) {
this.updateStatus(browser, "");
return;
}
if (doc.location.protocol != "http:") {
this.updateStatus(browser, "");
return;
}
if (this.getUpdateMode() == this.UPDATEMODE_PERIODICALLY
&& typeof this.localTranslationTable != "undefined") {
if (this.localTranslationTable["$builddate"]
== this.getPreference("$builddate", -1)) {
setTimeout(
function () {
MYLINGUAL.localizeWithLocalData(doc, browser);
},
1);
return;
}
this.localTranslationTable = undefined;
}
if (this.getUpdateMode() == this.UPDATEMODE_EVERYTIME) {
try {
var xhr = new XMLHttpRequest();
xhr.open(
"get",
this.buildTranslationURL(doc.location.host),
true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
MYLINGUAL.localizeWithData(
MYLINGUAL.parseJSON(xhr.responseText),
doc,
browser);
}
};
xhr.send(null);
} catch (e) {
alert("Japanize: " + e.toString());
}
} else {
try {
var xhr = new XMLHttpRequest();
xhr.open(
"get",
this.getStoreURI("all.txt").spec,
true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
var json = MYLINGUAL.parseJSON(xhr.responseText);
MYLINGUAL.localTranslationTable = json;
MYLINGUAL.localizeWithLocalData(doc, browser);
}
};
xhr.send(null);
} catch (e) {
alert("Japanize: " + e.toString());
}
}
},
localizeWithLocalData: function (doc, browser) {
var table;
if (typeof this.localTranslationTable == 'object') {
table = this.localTranslationTable[
MYLINGUAL.normalizeHost(doc.location.host)];
if (typeof table == 'undefined'
&& doc.location.host.match(/^[^\.]*\./)) {
table = this.localTranslationTable['*.' + RegExp.rightContext];
}
}
this.localizeWithData(table, doc, browser);
},
canTranslateHost: function (host) {
var list = this.getPreference("hosts", "").split(",");
if (list.length == 0) {
return true;
}
for (var i = 0; i < list.length; i++) {
var flag = list[i].charAt(0);
var pat = MYLINGUAL._gsub(
list[i].substring(1),
/[^A-Za-z0-9\-]/g,
function (m) {
return m == '*' ? '.*' : '\\' + m;
});
pat = new RegExp(pat);
if (typeof pat != 'undefined' && host.match(new RegExp(pat))) {
if (flag == '-') {
return false;
} else if (flag == '+') {
return true;
}
}
}
return true;
},
localizeWithData: function (json, doc, browser) {
this.updateStatus(browser, "");
// do nothing unless translation is available
if (typeof json != "object") {
return;
}
// check preferences
if (! this.canTranslateHost(doc.location.host)) {
this.updateStatus(browser, this._s('status.original'));
return;
}
// convert json to internal representation
var mappings = {
text: {},
re: []
};
this.initCommandMappings(mappings);
for (var n in json) {
if (n.charAt(0) == '$') {
this.compileCommand(mappings, n, json[n]);
} else if (n.match(/^\/(\^.*\$)\/$/)) {
var v = json[n];
try {
n = new RegExp(RegExp.$1);
} catch (e) {
this.debugAlert(
this._f('mylingual.error.recompile', [ n ]));
continue;
}
mappings.re.push([ n, v ]);
} else {
mappings.text[n] = json[n];
}
}
this.postProcessCommandMappings(mappings);
// check url patterns
if (this.ifSkipURL(mappings, doc.location)) {
this.updateStatus(browser, this._s('status.bannedpage'));
return;
}
// setup logger
var log = ! ! this.getPreference("log", "");
if (log && typeof FireBug == 'undefined' && ! this.noFireBugAlert) {
alert("Japanize: Install FireBug to view translation logs.");
this.noFireBugAlert = true;
log = false;
}
if (log) {
log = function (s) {
FireBug.console.log(s);
};
log.log = true;
} else {
log = function () {
};
log.log = false;
}
// convert
log("Japanize: translating: " + doc.location);
this.localizeElement(doc.body, mappings, true);
this.updateStatus(browser, this._s('status.translated'));
// build handler for handilng DHTML modifs.
var handler = function (evt) {
if (handler.underOperation) {
return;
}
if (log.log) {
var msg = (function (t) {
if (t.id) {
return "id='" + t.id + "'";
} else if (t.className) {
return "class='" + t.className + "'";
} else if (t.parentNode) {
return arguments.callee(t.parentNode);
} else if (t.nodeType == 9) {
return "no identifier at root";
} else {
return "not within document";
}
}).call(null, evt.target);
msg += (function (t) {
while (typeof t == 'object' && t.nodeType != 9) {
t = t.parentNode;
}
if (! t) {
return '';
}
return ", " + t.location;
}).call(null, evt.target);
log("Japanize: " + msg);
}
setTimeout(
function () {
handler.underOperation = true;
MYLINGUAL.localizeElement(
evt.target,
mappings,
MYLINGUAL.getElementTranslationMode(
mappings, doc.body, evt.target.parentNode));
handler.underOperation = false;
},
1);
};
if (doc.addEventListener
&& ! (navigator.userAgent.toString().match(/\sAppleWebKit\/([0-9]+)/i) && RegExp.$1 < 522)) {
doc.addEventListener("DOMNodeInserted", handler, false);
doc.addEventListener("DOMCharacterDataModified", handler, false);
doc.addEventListener(
"DOMAttrModified",
function (evt) {
if (evt.attrName == 'style') {
var iframes = evt.target.getElementsByTagName('iframe');
for (var i = 0; i < iframes.length; i++) {
var doc = iframes[i].contentDocument;
if (! doc.__MYLINGUAL_DONE) {
MYLINGUAL.loadLocalizationData(doc);
}
}
} else if (evt.target.tagName == 'INPUT'
|| evt.target.tagName == 'OPTION') {
handler(evt);
}
},
false);
}
},
translateText: function (orig, mappings) {
// direct match
if (typeof mappings.text[orig] != 'undefined') {
return mappings.text[orig];
}
// match (while taking care of surrounding spaces)
if (orig.match(/^([ \r\n\t\xa0]*)(.+?)([ \r\n\t\xa0]*)$/)
&& (RegExp.$1 != '' || RegExp.$3 != '')) {
if (typeof mappings.text[RegExp.$2] != 'undefined') {
return RegExp.$1 + mappings.text[RegExp.$2] + RegExp.$3;
}
}
// regexp
for (var i = 0; i < mappings.re.length; i++) {
var m = null;
if (m = orig.match(mappings.re[i][0])) {
return this._gsub(
mappings.re[i][1],
/\$(R?)([1-9])/g,
function (_dummy, rerun, digit) {
var t = m[digit - 0];
if (rerun) {
var t2 =
MYLINGUAL.translateText(t, mappings);
if (t2 != null) {
t = t2;
}
}
return t;
});
}
}
return null;
},
// patch required for safari
_gsub: function (str, re, func) {
return str.replace(re, func);
},
initCommandMappings: function (mappings) {
var f0 = function () {
return {
re: [],
reCaseless: [],
text: {}
}
};
var f1 = function () {
return {
'class': f0(),
id: f0(),
path: f0(),
tag: f0(),
url: f0()
};
};
mappings.skip = f1();
mappings.translate = f1();
},
postProcessCommandMappings: function (mappings) {
var f0 = function (t, n, f) {
t[n] = t[n].length != 0 ? new RegExp(t[n].join('|'), f) : undefined;
};
var f1 = function (t) {
f0(t, 're', '');
f0(t, 'reCaseless', 'i');
};
var f2 = function (t) {
f1(t['class']);
f1(t.id);
f1(t.path);
f1(t.tag);
f1(t.url);
};
f2(mappings.skip);
f2(mappings.translate);
},
compileCommand: function (mappings, name, value) {
if (! name.match(/^\$(.*?)\(\s*(~{0,2})\s*(.*)\s*\)$/)) {
return;
}
var type = RegExp.$1;
var re = RegExp.$2 ? RegExp.$2.length : 0;
var match = RegExp.$3;
var store = mappings
[value[0] == 'skip' || value[0] == '' ? 'skip' : 'translate']
[type];
if (typeof store != 'object') {
return;
}
if (re) {
if (! new RegExp(match, re == 2 ? 'i' : '')) {
this.debugAlert('Syntax error: ' + name);
return;
}
store[re == 2 ? 'reCaseless' : 're'].push(match);
} else {
if (type == 'tag') {
match = match.toUpperCase();
}
store.text[match] = 1;
}
},
ifSkipURL: function (mappings, loc) {
return this.translateOrSkip(mappings.skip.path, loc.pathname)
|| this.translateOrSkip(mappings.skip.url, loc.toString());
},
translateOrSkipElement: function (mappings, e, current) {
var table = mappings[current ? 'skip' : 'translate'];
if (this.translateOrSkip(table.tag, e.tagName)
|| e.className && this.translateOrSkip(table['class'], e.className)
|| e.id && this.translateOrSkip(table.id, e.id)) {
return ! current;
}
return current;
},
translateOrSkip: function (table, value) {
value = value.toString();
return typeof table.text[value] != 'undefined'
|| (table.re && value.match(table.re))
|| (table.reCaseless && value.match(table.reCaseless));
},
getElementTranslationMode: function (mappings, body, element) {
var path = [];
for (var p = (element.nodeType == 1 ? element : element.parentNode);
p != body;
p = p.parentNode) {
path.push(p);
}
var translate = true;
while (path.length != 0) {
translate =
this.translateOrSkipElement(mappings, path.pop(), translate);
}
return translate;
},
localizeElement: function (node, mappings, translate) {
if (node.nodeType == 1) {
translate = this.translateOrSkipElement(mappings, node, translate);
if (node.nodeName == "SCRIPT" || node.nodeName == "STYLE") {
// nothing to do
return;
} else if (node.nodeName == "INPUT") {
if (! translate) {
return;
}
if (node.type == "button" || node.type == "reset") {
var translated = this.translateText(node.value, mappings);
if (translated != null) {
node.value = translated;
}
}
return;
} else if (node.nodeName == "OPTION") {
if (! translate) {
return;
}
var translated = this.translateText(node.text, mappings);
if (translated != null) {
node.value = node.value.toString();
node.text = translated;
}
return;
}
var children = node.childNodes;
for (var i = 0; i < children.length; i++) {
this.localizeElement(children.item(i), mappings, translate);
}
} else if (translate && node.nodeType == 3) {
var translated =
this.translateText(node.nodeValue, mappings);
if (translated != null) {
node.nodeValue = translated;
}
}
},
parseJSON: function (text) {
var json = undefined;
try {
var s = Components.utils.Sandbox(
"http://sandbox.japanize.31tools.com/");
Components.utils.evalInSandbox("json = " + text, s);
json = s.json;
} catch (e) {
this.debugAlert(e);
}
return json;
},
findBrowser: function (doc) {
var tb = getBrowser();
for (var i = 0; i < tb.browsers.length; ++i) {
var b = tb.getBrowserAtIndex(i);
if (b.contentDocument == doc) {
return b;
}
}
return null;
},
updateStatus: function (browser, text) {
if (browser == null) return;
browser.contentDocument.JAPANIZED_status = text;
this.redrawStatus();
},
redrawStatus: function () {
var status =
getBrowser().selectedBrowser.contentDocument.JAPANIZED_status;
if (typeof status == "undefined") {
status = "";
}
var label = document.getElementById("japanize-status-label");
if (status == "") {
label.style.display = "none";
} else {
label.value = status;
label.style.display = "inline";
}
},
getMode: function () {
var img = document.getElementById("japanize-status-icon");
return ! ! img.src.toString().match(/icon_on(_message)?\.gif$/);
},
setMode: function (on) {
// setup icon
var s = on ? "on" : "off";
var img = document.getElementById("japanize-status-icon");
img.src = img.src.toString().replace(/icon_o(n|ff)/, "icon_" + s);
document.getElementById("japanize-status-main").value =
this._s('tooltip.is' + s);
},
getMenuItem: function (suffix) {
return document.getElementById("japanize-popup-" + suffix);
},
getBundle: function () {
return document.getElementById("japanize-bundle");
},
_s: function (n) {
try {
return this.getBundle().getString('mylingual.' + n);
} catch (e) {
this.debugAlert(
"Could not find string resource: " + n + "\n\n" + e.toString());
}
},
_f: function (n, a) {
return this.getBundle().getFormattedString('mylingual.' + n, a);
},
showPopup: function (evt) {
this.getMenuItem("enabled").setAttribute(
"checked", this.getMode().toString());
if (this.getUpdateMode() == this.UPDATEMODE_EVERYTIME) {
this.getMenuItem("updateeverytime").setAttribute(
"checked", "true");
this.getMenuItem("updateperiodically").setAttribute(
"checked", "false");
this.getMenuItem("updatenow").setAttribute(
"disabled", "true");
} else {
this.getMenuItem("updateeverytime").setAttribute(
"checked", "false");
this.getMenuItem("updateperiodically").setAttribute(
"checked", "true");
this.getMenuItem("updatenow").setAttribute(
"disabled", "false");
}
},
handlePopup: function (evt) {
if (! evt.target.id.toString().match(/^japanize-popup-/)) {
return;
}
var cmd = RegExp.rightContext;
if (cmd == "enabled") {
this.setMode(! this.getMode());
} else if (cmd == "updateeverytime") {
this.setUpdateMode(this.UPDATEMODE_EVERYTIME);
} else if (cmd == "updateperiodically") {
this.setUpdateMode(this.UPDATEMODE_PERIODICALLY);
if (this.needsUpdate()) {
if (this.getPromptService().confirm(
window,
this._s('prompt.title'),
this._s('prompt.recommendupdate'))) {
this.downloadAllTable(true);
}
}
} else if (cmd == "updatenow") {
this.downloadAllTable(true);
}
},
saveAllTable: function (content, buildDate, retryCount, notifyUser) {
try {
this.saveToStore("all.txt", content);
this.setPreference("$builddate", buildDate);
if (notifyUser) {
this.notifyUser(this._s('prompt.defupdated'));
}
} catch (e) {
if (retryCount == 0) {
this.notifyUser(
this._s('prompt.defsavefailed')
+ "\n\n" + e.toString());
} else {
setTimeout(
function () {
MYLINGUAL.saveAllTable(
content, buildDate, retryCount - 1, notifyUser);
},
100);
}
}
},
onAllTableDownload: function (xhr, notifyUser) {
var json = this.parseJSON(xhr.responseText);
if (typeof json != "object") {
if (notifyUser) {
this.notifyUser(this._s('prompt.defdownloadfailed'));
}
return;
}
if (json["$builddate"] == this.getPreference("$builddate", -1)) {
if (notifyUser) {
this.notifyUser(this._s('prompt.defnochanges'));
}
return;
}
this.saveAllTable(xhr.responseText, json["$builddate"], 10, notifyUser);
},
downloadAllTable: function (notifyUser) {
try {
var xhr = new XMLHttpRequest();
xhr.open(
"get",
this.getBaseURL() + "alldata/all.txt",
true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
MYLINGUAL.onAllTableDownload(xhr, notifyUser);
}
};
xhr.send(null);
} catch (e) {
alert("Japanize : " + e.toString());
}
},
needsUpdate: function () {
// every 3 hours by default
var interval = this.getPreference("updateinterval", 3 * 3600) * 1000;
var lastUpdateAt = this.getPreference("lastupdateat", 0) * 1;
if (new Date().getTime() < lastUpdateAt + interval) {
return false;
}
return true;
},
periodicalTasks: function () {
if (! this.needsUpdate()) {
return;
}
this.setPreference("lastupdateat", new Date().getTime());
// update alltable if in periodical mode
if (this.getUpdateMode() == this.UPDATEMODE_PERIODICALLY) {
this.downloadAllTable(false);
}
}
};
// hacks for opera
MYLINGUAL.updateStatus = function (_dummy1, text) {
if (text) {
var t = document.createElement('div');
t.id = '__mylingual_status';
(function (o) {
for (var i in o) {
t.style[i] = o[i];
}
})({
border: '1px solid #666',
background: '#f88',
padding: '0.3em',
color: 'black',
fontFamily: 'sans-serif',
fontWeight: 'bold',
position: window.innerWidth ? 'fixed' : 'absolute',
left: '10px',
bottom: '10px',
zIndex: 100
});
document.body.appendChild(t);
t.innerHTML = 'Japanize: ' + text;
window.setTimeout(
function () {
t.parentNode.removeChild(t);
},
3000);
}
};
MYLINGUAL._s = function (n) {
return {
'status.translated': '翻訳完了',
'status.bannedpage': '翻訳しないページ'
}[n];
};
MYLINGUAL._f = function () {};
MYLINGUAL.localizeOpera = function (json) {
if (typeof json != 'object') return;
this.localizeWithData(json, document, {});
};
// flickr uses customized String.prototype.replace
if ("0a1".replace(/[a-z]/g, function (m) { return "A"; }) != "0A1") {
MYLINGUAL._gsub = function (str, re, func) {
var out = "";
var match;
var start = re.lastIndex = 0;
while (match = re.exec(str)) {
out += str.slice(start, match.index);
out += func.apply(null, match).toString();
start = re.lastIndex;
}
out += str.substring(start);
return out;
};
}
(function () {
var elem = document.createElement('script');
elem.src =
'http://japanize.31tools.com/data_jsonp/'
+ location.host
+ '?jsonp=MYLINGUAL.localizeOpera';
document.body.appendChild(elem);
})();