diff options
| author | hallgren <hallgren@chalmers.se> | 2012-05-15 15:36:06 +0000 |
|---|---|---|
| committer | hallgren <hallgren@chalmers.se> | 2012-05-15 15:36:06 +0000 |
| commit | 66e6b5269d52c1bedfa805c51b300ce5f5f3c490 (patch) | |
| tree | acc5706d4bd14d119e4ba7570201ea12bcf06aae /src | |
| parent | e90e1202c468950555c5362a00d5d5f75ab052c1 (diff) | |
Adding a Simple Translation Tool
It is part of the cloud services available with gf -server.
Diffstat (limited to 'src')
| -rw-r--r-- | src/www/index.html | 1 | ||||
| -rw-r--r-- | src/www/translator/about.html | 55 | ||||
| -rw-r--r-- | src/www/translator/index.html | 85 | ||||
| -rw-r--r-- | src/www/translator/translator.css | 54 | ||||
| -rw-r--r-- | src/www/translator/translator.js | 429 |
5 files changed, 624 insertions, 0 deletions
diff --git a/src/www/index.html b/src/www/index.html index 499f4be37..0c1261590 100644 --- a/src/www/index.html +++ b/src/www/index.html @@ -15,6 +15,7 @@ <li><a href="minibar/minibar.html">Minibar</a> <li><a href="TransQuiz/translation_quiz.html">Translation Quiz</a> <li><a href="gfse/">GF online editor for simple multilingual grammars</a> + <li><a href="translator/">Simple Translation Tool</a> </ul> <h2>Some Documentation</h2> diff --git a/src/www/translator/about.html b/src/www/translator/about.html new file mode 100644 index 000000000..395173162 --- /dev/null +++ b/src/www/translator/about.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html> <head> +<title>About: Simple Translation Tool</title> +<link rel="stylesheet" type="text/css" href="../gfse/editor.css" title="Cloud"> +<link rel="alternate stylesheet" type="text/css" href="../gfse/molto.css" title="MOLTO"> + +<link rel=author href="http://www.cse.chalmers.se/~hallgren/" title="Thomas Hallgren"> + +<meta name = "viewport" content = "width = device-width"> +<meta http-equiv="Content-type" content="text/html;charset=UTF-8"> +<meta charset="UTF-8"> +</head> + +<body> +<h1>About the Simple Translation Tool</h1> + +<p> +This is a simple bilingual document editor. Documents consist of a sequence +of segments that are translated independently. The user can add segments +in the source language and obtain automatically translated segments in +the target language. If an unsatisfactory automatic translation is +obtained, the user can click on it and replace it with a manual translation. + +<p> +The GF web service is used for automatic translation. The user picks which +grammar to use from a menu of available grammars. Through menu options, +the user also sets the source and target language for the document. + +<p> +The tool handles a set of documents. Documents can be named, saved (locally), +closed and reopened later. + +<h2>TODO</h2> +<ul> + <li>Test for browser compatibility (Safari & Firefox tested so far). + <li>Use GF lexer/unlexer to allow for more natural looking text. + <li>Import/export text. + <li>Cloud service. + <li>Interface to other translation services. + <li>Interface to grammar editor for grammar extension. + <li>... + <li>... +</ul> + + +<hr> +<div class=modtime><small> +<!-- hhmts start --> Last modified: Tue May 15 17:35:39 CEST 2012 <!-- hhmts end --> + </small></div> +<address> +<a href="http://www.cse.chalmers.se/~hallgren/">TH</a> +<img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt=""> +</address> +</body> + </html> diff --git a/src/www/translator/index.html b/src/www/translator/index.html new file mode 100644 index 000000000..7f3d46a22 --- /dev/null +++ b/src/www/translator/index.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<html> <head> +<title>Simple Translation Tool</title> +<link rel="stylesheet" type="text/css" href="../gfse/editor.css" title="Cloud"> +<link rel="stylesheet" type="text/css" href="translator.css" title="Cloud"> +<meta name = "viewport" content = "width = device-width"> +<meta charset="UTF-8"> +</head> + +<body> +<div class=pagehead> +<h1>Simple Translation Tool</h1> +<form name=options> +<table class=menubar> + <tr><td>File + <dl> + <dt onclick="translator.new(this)">New + <dt onclick="translator.browse(this)">Open... + <dt onclick="translator.save(this)">Save + <dt onclick="translator.saveAs(this)">Save As... + <dt onclick="translator.close(this)">Close + </dl> + <td>Edit + <dl> + <dt onclick="translator.import(this)">Add a segment... + <dt onclick="translator.remove(this)">Remove the last segment + </dl> + <td>View + <dl> + <dt><label><input type=radio checked>Segment by segment</label> + </dl> + <td>Options + <dl> + <dt> + <table class=submenu> + <tr><td>Source Language + <dl id=source> + <dt><label><input name=source value=Eng type=radio onchange="translator.change(this)">English</label> + <dt><label><input name=source value=Swe type=radio onchange="translator.change(this)">Swedish</label> + <dt><label><input name=source value=Ita type=radio onchange="translator.change(this)">Italian</label> + </dl> + </table> + <dt> + <table class=submenu> + <tr><td>Target Language + <dl id=target> + <dt><label><input name=target value=Eng type=radio onchange="translator.change(this)">English</label> + <dt><label><input name=target value=Swe type=radio onchange="translator.change(this)">Swedish</label> + <dt><label><input name=target value=Ita type=radio onchange="translator.change(this)">Italian</label> + </dl> + </table> + <dt> + <table class=submenu> + <tr><td>Default translation method + <dl id=methods> + <dt><label><input name=method value=Manual type=radio onchange="translator.change(this)">Manual</label> + </dl> + </table> + </dl> +</table> +</form> +</div> + +<div id=document class=document> +... + <noscript>This document translation editor requires JavaScript to work + </noscript> +</div> +<hr> +<div class=modtime><small> +<!-- hhmts start --> Last modified: Tue May 15 16:17:32 CEST 2012 <!-- hhmts end --> +</small></div> +<a href="about.html">About</a> + +<script type="text/javascript" src="../minibar/support.js"></script> +<script type="text/javascript" src="../minibar/pgf_online.js"></script> +<!-- +<script type="text/javascript" src="../gfse/cloud2.js"></script> +--> +<script type="text/javascript" src="translator.js"></script> +<script type="text/javascript" > +var translator = new Translator() +</script> +</body> +</html> diff --git a/src/www/translator/translator.css b/src/www/translator/translator.css new file mode 100644 index 000000000..dff8d103a --- /dev/null +++ b/src/www/translator/translator.css @@ -0,0 +1,54 @@ +body { margin: 5px; } +h1 { float: right; margin: 0; font-size: 150%; } +h2 { font-size: 120%; } +div.pagehead { font-family: sans-serif; + background-color: #ccc; +} +table.menubar td { padding: 5px; } +table.menubar dl, td.options > div > dl { + z-index: 1; + display: none; position: absolute; + background: white; color: black; + border: 1px solid black; + margin: 0; + box-shadow: 5px 5px 5px rgba(0,0,0,0.25); +} +table.menubar td:hover > dl { display: block; } +table.menubar dt { margin: 0; padding: 5px; } +table.submenu dt { padding: 0; } +table.menubar td:hover, table.menubar dt:hover { background-color: #36f; color: white; } +table table dl { left: 6em; } +table.menubar dt { white-space: nowrap; } +div.document { + clear: both; + background: white; + border: 2px solid #009; + padding: 0.6ex; +} + +div.document h2 { color: #009; } + +table.segments { margin-left: auto; margin-right: auto; } +tr.segment:hover { background: #ffc; } + +td.source, td.options, td.target { + padding: 1ex; +} +td.source, td.target { + border-bottom: 2px solid #ccc; +} +td.options > div { position: relative; margin: 0; } +td.options:hover > div > dl { display: block; } +td.options > div > dl { + left: 0.8em; + padding: 0.6ex; + font-family: sans-serif; + white-space: nowrap; +} + +td.target input[name=it] { + width: 100%; font-family: inherit; font-size: inherit; +} + +span.arrow { color: blue; } +span.error { color: red; }
\ No newline at end of file diff --git a/src/www/translator/translator.js b/src/www/translator/translator.js new file mode 100644 index 000000000..a599b8d4a --- /dev/null +++ b/src/www/translator/translator.js @@ -0,0 +1,429 @@ + + +/* --- Translator object ---------------------------------------------------- */ + +function Translator() { + this.local=tr_local(); + this.view=element("document") + this.current=this.local.get("current") + this.document=this.current && this.current!="/" && this.local.get("/"+this.current) || empty_document() + this.server=pgf_online({}) + this.server.get_grammarlist(bind(this.extend_methods,this)) + update_language_menu(this,"source") + update_language_menu(this,"target") + this.redraw(); +} + +function update_language_menu(t,id) { + var dl=element(id); + clear(dl); + for(var i in languages) { + var l=languages[i] + dl.appendChild(wrap("dt",radiobutton(id,l.code,l.name,bind(t.change,t)))) + } +} + +Translator.prototype.redraw=function() { + var t=this; + if(t.current=="/") t.browse() + else { + t.drawing=t.draw_document() + clear(t.view) + appendChildren(t.view,t.drawing.doc) + var o=t.document.options + update_radiobutton("method",o.method) + update_radiobutton("source",o.from) + update_radiobutton("target",o.to) + if(o.method!="Manual") { + function cont2(gr_info) { + t.grammar_info=gr_info + t.update_translations() + } + function cont1() { t.server.grammar_info(cont2)} + t.server.switch_grammar(o.method,cont1) + } + else + t.update_translations() + } +} + +Translator.prototype.update_translations=function() { + var t=this + var doc=t.document + var o=doc.options + var ss=doc.segments + var ds=t.drawing.segments + + function supported(concname) { + var l=t.grammar_info.languages + for(var i in l) if(l[i].name==concname) return true + return false + } + + function update_translation(i) { + var segment=ss[i] + function replace(sd) { + var old=ds[i] + ds[i]=sd + replaceNode(sd,old) + } + function upd2(ts) { + switch(ts.length) { + case 1: segment.target=ts[0]; break; + case 0: segment.target="[no translation]";break; + default: segment.target="[ambiguous translation]" + } + segment.options=JSON.parse(JSON.stringify(o)) // no sharing! + replace(t.draw_segment(segment,i)) + } + function upd(translate_output) { + //console.log(translate_output) + var ts=collect_texts(translate_output[0].translations) + upd2(ts) + } + var fs=supported(gfrom) + var ts=supported(gto) + if(fs && ts) { + if(segment.options.method!="Manual" + && JSON.stringify(segment.options)!=JSON.stringify(o)) + t.server.translate({from:gfrom,to:gto,input:segment.source},upd) + } + else { + var fn=concname(o.from) + var tn=concname(o.to) + var unsup=" is not supported by the grammar" + var sup=" is supported by the grammar" + var msg= fs ? tn+unsup : ts ? fn+unsup : + "Neither "+fn+" nor "+tn+sup + upd2(["["+msg+"]"]) + } + } + + if(doc.options.method!="Manual") { + var gname=t.grammar_info.name + var gfrom=gname+o.from + var gto=gname+o.to + for(var i in ss) update_translation(i) + } +} + +Translator.prototype.extend_methods=function(grammars) { + this.grammars=grammars + var dl=element("methods") + if(dl) for(var i in grammars) { + dl.appendChild(wrap("dt",radiobutton("method",grammars[i], + "GF: "+grammars[i], + bind(this.change,this)))) + } + update_radiobutton("method",this.document.options.method) +} + +Translator.prototype.change=function(el) { + var t=this + var o=t.document.options; + function update(field) { + if(el.value!=o[field]) { + o[field]=el.value + t.redraw() + } + } + switch(el.name) { + case "method": update("method"); break; + case "source": update("from"); break; + case "target": update("to"); break; + } +} + +Translator.prototype.new=function(el) { + hide_menu(el); + this.current=null; + this.local.put("current",null) + this.document=empty_document() + this.redraw(); +} + +Translator.prototype.browse=function(el) { + hide_menu(el); + var t=this + function browse() { + var ul=empty_class("ul","files") + var pre=t.local.prefix+"/" + for(var i in localStorage) { + if(hasPrefix(i,pre)) { + var name=i.substr(pre.length) + var link=a(jsurl("translator.open('"+name+"')"),[text(name)]) + ul.appendChild(li(link)) + } + } + clear(t.view) + t.view.appendChild(wrap("h2",text("Your translator documents"))) + t.view.appendChild(ul) + t.current="/" + t.local.put("current","/") + } + setTimeout(browse,100) // leave time to hide the menu first +} + +Translator.prototype.open=function(name) { + var t=this + if(name) { + var path="/"+name + var document=t.local.get(path); + if(document) { + t.current=name; + t.local.put("current",name) + t.document=document; + t.redraw(); + } + else alert("No such document: "+name) + } +} + +Translator.prototype.save=function(el) { + hide_menu(el); + if(this.current!="/") { + if(this.current) this.local.put("/"+this.current,this.document) + else this.saveAs() + } +} + +Translator.prototype.saveAs=function(el) { + hide_menu(el); + if(this.current!="/") { + var name=prompt("File name?") + if(name) { + this.current=this.document.name=name; + this.local.put("current",name) + this.save(); + this.redraw(); + } + } +} + +Translator.prototype.close=function(el) { + hide_menu(el); + this.browse(); +} + +Translator.prototype.import=function(el) { + hide_menu(el); + var t=this + function imp() { + var text=prompt("Text segment to import?") + if(text) { + t.document.segments.push(new_segment(text)) + t.redraw(); + } + } + setTimeout(imp,100) +} +Translator.prototype.remove=function(el) { + hide_menu(el); + var t=this + function rm() { + if(t.document && t.document.segments.length>0) { + t.document.segments.pop(); + t.redraw(); + } + } + setTimeout(rm,100) +} + +Translator.prototype.edit_translation=function(i) { + var t=this + var ds=t.drawing.segments + + function replace_segment(sd) { + var old=ds[i] + ds[i]=sd + replaceNode(sd,old) + } + + function edit_segment(s) { + function restore() { replace_segment(t.draw_segment(s,i)) } + function done() { + s.options.method="Manual" + s.options.from=t.document.options.from + s.options.to=t.document.options.to + s.target=inp.value // side effect, updating the document in-place + restore(); + return false; + } + var inp=node("input",{name:"it",value:s.target}) + var e=wrap("form",[inp, submit(), button("Cancel",restore)]) + var target=wrap_class("td","target",e) + var edit=t.draw_segment_given_target(s,target) + replace_segment(edit) + e.onsubmit=done + inp.focus(); + } + edit_segment(t.document.segments[i]) +} + +function hide_menu(el) { + function disp(s) { el.parentNode.style.display=s||""; } + if(el) { + disp("none") + setTimeout(disp,500) + } +} + +/* --- Documents ------------------------------------------------------------ */ + +/* +type Document = { name:String, options: Options, segments:[Segment] } +type Segment = { source:String, target:String, options:Options } +type Options = {from: Lang, to: Lang, method:Method} +type Lang = String // Eng, Swe, Ita, etc +type Method = "Manual" | GrammarName +type GrammarName = String // e.g. "Foods.pgf" +*/ + +Translator.prototype.draw_document=function() { + var t=this + var doc=t.document + var o=doc.options; + var segments=mapix(bind(t.draw_segment,t),doc.segments) + var drawing=[node("h2",{},[text(doc.name),text(" "), + wrap("small",draw_translation(o))]), + wrap_class("table","segments",segments)] + return {doc:drawing,segments:segments} +} + +Translator.prototype.draw_segment=function(s,i) { + var t=this + var dopt=t.document.options + var opt=s.options + var txt=text(s.target) + if(opt.from!=dopt.from || opt.to!=dopt.to) txt=span_class("error",txt) + var target=wrap_class("td","target",txt) + function edit() { t.edit_translation(i) } + target.onclick=edit + return t.draw_segment_given_target(s,target) +} + +Translator.prototype.draw_segment_given_target=function(s,target) { + var t=this + + function draw_options2(o) { + function change(el) { + o.method=el.value // side effect, updating the document in-place + t.update_translations() + } + var manual=o.method=="Manual" + var autoB=radiobutton("method","Auto","Auto",change,!manual) + var manualB=radiobutton("method","Manual","Manual",change,manual) + return wrap("form", + [wrap("dt",autoB), + wrap("dt",manualB), + wrap("dt",draw_translation(o))]) + } + + function draw_options(o) { + return wrap("div",[span_class("arrow",text(" ⇒ ")), + wrap("dl",draw_options2(o))]) + } + + return wrap_class("tr","segment", + [wrap_class("td","source",text(s.source)), + wrap_class("td","options",draw_options(s.options)), + target]) +} + +function empty_document() { + return { name:"Unnamed", + options: {from:"Eng", to:"Swe", method:"Manual"}, + segments:[] } +} + +function new_segment(source) { + return { source:source, target:"", options:{} } // leave options empty +} + +function draw_translation(o) { + return text("("+concname(o.from||"?")+" → "+concname(o.to||"?")+")") +} + +/* --- Auxiliary functions -------------------------------------------------- */ + +function lang(code,name) { return { code:code, name:name} } +function lang1(name) { + var ws=name.split("/"); + return ws.length==1 ? lang(name.substr(0,3),name) : lang(ws[0],ws[1]); +} +var languages = + map(lang1,"Amharic Arabic Bulgarian Catalan Danish Dutch English Finnish French German Hindi Ina/Interlingua Italian Japanese Latin Norwegian Polish Ron/Romanian Russian Spanish Swedish Thai Turkish Urdu".split(" ")); + +var langname={}; +for(var i in languages) + langname[languages[i].code]=languages[i].name + +function concname(code) { return langname[code] || code; } + + +function tr_local() { + var prefix="gf.translator." + function dummy() { + return { + prefix: prefix, + get: function(name,def) { return def }, + put: function(name,value) { } + } + } + function real() { + return { + prefix: prefix, + get: function (name,def) { + var id=prefix+name + return localStorage[id] ? JSON.parse(localStorage[id]) : def; + }, + put: function (name,value) { + var id=prefix+name; + localStorage[id]=JSON.stringify(value); + } + } + } + return window.localStorage ? real() : dummy() +} + +// Collect alternative texts in the output from PGF service translate command +function collect_texts(ts) { + var list=[] + for(var i in ts) + for(var j in ts[i].linearizations) { + var t=ts[i].linearizations[j].text + if(!elem(t,list)) list.push(t) // avoid duplicates + } + return list +} + +function mapix(f,xs) { + var ys=[]; + for(var i in xs) ys.push(f(xs[i],i)) + return ys; +} + +/* --- DOM Support ---------------------------------------------------------- */ + +function a(url,linked) { return node("a",{href:url},linked); } +function li(xs) { return wrap("li",xs); } +function jsurl(js) { return "javascript:"+js; } + +function replaceNode(node,ref) { ref.parentNode.replaceChild(node,ref) } + +function radiobutton(name,value,label,change,checked) { + var b=node("input",{type:"radio",name:name,value:value}) + if(change) b.onchange=function(event) { return change(event.target) } + if(checked) b.checked=true + return wrap("label",[b,text(label)]) +} + +function update_radiobutton(name,value) { + var bs=document.forms.options[name] + if(bs.length) + for(var i in bs) if(bs[i].value==value) bs[i].checked=true +} + +function submit(label) { + return node("input",{type:"submit",value:label||"OK"}) +} |
