Updated translation tool to accomodate in-place translation of strings. Major thanks to surgecurrent for the code!

This commit is contained in:
Oskar Wiksten
2012-09-13 21:10:13 +02:00
parent af5aa14e11
commit 6d1c5ebe9f
4 changed files with 274 additions and 72 deletions

View File

@@ -111,50 +111,99 @@ function pushMessage(res, msg) {
return res;
}
function compareAndorsTrailResourceRow(result, fieldList, id, obj1, obj2) {
for (var i = 0; i < fieldList._fields.length; ++i) {
var f = fieldList._fields[i];
var fieldName = fieldList.getFieldName(i);
if (f instanceof FieldList) {
fieldName = f._name;
function isTranslatableField(fieldName) {
return fieldName == "name" || fieldName == "logText" || fieldName == "message" || fieldName == "text";
}
function compareAndorsTrailResourceHeader(result, id, header1, header2) {
if (header1.length != header2.length) {
pushMessage(result, "Row \"" + id + "\" was expected to contain " + f1.length + " sub-entries, but only " + f2.length + " was found.");
return;
}
for (var i = 0; i < header1._fields.length; ++i) {
var f1 = header1._fields[i];
var fieldName1 = header1.getFieldName(i);
var f2 = header2._fields[i];
var fieldName2 = header2.getFieldName(i);
if (fieldName1 != fieldName2) {
pushMessage(result, "Row \"" + id + "\", field \"" + fieldName + "\" was expected to contain \"" + f1 + "\", but \"" + f2 + "\" was found.");
}
var isTranslatableField = (fieldName == "name" || fieldName == "logText" || fieldName == "message" || fieldName == "text");
var f1 = obj1[fieldName];
var f2 = obj2[fieldName];
if (f instanceof FieldList) {
if (!f2) { f2 = []; }
if (f1.length != f2.length) {
pushMessage(result, "Row \"" + id + "\", field \"" + fieldName + "\" was expected to contain " + f1.length + " sub-entries, but only " + f2.length + " was found.");
continue;
}
$.each(f1, function(i, obj) {
var id_ = id + ":" + obj[f._fields[0]];
compareAndorsTrailResourceRow(result, f, id_, f1[i], f2[i]);
});
} else {
if (isTranslatableField && f1.length > 1) {
if (f1 == f2) {
pushMessage(result, "Row \"" + id + "\", field \"" + fieldName + "\" does not seem to be translated. Both texts are \"" + f1 + "\".");
}
} else {
if (f1 != f2) {
pushMessage(result, "Row \"" + id + "\", field \"" + fieldName + "\" was expected to contain \"" + f1 + "\", but \"" + f2 + "\" was found.");
}
}
var fieldName2 = header2.getFieldName(i);
if (f1 instanceof FieldList) {
compareAndorsTrailResourceHeader(result, id+":"+fieldName1, f1, f2);
}
}
}
function extractTranslatableFields_(id, result, fieldList, prefix, obj) {
if (!result) {
result = {};
result.id = id;
result.fields = [];
}
if (!prefix) {
prefix = "";
}
for (var i = 0; i < fieldList._fields.length; ++i) {
var f = fieldList._fields[i];
if (f instanceof FieldList) {
// f is subfieldlist
$.each(obj[f._name], function(j, elem) {
extractTranslatableFields_(id, result, f, prefix+f._name+"["+j+"].", elem);
});
} else {
// f is field name
if (isTranslatableField(f)) {
result.fields.push({
"name":prefix+f,
"value":obj[f]
});
}
}
}
return result;
}
function extractTranslatableFields(id, fieldList, obj) {
return extractTranslatableFields_(id, undefined, fieldList, "", obj);
}
function compareAndorsTrailResourceRow(result, fieldList, id, obj1, obj2) {
// Assume the headers of both objects are correctly matched
trans1 = extractTranslatableFields(id, fieldList, obj1);
trans2 = extractTranslatableFields(id, fieldList, obj2);
$.each(trans1.fields, function(i, f1) {
f2 = trans2.fields[i];
if (f1.name != f2.name) {
pushMessage(result, "Row \"" + id + "\", field \"" + f1.name + "\" does not match in translated data field\"" + f2.name + "\".");
}
if (f1.value.length > 1) {
if (f1.value == f2.value) {
pushMessage(result, "Row \"" + id + "\", field \"" + f1.name + "\" does not seem to be translated. Both texts are \"" + f1.value + "\".");
}
}
});
}
function compareAndorsTrailResourceFormat(text1, text2) {
var result = { isResource: true, class1: "ok", class2: "ok", messages: [] };
var result = {
isResource: true,
class1: "ok",
class2: "ok",
messages: [],
header: undefined,
ds_english: undefined,
ds_translated: undefined
};
var header1 = findHeader(text1);
if (!header1) { return { isResource: false }; }
result.header = header1;
var header2 = findHeader(text2);
if (!header2) { result.class2 = "red"; return result; }
compareAndorsTrailResourceHeader(result, "", header1, header2);
if (result.class2 != "ok") { return result; }
var ds1 = new DataStore({});
var ds2 = new DataStore({});
ds1.deserialize(text1);
@@ -163,42 +212,153 @@ function compareAndorsTrailResourceFormat(text1, text2) {
var obj1 = obj;
var obj2 = ds2.get(i);
var id1 = obj1[header1._fields[0]];
if (!obj2) { return pushMessage(result, "Row " + i + ": expected to find an object with id \"" + id1 + "\", but such row was found."); }
if (!obj2) { return pushMessage(result, "Row " + i + ": expected to find an object with id \"" + id1 + "\", but such row was not found."); }
var id2 = obj2[header1._fields[0]];
if (id2 != id1) { return pushMessage(result, "Row " + i + ": Expected to find id \"" + id1 + "\", but found \"" + id2 + "\" instead."); }
compareAndorsTrailResourceRow(result, header1, id1, obj1, obj2);
});
result.ds_english = ds1;
result.ds_translated = ds2;
return result;
}
function applyChangeToObject(trans_obj, fieldName, newValue) {
(new Function("x", "v", "x."+fieldName+" = v;"))(trans_obj, newValue);
}
function appendEditRow(editTable, updateHook, trans_obj, id, fieldName, english_text, translated_text) {
if (english_text.length <= 1) {
// there's no meaningful text
return;
}
var row_id = name+"__row";
var cell_id = name+"__cell";
var edit_id = name+"__edit";
var cell = $("<span />").text(translated_text)
.attr("id", cell_id)
.attr("class", "clickToEdit");
var editor = $("<textarea />").val(translated_text)
.attr("id", cell_id)
.hide();
cell.click(function() {
editor.val(cell.text());
editor.show().focus();
cell.hide();
});
editor.blur(function() {
var new_text = editor.val();
cell.text(new_text);
editor.hide(); cell.show();
applyChangeToObject(trans_obj, fieldName, new_text);
updateHook();
});
editTable.append(
$("<tr />")
.attr("id", name+"__row")
.append($("<td />").text(id))
.append($("<td />").text(fieldName))
.append($("<td />").text(english_text))
.append($("<td />").append(cell).append(editor))
);
}
function clearEditTable() {
var editTable = $("#editTranslation tbody");
editTable.children().remove();
$("#export_text").val("");
}
function editTranslationHandler(data2, name, resourceComparison) {
var updateHook = function () {
var header = resourceComparison.header;
var ds2 = resourceComparison.ds_translated;
var text2 = ds2.serialize();
data2.find("string[name=\"" + name + "\"]").text(text2);
};
return function () {
clearEditTable();
var editTable = $("#editTranslation tbody");
var header = resourceComparison.header;
var ds1 = resourceComparison.ds_english;
var ds2 = resourceComparison.ds_translated;
$.each(ds1.items, function(i, obj1) {
var obj2 = ds2.get(i);
var id = obj1["id"];
var english_list = extractTranslatableFields(id, header, obj1);
var translated_list = extractTranslatableFields(id, header, obj2);
$.each(english_list.fields, function(j, f1) {
var f2 = translated_list.fields[j];
appendEditRow(
editTable, updateHook, obj2, id,
f1.name, f1.value, f2.value
);
});
});
};
}
function initTranslationHandler(data1, data2, name) {
var f = function() {
if (!confirm("Create new translation unit in this resouce?")) {
// if canceled
return;
}
clearEditTable();
// create new node in resource xml by copying english ver.
var node1 = data1.find("string[name=\"" + name + "\"]");
var text1 = node1.text();
data2.find("resources").append($("<string name=\""+name+"\" />").text(text1));
var resourceComparison = compareAndorsTrailResourceFormat(text1, text1);
// repalce this handler with "edit" event handler (and etc.)
var editHandler = editTranslationHandler(data2, name, resourceComparison);
$(this).parent().attr("class","yellow");
$(this).text("Edit").unbind('click', f).click(editHandler);
$(this).click();
}
return f;
}
function exportHandler(data2) {
var xmlString = function (data) {
var xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n";
xml += "<resources>\n";
$.each(data.find("string"), function() {
xml += "\t<string name=\""+$(this).attr("name")+"\">\n";
xml += $(this).text();
xml += "\n\t</string>\n\n";
});
xml += "</resources>\n";
return xml;
}
return function () {
$("#export_text").val(xmlString(data2));
};
}
function appendOutputRow(outputTable, name, data1, data2) {
if ($("#" + name, outputTable).size() > 0) return;
var text1 = data1.find("string[name=\"" + name + "\"]").text();
var text2 = data2.find("string[name=\"" + name + "\"]").text();
var class1 = text1 ? "ok" : "red";
var class2 = text2 ? "ok" : "red";
var tdTranslated = $("<td />");
if (text1 && text2) {
var resourceComparison = compareAndorsTrailResourceFormat(text1, text2);
if (resourceComparison.isResource) {
class1 = resourceComparison.class1;
class2 = resourceComparison.class2;
if (resourceComparison.messages.length > 0) {
var errorList = $("<ul />").attr("id", "validationWarnings");
$.each(resourceComparison.messages, function(i, msg) {
errorList.append($("<li />").text(msg));
});
var d = $("<span />").attr("id", "showValidationWarnings").text("Expand");
d.click(function() {
d.hide();
errorList.show();
});
tdTranslated.append(d);
tdTranslated.append(errorList);
errorList.hide();
if (text1) {
if (text2) {
var resourceComparison = compareAndorsTrailResourceFormat(text1, text2);
if (resourceComparison.isResource) {
class2 = resourceComparison.class2;
if (resourceComparison.messages.length > 0 || class2 == "ok") {
// yellow || ok
var d = $("<span />").text("Edit");
d.click(editTranslationHandler(data2, name, resourceComparison));
tdTranslated.append(d);
}
}
} else /* if (class2 == "red") */ {
var d = $("<span />").text("Init");
d.click(initTranslationHandler(data1, data2, name));
tdTranslated.append(d);
}
}
@@ -206,17 +366,16 @@ function appendOutputRow(outputTable, name, data1, data2) {
$("<tr />")
.attr("id", name)
.append($("<td />").text(name))
.append($("<td />").attr("class", class1))
.append(tdTranslated.attr("class", class2))
);
}
function validateTranslation_(englishData) {
$("#englishData").text(englishData);
var compareData1 = $( englishData );
var compareData2 = $( $("#compareToInput").val() );
var compareData1 = $( $.parseXML(englishData) );
var compareData2 = $( $.parseXML($("#compareToInput").val()) );
var resultTable = $("#validateResultContent table");
var resultTable = $("#validateResultContent #result");
var outputTable = $("tbody", resultTable );
outputTable.empty();
@@ -229,9 +388,9 @@ function validateTranslation_(englishData) {
var sectionCount = $("tr", outputTable).size();
var errors1 = sectionCount - $("td:nth-child(2).ok", outputTable).size();
var errors2 = sectionCount - $("td:nth-child(3).ok", outputTable).size();;
$("th #count1", resultTable).text( (errors1 > 0) ? " (" + errors1 + ")" : "" );
$("th #count2", resultTable).text( (errors2 > 0) ? " (" + errors2 + ")" : "" );
$("th #count2", resultTable).text( (errors1 > 0) ? " (" + errors1 + ")" : "" );
$("#export").click(exportHandler(compareData2));
$("#validateResultContent #loading").hide();
$("#validateResultContent #result").show();
@@ -244,7 +403,6 @@ function validateTranslation(englishData) {
}
}
function loadStep3() {
var compareToContent = $("#compareToInput").val();
if (compareToContent.length <= 0) {
@@ -269,5 +427,12 @@ function startTranslationValidator() {
loadResourceFile( $("#compareToExisting").val(), function(data) { $("#compareToInput").val(data); } );
});
$("#next2").button({ icons: {primary:'ui-icon-arrowthick-1-e'} }).click(loadStep3);
$("#prev3").button({ icons: {primary:'ui-icon-arrowthick-1-w'} }).click(function() { stepLeft("#validateResult", "#validate2"); });
$("#prev3").button({ icons: {primary:'ui-icon-arrowthick-1-w'} }).click(function() {
if (confirm("Leaving the result page will discard any modifications on current resource. Leave anyway?")) {
stepLeft("#validateResult", "#validate2");
clearEditTable();
}
});
$("#export").button({ icons: {primary:'ui-icon-arrowthick-1-e'} })
}

View File

@@ -58,6 +58,7 @@ function DataStore(input) {
alert("Could not find header row, cannot deserialize");
return;
}
this.fieldList = header;
this.items = deserializeObjectList(header, str);
this.onDeserialized();
}

View File

@@ -47,6 +47,7 @@ input[readonly] { color: #888; }
.validateStep h1 { font-size: 1.1em; }
.validateStep h1 .number { font-size: 1.3em; font-weight: bold; text-shadow: rgba(0, 0, 0, 0.3) 1px 1px 1px; }
.validateStep .buttons { padding-top: 10px; }
#validateResultContent table { border-collapse: collapse; margin-bottom: 1ex; }
#validateResultContent table th,td { border: 1px #ccc solid; text-align: left; }
#validateResultContent table tbody tr:nth-child(even) { background-color: #eee }
@@ -55,3 +56,14 @@ input[readonly] { color: #888; }
#validateResultContent table .yellow { background-color: yellow; }
#showValidationWarnings { cursor: pointer; }
#showValidationWarnings:hover { background-color: #d7d7ff; }
.clickToEdit { cursor: pointer; }
.clickToEdit:hover { background-color: #d7d7ff; }
#validateResultContent { width: 400px; float: left;d }
#editorForms { width: 670px; float: right;}
#editorForms #translation_edit_list { table-layout: fixed; }
#editorForms #translation_edit_list tr:nth-child(1) { width: 17em }
#editorForms #translation_edit_list tr:nth-child(2) { width: 8em }
#editorForms #translation_edit_list textarea { width: 100%; height: 8em; }

View File

@@ -61,22 +61,46 @@
<div id="validateResult" class="validateStep" style="display: none;">
<h1><span class="number">3.</span> Results</h1>
<div id="validateResultContent">
<div id="loading">Loading &amp; validating</div>
<table id="result">
<thead><tr>
<th>id</th>
<th>English<span id="count1" /></th>
<th>Translated<span id="count2" /></th>
</tr></thead>
<tbody>
</tbody>
</table>
<div>
<div id="validateResultContent">
<fieldset class="fieldSet">
<legend>Validation Result</legend>
<div id="loading">Loading &amp; validating</div>
<table id="result">
<thead><tr>
<th>id</th>
<th>Result<span id="count2" /></th>
</tr></thead>
<tbody>
</tbody>
</table>
</fieldset>
</div>
<div id="editorForms">
<fieldset class="fieldSet" id="editTranslation">
<legend>Edit translation</legend>
<table id="translation_edit_list">
<thead><tr>
<th>id</th>
<th>field</th>
<th>English</th>
<th>Translated</th>
</tr></thead>
<tbody>
</tbody>
</table>
</fieldset>
</div>
<div style="floar:clear;"></div>
</div>
<div class="buttons">
<span id="prev3">Back</span>
<span id="export">Export</span>
</div>
<div class="export_area">
<p>Translated Content:</p>
<textarea id="export_text" rows=8 cols=80></textarea>
</div>
</div>
</div>