353 lines
12 KiB
HTML
353 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>TS100 Translation Editor</title>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
|
|
<script src="translations_commons.js"></script>
|
|
<script src="translations_def.js"></script>
|
|
<script>
|
|
|
|
var app;
|
|
var defMap = {};
|
|
|
|
function save(){
|
|
saveJSON(app.current, "translation_"+app.current.languageCode.toLowerCase()+".json");
|
|
}
|
|
|
|
function view(){
|
|
showJSON(app.current, "translation_"+app.current.languageCode.toLowerCase()+".json");
|
|
}
|
|
|
|
function fileChanged(e) {
|
|
var target = e;
|
|
var id = target.id;
|
|
|
|
var file = target.files[0];
|
|
if (!file) {
|
|
return;
|
|
}
|
|
var fr = new FileReader();
|
|
fr.onload = function(e) {
|
|
try {
|
|
var json = JSON.parse(e.target.result);
|
|
} catch (ex) {
|
|
console.log(ex);
|
|
alert("Invalid JSON file: " + file.name);
|
|
return;
|
|
}
|
|
|
|
|
|
if (id == "referent-lang-file") {
|
|
if (checkTranslationFile(file.name)) {
|
|
app.referent = json;
|
|
app.meta.referentLoaded = true;
|
|
}
|
|
} else if (id == "current-lang-file") {
|
|
if (checkTranslationFile(file.name)) {
|
|
app.current = json;
|
|
if (!app.current.cyrillicGlyphs){
|
|
app.current.cyrillicGlyphs = false;
|
|
}
|
|
app.meta.currentLoaded = true;
|
|
}
|
|
}
|
|
synchronizeData();
|
|
}
|
|
fr.readAsText(file);
|
|
|
|
}
|
|
|
|
function synchronizeData() {
|
|
app.obsolete = {};
|
|
copyMissing(app.def.messages, app.referent.messages, app.current.messages);
|
|
copyMissing(app.def.characters, app.referent.characters, app.current.characters);
|
|
copyMissing(app.def.menuGroups, app.referent.menuGroups, app.current.menuGroups);
|
|
copyMissing(app.def.menuOptions, app.referent.menuOptions, app.current.menuOptions);
|
|
}
|
|
|
|
/**
|
|
* Copy all missing properties from referent to current
|
|
* for each entry in definition
|
|
*/
|
|
function copyMissing(defList, referentMap, currentMap) {
|
|
if (!isDefined(defList) || !isDefined(referentMap) || !isDefined(currentMap)) {
|
|
return;
|
|
}
|
|
var len = defList.length;
|
|
for (var i = 0; i < len; i++) {
|
|
var id = defList[i].id;
|
|
if (!isDefined(referentMap[id])) {
|
|
referentMap[id] = '';
|
|
}
|
|
if (!isDefined(currentMap[id])) {
|
|
currentMap[id] = referentMap[id];
|
|
}
|
|
}
|
|
processObsolete(defList, currentMap);
|
|
}
|
|
|
|
// Passes through all entries from the given map.
|
|
// If a corresponding entry is not found in the defList, it is removed from the map, and added into the obsolete map.
|
|
function processObsolete(defList, map) {
|
|
// Index list to map for faster search
|
|
var defMap = copyArrayToMap(defList);
|
|
Object.keys(map).forEach(function(key) {
|
|
if (!isDefined(defMap[key])) {
|
|
app.obsolete[key] = { id : key, value : map[key]};
|
|
delete map[key];
|
|
}
|
|
});
|
|
}
|
|
|
|
function length(obj, mode) {
|
|
if (!isDefined(mode) || mode == 0) {
|
|
// return direct length
|
|
return obj.length;
|
|
} else if (mode == 1) {
|
|
// return length of text property
|
|
return obj.text.length;
|
|
} else if (mode == 2) {
|
|
// return the longest length in text2 array
|
|
return Math.max(isDefinedNN(obj.text2[0]) ? obj.text2[0].length : 0, isDefinedNN(obj.text2[1]) ? obj.text2[1].length : 0);
|
|
}
|
|
}
|
|
|
|
function getAttribute(obj, attribute, isDouble) {
|
|
var d = isDouble ? "2" : "";
|
|
var v = obj[attribute+d];
|
|
if (isDefined(v))
|
|
return v;
|
|
return obj[attribute];
|
|
}
|
|
|
|
function loaded() {
|
|
app = new Vue({
|
|
el : '#app',
|
|
data : {
|
|
meta : {
|
|
referentLoaded : false,
|
|
currentLoaded : false,
|
|
},
|
|
def : {
|
|
},
|
|
referent : {
|
|
messages : {}
|
|
},
|
|
current : {
|
|
loaded: false,
|
|
},
|
|
obsolete : {},
|
|
menuDouble : false
|
|
},
|
|
methods : {
|
|
validateInput: function(valMap, id, mode) {
|
|
var d = defMap[id];
|
|
var vLen = 0;
|
|
if (!isDefined(mode))
|
|
mode = 0;
|
|
|
|
try {
|
|
// Sum for complex length
|
|
for (var i = 0; i < d.lenSum.fields.length; i++) {
|
|
vLen += length(valMap[d.lenSum.fields[i]], mode);
|
|
}
|
|
d = d.lenSum;
|
|
} catch (e) {
|
|
// Single field length
|
|
vLen = length(valMap[id], mode);
|
|
}
|
|
var maxLen = getAttribute(d, 'maxLen', mode == 2);
|
|
var minLen = getAttribute(d, 'minLen', mode == 2);
|
|
var len = getAttribute(d, 'len', mode == 2);
|
|
if (isNumber(maxLen) && vLen > maxLen
|
|
|| isNumber(minLen) && vLen < minLen
|
|
|| isNumber(len) && vLen != len
|
|
) {
|
|
return "invalid";
|
|
}
|
|
},
|
|
|
|
constraintString: function(e, d) {
|
|
var str = "";
|
|
var delim = "";
|
|
var v;
|
|
if (!isDefined(d) || d == false) {
|
|
d = "";
|
|
} else {
|
|
d = "2";
|
|
}
|
|
|
|
if (isDefinedNN(e.lenSum)) {
|
|
str = "len("+(e.lenSum.fields+"").replace(/,/g," + ")+") -> ";
|
|
e = e.lenSum;
|
|
}
|
|
v = getAttribute(e, 'len', d);
|
|
if (isNumber(v)) {
|
|
str += delim + "len=" + v;
|
|
delim = " and ";
|
|
}
|
|
v = getAttribute(e, 'minLen', d);
|
|
if (isNumber(v)) {
|
|
str += delim + "len>=" + v;
|
|
delim = " and ";
|
|
}
|
|
v = getAttribute(e, 'maxLen', d);
|
|
if (isNumber(v)) {
|
|
str += delim + "len<=" + v;
|
|
delim = " and ";
|
|
}
|
|
return str;
|
|
}
|
|
}
|
|
});
|
|
app.def = def;
|
|
copyArrayToMap(app.def.messages, defMap);
|
|
copyArrayToMap(app.def.characters, defMap);
|
|
copyArrayToMap(app.def.menuGroups, defMap);
|
|
copyArrayToMap(app.def.menuOptions, defMap);
|
|
}
|
|
|
|
window.onload=loaded;
|
|
</script>
|
|
<link href="translations.css" rel="stylesheet" type="text/css">
|
|
</head>
|
|
<body>
|
|
|
|
<div id="app">
|
|
<h1>TS100 Translation Editor<span v-if="meta.currentLoaded"> - {{ current.languageLocalName }} [{{current.languageCode}}]</span></h1>
|
|
<table class="header data">
|
|
<tr>
|
|
<td class="label">Referent Language</td>
|
|
<td class="value">
|
|
<input type="file" id="referent-lang-file" onchange="fileChanged(this)" accept=".json">
|
|
<span class="selected" v-if="meta.referentLoaded">{{ referent.languageLocalName }} [{{referent.languageCode}}]</span>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="meta.referentLoaded">
|
|
<td class="label">Current Language</td>
|
|
<td class="value">
|
|
<input type="file" id="current-lang-file" onchange="fileChanged(this)" accept=".json">
|
|
<span class="selected" v-if="meta.currentLoaded">{{ current.languageLocalName }} [{{current.languageCode}}]</span>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="meta.currentLoaded">
|
|
<td class="label">Local Language Code</td>
|
|
<td class="value"><input type="text" v-model="current.languageCode" maxlength="8" v-on:change="current.languageCode=current.languageCode.toUpperCase()" class="short"></td>
|
|
</tr>
|
|
<tr v-if="meta.currentLoaded">
|
|
<td class="label">Local Language Name</td>
|
|
<td class="value"><input type="text" v-model="current.languageLocalName" class="short"></td>
|
|
</tr>
|
|
<tr v-if="meta.currentLoaded">
|
|
<td class="label">Font table to use</td>
|
|
<td class="value">
|
|
<select v-model="current.cyrillicGlyphs" v-on:change="current.cyrillicGlyphs = current.cyrillicGlyphs=='true'">
|
|
<option value="false">Latin Extended</option>
|
|
<option value="true">Cyrillic Glyphs</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<div v-if="def.messages && referent.messages && current.messages">
|
|
|
|
<div class="footer">
|
|
<input type="button" value="Save" onclick="save()">
|
|
<input type="button" value="View" onclick="view()">
|
|
</div>
|
|
|
|
<div v-if="Object.keys(obsolete).length > 0">
|
|
<h2>Obsolete</h2>
|
|
<table class="data">
|
|
<tr v-for="entry in obsolete">
|
|
<td class="label"><div class="stringId">{{entry.id}}</div></td>
|
|
<td class="value"><div class="ref">{{entry.value}}</div></td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<h2>Messages and Strings</h2>
|
|
<table class="data">
|
|
<tr v-for="message in def.messages" v-bind:class="validateInput(current.messages, message.id)">
|
|
<td class="label"><div class="stringId">{{message.id}}</div></td>
|
|
<td class="value">
|
|
<div class="constraint">{{constraintString(message)}}</div>
|
|
<div class="ref">{{referent.messages[message.id]}}</div>
|
|
<div class="note" v-if="message.note">{{message.note}}</div>
|
|
<div class="tran"><input :id="'in_'+message.id" type="text" v-model="current.messages[message.id]" v-bind:class="{unchanged : current.messages[message.id] == referent.messages[message.id], empty : current.messages[message.id]==''}"></div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2>Characters</h2>
|
|
<table class="data">
|
|
<tr v-for="char in def.characters" v-bind:class="validateInput(current.characters, char.id)">
|
|
<td class="label"><div class="stringId">{{char.id}}</div></td>
|
|
<td class="value">
|
|
<div class="constraint">{{constraintString(char)}}</div>
|
|
<div class="ref">{{referent.characters[char.id]}}</div>
|
|
<div class="tran"><input type="text" v-model="current.characters[char.id]" v-bind:class="{unchanged : current.characters[char.id] == referent.characters[char.id], empty : current.characters[char.id].length != 1}"></div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2>Menu Groups</h2>
|
|
<table class="data">
|
|
<tr v-for="menu in def.menuGroups" v-bind:class="validateInput(current.menuGroups, menu.id, 2)">
|
|
<td class="label"><div class="stringId">{{menu.id}}</div></td>
|
|
<td class="value">
|
|
<div class="label">Menu Name</div>
|
|
<div class="constraint">{{constraintString(menu)}}</div>
|
|
<div class="ref">{{referent.menuGroups[menu.id].text2}}</div>
|
|
<div class="tran" v-bind:class="{unchanged : current.menuGroups[menu.id].text2[0] == referent.menuGroups[menu.id].text2[0] && current.menuGroups[menu.id].text2[1] == referent.menuGroups[menu.id].text2[1], empty : current.menuGroups[menu.id].text2[0] == '' || current.menuGroups[menu.id].text2[1] == ''}"><input type="text" v-model="current.menuGroups[menu.id].text2[0]"><input type="text" v-model="current.menuGroups[menu.id].text2[1]"></div>
|
|
<div class="label">Description</div>
|
|
<div class="ref">{{referent.menuGroups[menu.id].desc}}</div>
|
|
<div class="tran"><input type="text" v-model="current.menuGroups[menu.id].desc" v-bind:class="{unchanged : current.menuGroups[menu.id].desc == referent.menuGroups[menu.id].desc, empty : current.menuGroups[menu.id].desc == ''}"></div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2>Menu Options</h2>
|
|
<table class="data">
|
|
<tr>
|
|
<td class="label">Menu Type</td>
|
|
<td class="value">
|
|
<select v-model="current.menuDouble" v-on:change="current.menuDouble = current.menuDouble=='true'">
|
|
<option value="false">Single-Line</option>
|
|
<option value="true">Double-Line</option>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
<tr v-for="menu in def.menuOptions" v-bind:class="validateInput(current.menuOptions, menu.id, (current.menuDouble ? 2 : 1))">
|
|
<td class="label"><div class="stringId">{{menu.id}}</div></td>
|
|
<td class="value">
|
|
<div v-bind:class="{hidden : current.menuDouble}">
|
|
<div class="label">Menu Name (Single-Line)</div>
|
|
<div class="constraint">{{constraintString(menu, current.menuDouble)}}</div>
|
|
<div class="ref">{{referent.menuOptions[menu.id].text}}</div>
|
|
<div class="tran"><input type="text" v-model="current.menuOptions[menu.id].text" v-bind:class="{unchanged : current.menuOptions[menu.id].text == referent.menuOptions[menu.id].text, empty : current.menuOptions[menu.id].text == ''}"></div>
|
|
</div>
|
|
<div v-bind:class="{hidden : !current.menuDouble}">
|
|
<div class="label">Menu Name (Double-Line)</div>
|
|
<div class="constraint">{{constraintString(menu, current.menuDouble)}}</div>
|
|
<div class="ref">{{referent.menuOptions[menu.id].text2}}</div>
|
|
<div class="tran" v-bind:class="{unchanged : current.menuOptions[menu.id].text2[0] == referent.menuOptions[menu.id].text2[0] && current.menuOptions[menu.id].text2[1] == referent.menuOptions[menu.id].text2[1], empty : current.menuOptions[menu.id].text2[0] == '' || current.menuOptions[menu.id].text2[1] == ''}"><input type="text" v-model="current.menuOptions[menu.id].text2[0]"><input type="text" v-model="current.menuOptions[menu.id].text2[1]"></div>
|
|
</div>
|
|
<div class="label">Description</div>
|
|
<div class="ref">{{referent.menuOptions[menu.id].desc}}</div>
|
|
<div class="tran"><input type="text" v-model="current.menuOptions[menu.id].desc" v-bind:class="{unchanged : current.menuOptions[menu.id].desc == referent.menuOptions[menu.id].desc, empty : current.menuOptions[menu.id].desc == ''}"></div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<div class="footer">
|
|
<input type="button" value="Save" onclick="save()">
|
|
<input type="button" value="View" onclick="view()">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html> |