653 lines
24 KiB
HTML
653 lines
24 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<title>IronOS 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 + ".json"
|
|
);
|
|
}
|
|
|
|
function view() {
|
|
showJSON(
|
|
app.current,
|
|
"translation_" + app.current.languageCode + ".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.fonts) {
|
|
app.current.fonts = ["ascii_basic"];
|
|
}
|
|
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;
|
|
}
|
|
// 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) {
|
|
var d = "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: {},
|
|
fontToAdd: "latin_extended",
|
|
},
|
|
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) {
|
|
var str = "";
|
|
var delim = "";
|
|
var v;
|
|
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;
|
|
},
|
|
|
|
getWholeScreenMessageMaxLen: function (valMap, id, prop) {
|
|
var v = prop ? valMap[id][prop] : valMap[id];
|
|
var maxLen;
|
|
if (this.isSmall(v)) {
|
|
maxLen = defMap[id].maxLen2 || 16;
|
|
} else {
|
|
maxLen = defMap[id].maxLen || 8;
|
|
}
|
|
return maxLen;
|
|
},
|
|
|
|
validateWholeScreenMessage: function (valMap, id, prop) {
|
|
var v = prop ? valMap[id][prop] : valMap[id];
|
|
var maxLen = this.getWholeScreenMessageMaxLen(valMap, id, prop);
|
|
if (this.isSmall(v)) {
|
|
if (v[0].length === 0) {
|
|
return "invalid";
|
|
} else if (Math.max(v[0].length, v[1].length) > maxLen) {
|
|
return "invalid";
|
|
}
|
|
} else {
|
|
if (v.length > maxLen) {
|
|
return "invalid";
|
|
}
|
|
}
|
|
},
|
|
|
|
constraintWholeScreenMessage: function (valMap, id, prop) {
|
|
return (
|
|
"len <= " + this.getWholeScreenMessageMaxLen(valMap, id, prop)
|
|
);
|
|
},
|
|
|
|
isSmall: function (v) {
|
|
return v instanceof Array;
|
|
},
|
|
|
|
convertToLarge: function (valMap, id, prop) {
|
|
var v = prop ? valMap[id][prop] : valMap[id];
|
|
var message = v[0] + (v[1] !== "" ? " " + v[1] : "");
|
|
if (prop) {
|
|
valMap[id][prop] = message;
|
|
} else {
|
|
valMap[id] = message;
|
|
}
|
|
},
|
|
|
|
convertToSmall: function (valMap, id, prop) {
|
|
var v = prop ? valMap[id][prop] : valMap[id];
|
|
var message = [v, ""];
|
|
if (prop) {
|
|
valMap[id][prop] = message;
|
|
} else {
|
|
valMap[id] = message;
|
|
}
|
|
},
|
|
|
|
removeFont: function (i) {
|
|
this.current.fonts.splice(i, 1);
|
|
},
|
|
|
|
addFont: function () {
|
|
this.current.fonts.push(this.fontToAdd);
|
|
},
|
|
},
|
|
});
|
|
app.def = def;
|
|
copyArrayToMap(app.def.messages, defMap);
|
|
copyArrayToMap(app.def.messagesWarn, 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>
|
|
IronOS Translation Editor<span v-if="meta.currentLoaded">
|
|
- {{ current.languageLocalName }} [{{current.languageCode}}]</span
|
|
>
|
|
</h1>
|
|
<table class="header data">
|
|
<tr>
|
|
<td class="label">Reference 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 tables to use<br />("ascii_basic" must be first)
|
|
</td>
|
|
<td class="value">
|
|
<ul>
|
|
<li v-for="(font, i) in current.fonts">
|
|
<button
|
|
type="button"
|
|
@click="removeFont(i)"
|
|
:disabled="i == 0 && font == 'ascii_basic'"
|
|
>
|
|
-
|
|
</button>
|
|
{{ font }}
|
|
</li>
|
|
</ul>
|
|
<select v-model="fontToAdd">
|
|
<!-- <option value="ascii_basic">ascii_basic: ASCII Basic</option> -->
|
|
<option value="latin_extended">
|
|
latin_extended: Latin Extended
|
|
</option>
|
|
<option value="greek">greek: Greek Glyphs</option>
|
|
<option value="cyrillic">cyrillic: Cyrillic Glyphs</option>
|
|
<option value="cjk">cjk: Chinese/Japanese/Korean</option>
|
|
</select>
|
|
<button type="button" @click="addFont()">Add</button>
|
|
</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="label">Description</div>
|
|
<div class="ref">{{message.description}}</div>
|
|
<div class="label">Reference</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>Warning Messages</h2>
|
|
<table class="data">
|
|
<tr
|
|
v-for="message in def.messagesWarn"
|
|
v-bind:class="validateWholeScreenMessage(current.messagesWarn, message.id)"
|
|
>
|
|
<td class="label"><div class="stringId">{{message.id}}</div></td>
|
|
<td class="value">
|
|
<div class="constraint">
|
|
{{constraintWholeScreenMessage(current.messagesWarn,
|
|
message.id)}}
|
|
</div>
|
|
<div class="label">Description</div>
|
|
<div class="ref">{{message.description}}</div>
|
|
<div class="label">Reference</div>
|
|
<div class="ref">{{referent.messagesWarn[message.id]}}</div>
|
|
<div class="note" v-if="message.note">{{message.note}}</div>
|
|
<div
|
|
class="tran"
|
|
v-if="isSmall(current.messagesWarn[message.id])"
|
|
>
|
|
<input
|
|
:id="'in_'+message.id+'_0'"
|
|
type="text"
|
|
v-model="current.messagesWarn[message.id][0]"
|
|
v-bind:class="{unchanged : current.messagesWarn[message.id][0] == referent.messagesWarn[message.id][0] && current.messagesWarn[message.id][1] == referent.messagesWarn[message.id][1], empty : current.messagesWarn[message.id][0] == '' && current.messagesWarn[message.id][1] == ''}"
|
|
/>
|
|
<input
|
|
:id="'in_'+message.id+'_1'"
|
|
type="text"
|
|
v-model="current.messagesWarn[message.id][1]"
|
|
v-bind:class="{unchanged : current.messagesWarn[message.id][0] == referent.messagesWarn[message.id][0] && current.messagesWarn[message.id][1] == referent.messagesWarn[message.id][1], empty : current.messagesWarn[message.id][0] == '' && current.messagesWarn[message.id][1] == ''}"
|
|
/>
|
|
<button
|
|
type="button"
|
|
@click="convertToLarge(current.messagesWarn, message.id)"
|
|
>
|
|
Convert to large text
|
|
</button>
|
|
</div>
|
|
<div class="tran" v-else>
|
|
<input
|
|
:id="'in_'+message.id"
|
|
type="text"
|
|
v-model="current.messagesWarn[message.id]"
|
|
v-bind:class="{unchanged : current.messagesWarn[message.id] == referent.messagesWarn[message.id], empty : current.messagesWarn[message.id]==''}"
|
|
/>
|
|
<button
|
|
type="button"
|
|
@click="convertToSmall(current.messagesWarn, message.id)"
|
|
>
|
|
Convert to small text
|
|
</button>
|
|
</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="label">Description</div>
|
|
<div class="ref">{{char.description}}</div>
|
|
<div class="label">Reference</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="validateWholeScreenMessage(current.menuGroups, menu.id, 'text2')"
|
|
>
|
|
<td class="label"><div class="stringId">{{menu.id}}</div></td>
|
|
<td class="value">
|
|
<div class="label">Menu Name</div>
|
|
<div class="constraint">
|
|
{{constraintWholeScreenMessage(current.menuGroups, menu.id,
|
|
'text2')}}
|
|
</div>
|
|
<div class="label">Description</div>
|
|
<div class="ref">{{menu.description}}</div>
|
|
<div class="label">Reference</div>
|
|
<div class="ref">{{referent.menuGroups[menu.id].text2}}</div>
|
|
<div
|
|
class="tran"
|
|
v-if="isSmall(current.menuGroups[menu.id].text2)"
|
|
>
|
|
<input
|
|
type="text"
|
|
v-model="current.menuGroups[menu.id].text2[0]"
|
|
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[1]"
|
|
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] == ''}"
|
|
/>
|
|
<button
|
|
type="button"
|
|
@click="convertToLarge(current.menuGroups, menu.id, 'text2')"
|
|
>
|
|
Convert to large text
|
|
</button>
|
|
</div>
|
|
<div class="tran" v-else>
|
|
<input
|
|
type="text"
|
|
v-model="current.menuGroups[menu.id].text2"
|
|
v-bind:class="{unchanged : current.menuGroups[menu.id].text2 == referent.menuGroups[menu.id].text2, empty : current.menuGroups[menu.id].text2==''}"
|
|
/>
|
|
<button
|
|
type="button"
|
|
@click="convertToSmall(current.menuGroups, menu.id, 'text2')"
|
|
>
|
|
Convert to small text
|
|
</button>
|
|
</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
|
|
v-for="menu in def.menuOptions"
|
|
v-bind:class="validateWholeScreenMessage(current.menuOptions, menu.id, 'text2')"
|
|
>
|
|
<td class="label"><div class="stringId">{{menu.id}}</div></td>
|
|
<td class="value">
|
|
<div v-bind:class="{hidden : false}">
|
|
<div class="label">Menu Name</div>
|
|
<div class="constraint">
|
|
{{constraintWholeScreenMessage(current.menuOptions, menu.id,
|
|
'text2')}}
|
|
</div>
|
|
<div class="label">Description</div>
|
|
<div class="ref">{{menu.description}}</div>
|
|
<div class="label">Reference</div>
|
|
<div class="ref">{{referent.menuOptions[menu.id].text2}}</div>
|
|
<div
|
|
class="tran"
|
|
v-if="isSmall(current.menuOptions[menu.id].text2)"
|
|
>
|
|
<input
|
|
type="text"
|
|
v-model="current.menuOptions[menu.id].text2[0]"
|
|
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[1]"
|
|
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] == ''}"
|
|
/>
|
|
<button
|
|
type="button"
|
|
@click="convertToLarge(current.menuOptions, menu.id, 'text2')"
|
|
>
|
|
Convert to large text
|
|
</button>
|
|
</div>
|
|
<div class="tran" v-else>
|
|
<input
|
|
type="text"
|
|
v-model="current.menuOptions[menu.id].text2"
|
|
v-bind:class="{unchanged : current.menuOptions[menu.id].text2 == referent.menuOptions[menu.id].text2, empty : current.menuOptions[menu.id].text2==''}"
|
|
/>
|
|
<button
|
|
type="button"
|
|
@click="convertToSmall(current.menuOptions, menu.id, 'text2')"
|
|
>
|
|
Convert to small text
|
|
</button>
|
|
</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>
|