325 lines
7.7 KiB
HTML
325 lines
7.7 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>TS100 Translation Parser</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 = {};
|
|
var langMap = {};
|
|
var lang;
|
|
|
|
var defMsgMap;
|
|
var defCharMap;
|
|
var defGrpMap;
|
|
var defOptMap;
|
|
|
|
function save(langCode){
|
|
saveJSON(langMap[langCode], "translation_"+langCode.toLowerCase()+".json");
|
|
}
|
|
|
|
function view(langCode){
|
|
showJSON(langMap[langCode], "translation_"+langCode.toLowerCase()+".json");
|
|
}
|
|
|
|
function translationFileSelected(e) {
|
|
var target = e;
|
|
var id = target.id;
|
|
|
|
var file = target.files[0];
|
|
if (!file) {
|
|
return;
|
|
}
|
|
var fr = new FileReader();
|
|
fr.onload = function(e) {
|
|
parseTranslationFile(file.name, e.target.result);
|
|
}
|
|
fr.readAsText(file);
|
|
|
|
}
|
|
|
|
function parseTranslationFile(name, src) {
|
|
// remove multiline comments
|
|
src = src.replace(/\/\*[\s\S.]*?\*\//mg, "");
|
|
// remove single-line comments
|
|
src = src.replace(/\/\/.*/mg, "");
|
|
// remove empty lines
|
|
src = src.replace(/^\s*\n/gm, "");
|
|
|
|
var langCode = "";
|
|
var srcLines = src.split("\n");
|
|
|
|
var reMessage = /const\s+char\s*\*\s+([\w\d]+)\s*=\s*"(.*)"/;
|
|
var reSettingsDescStart = /const\s+char\s*\*\s+SettingsDescriptions\[/;
|
|
var reSettingsNamesStart = /const\s+char\s*\*\s+SettingsShortNames\[/;
|
|
var reSettingsMenuDescStart = /const\s+char\s*\*\s+SettingsMenuEntriesDescriptions\[/;
|
|
var reChar = /const\s+char\s+([\w\d]+)\s*=\s*'(\w)'/;
|
|
var reMenuMode = /SettingsShortNameType\s*=\s*SHORT_NAME_(\w+)_LINE/;
|
|
|
|
var reMenuStart = /\s*const\s+char\s*\*\s+SettingsMenuEntries\[/;
|
|
|
|
// var reString = /^\s*"(.*)"/;
|
|
var reString = /"(.*)"/;
|
|
var reSingleLine = /{\s*"(.*)"\s*}/;
|
|
var reDoubleLine = /{\s*"(.*)"\s*,\s*"(.*)"\s*}/;
|
|
|
|
var mode = '';
|
|
var entryIndex = 0;
|
|
for (var li = 0; li < srcLines.length; li++) {
|
|
// trim lines
|
|
line = srcLines[li] = srcLines[li].trim();
|
|
|
|
// if entering a new lang block
|
|
if (startsWith(line, "#ifdef LANG_")) {
|
|
mode = 'new-language';
|
|
langCode = line.substring(12);
|
|
lang = langMap[langCode];
|
|
// use existing or instantiate new
|
|
if (!isDefined(lang)) {
|
|
lang = {
|
|
languageCode: langCode,
|
|
cyrillicGlyphs: false,
|
|
messages: {},
|
|
characters: {},
|
|
menuDouble : false,
|
|
menuGroups: {},
|
|
menuOptions: {}
|
|
};
|
|
langMap[langCode] = lang;
|
|
app.languages[app.languages.length] = langCode;
|
|
}
|
|
entryIndex = 0;
|
|
continue;
|
|
}
|
|
|
|
// Use Cyrillic glyphs
|
|
if (startsWith(line, "#define CYRILLIC_GLYPHS")) {
|
|
lang.cyrillicGlyphs = true;
|
|
entryIndex = 0;
|
|
continue;
|
|
}
|
|
|
|
// Menu type
|
|
reMenuMode.lastIndex = 0;
|
|
match = reMenuMode.exec(line);
|
|
if (match) {
|
|
lang.menuDouble = match[1] == 'DOUBLE';
|
|
entryIndex = 0;
|
|
continue;
|
|
}
|
|
|
|
// Messages
|
|
reMessage.lastIndex = 0;
|
|
match = reMessage.exec(line);
|
|
if (match) {
|
|
lang.messages[match[1]] = xunescape(match[2]);
|
|
entryIndex = 0;
|
|
continue;
|
|
}
|
|
|
|
// Chars descriptions
|
|
reChar.lastIndex = 0;
|
|
match = reChar.exec(line);
|
|
if (match) {
|
|
// found description block start
|
|
mode = 'char';
|
|
lang.characters[match[1]] = xunescape(match[2]);
|
|
entryIndex = 0;
|
|
continue;
|
|
}
|
|
// Settings descriptions
|
|
reSettingsDescStart.lastIndex = 0;
|
|
match = reSettingsDescStart.exec(line);
|
|
if (match) {
|
|
// found description block start
|
|
mode = 'settingsDesc';
|
|
entryIndex = 0;
|
|
continue;
|
|
}
|
|
reSettingsNamesStart.lastIndex = 0;
|
|
match = reSettingsNamesStart.exec(line);
|
|
if (match) {
|
|
// found description block start
|
|
mode = 'settingsNames';
|
|
entryIndex = 0;
|
|
continue;
|
|
}
|
|
reMenuStart.lastIndex = 0;
|
|
match = reMenuStart.exec(line);
|
|
if (match) {
|
|
// found description block start
|
|
mode = 'menu';
|
|
entryIndex = 0;
|
|
continue;
|
|
}
|
|
reSettingsMenuDescStart.lastIndex = 0;
|
|
match = reSettingsMenuDescStart.exec(line);
|
|
if (match) {
|
|
// found description block start
|
|
mode = 'menuDesc';
|
|
entryIndex = 0;
|
|
continue;
|
|
}
|
|
|
|
if (mode == 'menu') {
|
|
// processing menu group names
|
|
reString.lastIndex = 0;
|
|
match = reString.exec(line);
|
|
if (match) {
|
|
// found description string
|
|
var entry = getMenuGroup(entryIndex);
|
|
var m = match[1].split("\\n");
|
|
entry.text2[0] = xunescape(m[0]);
|
|
entry.text2[1] = xunescape(m[1]);
|
|
entryIndex++;
|
|
}
|
|
} else if (mode == 'menuDesc') {
|
|
// processing menu group descriptions
|
|
reString.lastIndex = 0;
|
|
match = reString.exec(line);
|
|
if (match) {
|
|
// found description string
|
|
var entry = getMenuGroup(entryIndex);
|
|
entry.desc = xunescape(match[1]);
|
|
entryIndex++;
|
|
}
|
|
} else if (mode == 'settingsDesc') {
|
|
// processing option descriptions
|
|
reString.lastIndex = 0;
|
|
match = reString.exec(line);
|
|
if (match) {
|
|
// found description string
|
|
var entry = getMenuOption(entryIndex);
|
|
entry.desc = xunescape(match[1]);
|
|
entryIndex++;
|
|
}
|
|
} else if (mode == 'settingsNames') {
|
|
reDoubleLine.lastIndex = 0;
|
|
match = reDoubleLine.exec(line);
|
|
if (match) {
|
|
var entry = getMenuOption(entryIndex);
|
|
entry.text2[0] = xunescape(match[1]);
|
|
entry.text2[1] = xunescape(match[2]);
|
|
entryIndex++;
|
|
} else {
|
|
reSingleLine.lastIndex = 0;
|
|
match = reSingleLine.exec(line);
|
|
if (match) {
|
|
var entry = getMenuOption(entryIndex);
|
|
entry.text = xunescape(match[1]);
|
|
entryIndex++;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
app.done = 1;
|
|
}
|
|
|
|
function getMenuOption(entryIndex) {
|
|
var optionDef = def.menuOptions[entryIndex];
|
|
if (!isDefined(optionDef)) {
|
|
var s = "Could not find menu option with index "+entryIndex;
|
|
alert(s);
|
|
throw s;
|
|
}
|
|
var id = optionDef.id;
|
|
var entry = lang.menuOptions[id];
|
|
if (!isDefined(entry)) {
|
|
entry =
|
|
{
|
|
"text": "",
|
|
"text2": ["", ""],
|
|
"desc": ""
|
|
}
|
|
lang.menuOptions[id] = entry;
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
function getMenuGroup(entryIndex) {
|
|
var optionDef = def.menuGroups[entryIndex];
|
|
if (!isDefined(optionDef)) {
|
|
var s = "Could not find menu group with index "+entryIndex;
|
|
alert(s);
|
|
throw s;
|
|
}
|
|
var id = optionDef.id;
|
|
var entry = lang.menuGroups[id];
|
|
if (!isDefined(entry)) {
|
|
entry =
|
|
{
|
|
"text2": ["", ""],
|
|
"desc": ""
|
|
}
|
|
lang.menuGroups[id] = entry;
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
function markSaved(lang) {
|
|
document.getElementById("row_"+lang).classList.add("saved");
|
|
}
|
|
|
|
function loaded() {
|
|
app = new Vue({
|
|
el : '#app',
|
|
data : {
|
|
languages: [],
|
|
done : false,
|
|
def : {
|
|
}
|
|
|
|
},
|
|
methods : {
|
|
vSave : function(lang) {
|
|
save(lang);
|
|
markSaved(lang);
|
|
},
|
|
vView : function(lang) {
|
|
view(lang);
|
|
markSaved(lang);
|
|
}
|
|
}
|
|
});
|
|
|
|
app.def = def;
|
|
defMsgMap = copyArrayToMap(app.def.messages);
|
|
defCharMap = copyArrayToMap(app.def.characters);
|
|
defGrpMap = copyArrayToMap(app.def.menuGroups);
|
|
defOptMap = copyArrayToMap(app.def.menuOptions);
|
|
}
|
|
|
|
window.onload=loaded;
|
|
</script>
|
|
<link href="translations.css" rel="stylesheet" type="text/css">
|
|
</head>
|
|
<body>
|
|
|
|
<div id="app">
|
|
<h1>TS100 Translation Parser</h1>
|
|
<table class="header data">
|
|
<tr>
|
|
<td class="label">Translation.cpp</td>
|
|
<td class="value">
|
|
<input type="file" id="translation-cpp-file" onchange="translationFileSelected(this)" accept=".cpp">
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<div class="data" v-if="done">
|
|
<div class="value" v-for="lang in languages" :id="'row_'+lang">
|
|
<input type="button" :value="'Save '+lang" v-on:click="vSave(lang)">
|
|
<input type="button" :value="'View '+lang" v-on:click="vView(lang)">
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</body>
|
|
</html> |