// ==UserScript==
// @name PoiDBTrans
// @namespace https://greasyfork.org/en/users/135297-joe-barker
// @version 9
// @description Translate ship names and menu items on db.kcwiki.org
// @author jwbarker
// @license Public Domain
// @match https://db.kcwiki.org/*
// @grant GM_setValue
// @grant GM_getValue
// @require http://code.jquery.com/jquery-2.2.4.min.js#sha256=BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=
// @require https://greasyfork.org/scripts/30548-waitforkeyelements/code/waitForKeyElements.js
// @require https://greasyfork.org/scripts/5279-greasemonkey-supervalues/code/GreaseMonkey_SuperValues.js
// ==/UserScript==
(function() {
'use strict';
//***********************************
// Translation of page items
var trans={};
var trans_ship_name=function(nm) {
if(trans.ships.hasOwnProperty(nm)) {
return trans.ships[nm];
}
//Check if suffixes present
var sfx=Object.keys(trans.affix).find(function(sfx) {
return nm.endsWith(sfx);
});
if(sfx) {
var t=trans_ship_name(nm.slice(0,-sfx.length))+trans.affix[sfx];
trans.ships[nm]=t;
return t;
}
return nm;
};
var createLoadOwnedShips=function() { //Create button to load owned ship data
var el=document.createElement("SPAN");
el.appendChild(document.createTextNode("Load owned ships:"));
var ship_file_inp=document.createElement("INPUT");
ship_file_inp.setAttribute("type", "file");
ship_file_inp.onchange = function(event){
var input = event.target;
var reader = new FileReader();
reader.onload = function(){
var d;
try {
d = JSON.parse(reader.result);
} catch(e) { window.alert("PoiDBTrans:\nError: Failed to parse selected file as JSON!"); return; }
//Load ship id data
var ship_id=trans.wctf_id;
var ship_nm=trans.wctf_nm;
//Process locked ship data from user's file
var locked={};
for(var s in d.ships) {
if(d.ships[s].lock!=1) continue;
var ss=ship_id[d.ships[s].masterId];
while(ss) {
locked[ss.name.ja_jp]=true;
if(ss.remodel.prev)
ss=ship_id[ss.remodel.prev];
else
ss=null;
}
ss=ship_id[d.ships[s].masterId];
while(ss) {
locked[ss.name.ja_jp]=true;
if(ss.remodel.next)
ss=ship_id[ss.remodel.next];
else
ss=null;
}
}
if(Object.keys(locked).length==0) {
window.alert("PoiDBTrans:\nUser ship list load failed!\nNo owned ships found");
return;
}
GM_SuperValue.set ("locked_ships", locked);
window.alert("PoiDBTrans:\nUser ship list loaded OK.");
window.location.href=window.location.href;
};
reader.readAsText(input.files[0]);
};
el.appendChild(ship_file_inp);
var help=document.createElement("A");
help.innerHTML="?";
help.onclick=function() {
alert("Use this to highlight drops that you do not own yet.\n\nFirst, use KC3Kai to export your list of owned ships:\n"+
"1) In Strategy Room, go to Player/Profile\n2) Click 'Export Basic Profile' and save this file somewhere\n"+
"3) Return to this page and click the 'Choose file' button\n4) Select the file you just saved\n"+
"5) If loading succeeds, a popup box will appear informing you so\n\n(Note, only heartlocked ships are counted.)");
};
el.appendChild(help);
return el;
};
var transFunc=function() { //Main translation function
//Wait for all data to load
if(!trans.hasOwnProperty('ships')) return;
if(!trans.hasOwnProperty('affix')) return;
if(!trans.hasOwnProperty('equips')) return;
if(!trans.hasOwnProperty('misc')) return;
if(!trans.hasOwnProperty('wctf_id')) return;
var locked_ships = GM_SuperValue.get ("locked_ships"); //Load user ship list
if(locked_ships) {
if(Object.keys(locked_ships).length==0) //Must have screwed up load
locked_ships=null;
else
locked_ships["(无掉落)"]=true;
}
var rgx_map=/^https:\/\/db\.kcwiki\.org\/drop\/map\/\d+(\/\d)?\/[^.]+\.html$/;
var rgx_ship=/^https:\/\/db\.kcwiki\.org\/drop\/ship\/\d+\/?$/;
var rgx_shipConst=/^https:\/\/db\.kcwiki\.org\/construction\/ship\/\d+\.html$/;
var rgx_equip=/^https:\/\/db\.kcwiki\.org\/development\/item\/\d+\.html$/;
if(rgx_map.test(document.URL)) { //Map drop page
//Translate ship names/categories
var transShipRow=function(el) {
var cols=el.getElementsByTagName("TD");
var shpc=cols[1];
if(shpc.childNodes[0].nodeType==Node.ELEMENT_NODE &&
!shpc.childNodes[0].hasAttribute("translated") ) {
var nm=shpc.childNodes[0].innerHTML;
shpc.childNodes[0].innerHTML=trans_ship_name(nm);
if(locked_ships && !locked_ships.hasOwnProperty(nm) &&
nm.substr(-4)!=" ***" )
shpc.childNodes[0].innerHTML+=" ***";
if(trans.wctf_nm.hasOwnProperty(nm) && trans.wctf_nm[nm].links &&
trans.wctf_nm[nm].links[1] && trans.wctf_nm[nm].links[1].name &&
trans.wctf_nm[nm].links[1].name=="英文WIKI" && trans.wctf_nm[nm].links[1].url ) {
var a=document.createElement("A");
a.setAttribute("href", trans.wctf_nm[nm].links[1].url);
a.style.verticalAlign="super";
a.innerHTML=" ?";
shpc.childNodes[0].insertBefore(a,null);
}
shpc.childNodes[0].setAttributeNode(document.createAttribute("translated"));
} else if(shpc.childNodes[0].nodeType==Node.TEXT_NODE &&
!shpc.hasAttribute("translated") ) {
var nm=shpc.childNodes[0].nodeValue;
shpc.childNodes[0].nodeValue=trans_ship_name(nm);
if(locked_ships && !locked_ships.hasOwnProperty(nm) &&
nm.substr(-4)!=" ***" )
shpc.childNodes[0].nodeValue+=" ***";
if(trans.wctf_nm.hasOwnProperty(nm) && trans.wctf_nm[nm].links &&
trans.wctf_nm[nm].links[1] && trans.wctf_nm[nm].links[1].name &&
trans.wctf_nm[nm].links[1].name=="英文WIKI" && trans.wctf_nm[nm].links[1].url ) {
var a=document.createElement("A");
a.setAttribute("href", trans.wctf_nm[nm].links[1].url);
a.style.verticalAlign="super";
a.innerHTML=" ?";
shpc.insertBefore(a,shpc.childNodes[0].nextSibling);
}
shpc.setAttributeNode(document.createAttribute("translated"));
}
var typ=cols[2];
if(trans.misc.hasOwnProperty(typ.innerHTML))
typ.innerHTML=trans.misc[typ.innerHTML];
};
var compTransCache={};
var rgx_comp1=/^\s*([^(]+)\s*([(]\s*\d+\s*[)])\s*\/?/;
var rgx_comp2=/^\s*[(]\s*([^)]+)\s*[)]/;
var compTrans=function(el) {
if(el.children.length==0 || el.children[0].tagName!="TD") return;
el=el.children[0];
if(el.children.length==0 || el.children[0].tagName!="DIV" || !el.children[0].classList.contains("table-like")) return;
el=el.children[0];
for(var j=1;j<el.children.length;++j) {
var el2=el.children[j];
if(el2.children.length==0 || el2.children[0].tagName!="SPAN") continue;
el2=el2.children[0];
if(compTransCache.hasOwnProperty(el2.innerHTML)) {
el2.innerHTML=compTransCache[el2.innerHTML];
continue;
}
var didTrans=false;
var t="";
var old=el2.innerHTML;
var match=rgx_comp1.exec(old);
while(match!=null) {
var tnm=trans_ship_name(match[1]);
if(tnm!=match[1]) {
t+=" / "+tnm+" "+match[2];
didTrans=true;
} else {
t+=" / "+match[1]+match[2];
}
old=old.slice(match[0].length);
match=rgx_comp1.exec(old);
}
t=t.slice(3);
match=rgx_comp2.exec(old);
if(match!=null) {
if(trans.misc.hasOwnProperty(match[1])) {
t+=" ("+trans.misc[match[1]]+")";
didTrans=true;
} else {
t+=" ("+match[1]+")";
}
} else {
t+=old;
}
if(!didTrans) {
//No trans
compTransCache[el2.innerHTML]=el2.innerHTML;
continue;
}
compTransCache[el2.innerHTML]=t;
el2.innerHTML=t;
}
};
var trans_tr=function(el) {
if(el instanceof jQuery) el=el.get(0);
if(el.hasAttribute("data-index")) transShipRow(el);
else if(el.classList.contains("detail-view")) compTrans(el);
};
waitForKeyElements ("tr", trans_tr);
var rows=document.getElementsByTagName("TR");
for(var i=0;i<rows.length;++i)
trans_tr(rows[i]);
waitForKeyElements("div.section-cbox-w",function(el) {
if(el instanceof jQuery) el=el.get(0);
el.parentElement.insertBefore(createLoadOwnedShips(), el);
});
} else if(document.URL=="https://db.kcwiki.org/drop/") { //Main drop page
$(".panel-title").each(function(i,el) {
if(el instanceof jQuery) el=el.get(0);
if(el.innerHTML!="舰娘") return;
el.parentElement.parentElement.parentElement.appendChild(createLoadOwnedShips());
});
}
//Translate buttons/links (on all pages)
waitForKeyElements ("a", function(el) {
if(el instanceof jQuery) el=el.get(0);
if( ( rgx_ship.test(el.href) || //Link to ship drop page
rgx_shipConst.test(el.href) ) //Or ship const page
&& trans.ships.hasOwnProperty(el.innerHTML)) {
var nm=el.innerHTML;
el.innerHTML=trans.ships[nm];
if(locked_ships && !locked_ships.hasOwnProperty(nm))
el.style.textDecoration='underline';
} else if(rgx_equip.test(el.href) && //Link to equip recipe page
trans.equips.hasOwnProperty(el.innerHTML)) {
el.innerHTML=trans.equips[el.innerHTML];
} else if(trans.misc.hasOwnProperty(el.innerHTML)) //Miscellaneous links
el.innerHTML=trans.misc[el.innerHTML];
});
};
//***********************************
// Load translation from KC3Kai-translations
// (Run translation function after all are loaded)
var do_load_data=(function() {
//Create a function which will try to load data from a sequence of mirror urls,
//process and cache that data if sucessful, or use cached data if missing
var cache={};
cache.store=GM_SuperValue.get("cache_store");
if(!cache.store)
cache.store={};
cache.update_wait=0;
cache.update_needed=false;
cache.update=function() {
if( --cache.update_wait > 0 )
return;
if(!cache.update_needed)
return;
GM_SuperValue.set ("cache_store", cache.store);
};
var load=function(name,dataType,urls,process_function) {
if(!Array.isArray(urls))
urls=[urls];
var now = Date.now();
++cache.update_wait;
var ajax_fail=function() {
//If all else fails, check the cache
if(cache.store.hasOwnProperty([name])) {
var data=cache.store[name].data;
if(process_function(data)) {
//success
var oldtime=(new Date(cache.store[name].access_time)).toLocaleString();
console.log('PoiDBTrans: Loaded old('+oldtime+') version of '+name);
if(now > cache.store[name].last_warning + 24*60*60*1000) {
window.alert("PoiDBTrans:\nFailed loading "+name+
", using old data ("+oldtime+
") instead\n\n(This warning will now be disabled for 24hrs)");
cache.store[name].last_warning = now;
cache.update_needed=true;
}
}
//No else, we don't cache data that fails processing
} else {
console.log('PoiDBTrans: Failed loading '+name);
window.alert("PoiDBTrans:\nFailed loading "+name+"\nTranslation disabled.");
}
cache.update();
};
//Make a sequence of callback functions that try each url in turn if the previous fails
for(let i=urls.length-1;i>=0;--i) {
const oldfail=ajax_fail;
const url=urls[i];
ajax_fail=function() {
$.ajax({
dataType: dataType,
url: url,
cache: true
}).done(function( data ) {
try {
if(!process_function(data))
throw 'failed';
} catch(e) {
console.log('PoiDBTrans: Failed to process '+url);
if(e.message)
console.log(e.message);
else
console.log(e);
oldfail();
return;
}
//Success
cache.store[name]={};
cache.store[name].data=data;
cache.store[name].access_time = now;
cache.store[name].last_warning = now - 24*60*60*1000;
cache.update_needed=true;
cache.update();
}).fail(function() {
console.log('PoiDBTrans: Failed to access (or parse) '+url);
oldfail();
});
};
}
//Kick things off
ajax_fail();
};
return load;
})();
do_load_data('ships.json','json',[
'https://gitcdn.xyz/repo/KC3Kai/kc3-translations/master/data/en/ships.json',
'https://raw.githubusercontent.com/KC3Kai/kc3-translations/master/data/en/ships.json'
],
function(data) {
trans.ships=data;
trans.ships["(无掉落)"]="(No drop)";
transFunc();
return true;
}
);
do_load_data('ship_affix.json','json',[
'https://gitcdn.xyz/repo/KC3Kai/kc3-translations/master/data/en/ship_affix.json',
'https://raw.githubusercontent.com/KC3Kai/kc3-translations/master/data/en/ship_affix.json'
],
function(data) {
trans.affix=Object.assign({},data.suffixes,data.yomi);
transFunc();
return true;
}
);
do_load_data('WhoCallsTheFleet_ships.nedb','text',[
'https://gitcdn.xyz/repo/KC3Kai/KC3Kai/master/src/data/WhoCallsTheFleet_ships.nedb',
'https://raw.githubusercontent.com/KC3Kai/KC3Kai/master/src/data/WhoCallsTheFleet_ships.nedb'
],
function(data) {
if(!/^\s*{/.test(data))
return false;
var wctf_id={};
var wctf_nm={};
var ok=0;
data.split("\n").forEach(function(line) {
try {
var s=JSON.parse(line);
wctf_id[s.id]=s;
if(!wctf_nm.hasOwnProperty(s.name.ja_jp) ||
wctf_nm[s.name.ja_jp].id > s.id )
wctf_nm[s.name.ja_jp]=s;
++ok;
// } catch (e) { }
} catch (e) { console.log(line); }
});
if(ok==0)
return false;
trans.wctf_id=wctf_id;
trans.wctf_nm=wctf_nm;
transFunc();
return true;
}
);
do_load_data('items.json','json',[
'https://gitcdn.xyz/repo/KC3Kai/kc3-translations/master/data/en/items.json',
'https://raw.githubusercontent.com/KC3Kai/kc3-translations/master/data/en/items.json'
],
function(data) {
var equips=data;
equips["(失敗)"]="Fail (Penguin)";
trans.equips=equips;
transFunc();
return true;
}
);
//Translation of menu/other items
var misc={};
//Ships
misc["空母"]="CV(B)";
misc["轻空母"]="CVL";
misc["战舰"]="(F)BB(V)";
misc["重巡洋舰"]="CA(V)";
misc["轻巡洋舰"]="CL";
misc["驱逐舰"]="DD";
misc["海防舰"]="DE";
misc["补给舰"]="AO";
misc["扬陆舰"]="LHA";
misc["工作舰"]="AR";
misc["潜水舰"]="SS";
misc["潜水空母"]="SSV";
misc["驱逐"]="DD";
misc["轻巡、练巡"]="CL/CLT";
misc["重巡"]="CA";
misc["轻母"]="CVL";
misc["潜水母舰"]="AS";
misc["水母"]="AV";
misc["练习巡洋舰"]="CT";
misc["其他"]="Other";
//Equipment
misc["主砲・副砲"]="Main/Secondary Gun";
misc["魚雷"]="Torpedo";
misc["艦載機"]="Attack Plane";
misc["弾薬・機銃"]="Ammunition / AA Gun";
misc["偵察機・電探"]="Reconnaissance Plane / Radar";
misc["缶・タービン・バルジ"]="Drum / Turbine / Torp Bulge";
misc["爆雷・ソナー"]="Depth Charge / Sonar";
//Recipes
misc["装備開発"]="Equipment";
misc["艦娘建造(通常)"]="Ships (Normal)";
misc["艦娘建造(大型)"]="Ships (LSC)";
//Menu sections
misc["建造统计"]="Construction";
misc["掉落统计"]="Drops";
misc["开发统计"]="Development";
misc["新实装、活动限定"]="New / Event";
//Difficulties
misc["甲"]="Hard";
misc["乙"]="Normal";
misc["丙"]="Easy";
misc["丁"]="Casual";
//Formations
misc["梯形陣"]="Echelon";
misc["輪形陣"]="Diamond";
misc["単横陣"]="Line Abreast";
misc["単縦陣"]="Line Ahead";
misc["複縦陣"]="Double Line";
misc["第一警戒航行序列"]="Cruising Formation 1, anti-sub";
misc["第二警戒航行序列"]="Cruising Formation 2, forward";
misc["第三警戒航行序列"]="Cruising Formation 3, ring";
misc["第四警戒航行序列"]="Cruising Formation 4, battle";
// misc[""]="";
trans.misc=misc;
transFunc();
})();