1
0
forked from me/IronOS
Files
IronOS/Translations/TranslationEditor.html
prokrypt ba3e5760f4 Update TranslationEditor.html
Remove toLowerCase() from languageCode.
2022-02-20 13:49:01 -08:00

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>