1
0
forked from GitHub/gf-core

translator: added support for the Apertium translation service

Also added feedback to source & target language menus to indicate which
languages are supported by the selected translation method. For Apertium, the
source language menu shows all possible source languages, and the target menu
shows the possible target languages for the chosen source language.
This commit is contained in:
hallgren
2012-06-13 21:34:59 +00:00
parent 5fd00972fe
commit d0281ef22f
4 changed files with 155 additions and 53 deletions

View File

@@ -23,14 +23,20 @@ target language. Imported text can be segmented based on punctuation.
Optionally, one can also use line breaks or blank lines to indicate segmentation Optionally, one can also use line breaks or blank lines to indicate segmentation
in imported text. Text can be edited after it has been imported. in imported text. Text can be edited after it has been imported.
<p>
Through menu options, the user sets the source and target language
for the document and chooses which translation method to use by default.
The tool supports two machine translation services:
<ul>
<li>The <a href="http://www.grammaticalframework.org/">GF</a> web service.
The user picks which GF grammar to use from a menu of available grammars.
<li>The <a href="http://www.apertium.org/">Apertium</a> web service.
</ul>
<p>If an unsatisfactory automatic translation is <p>If an unsatisfactory automatic translation is
obtained, the user can click on it and replace it with a manual translation. obtained, the user can click on it and replace it with a manual translation.
If multiple translations are obtained, one of them is shown by default and If multiple translations are obtained, one of them is shown by default and
the other ones are available in a popup menu. the other ones are available in a popup menu.
<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> <p>
The tool handles a set of documents. Documents can be named, saved, The tool handles a set of documents. Documents can be named, saved,
@@ -46,8 +52,8 @@ closed and reopened later. Documents can be saved locally or in the cloud.
<li>Document sharing in the cloud. <li>Document sharing in the cloud.
<li>Interface to other translation services. <li>Interface to other translation services.
<li>Interface to the grammar editor for grammar extension. <li>Interface to the grammar editor for grammar extension.
<li>More browser compatibility testing (Chrome, Firefox &amp; Safari tested <li>More browser compatibility testing (Chrome, Firefox, Safari &amp;
so far). Opera Mobile tested so far).
<li>... <li>...
<li>... <li>...
</ul> </ul>
@@ -55,7 +61,7 @@ closed and reopened later. Documents can be saved locally or in the cloud.
<hr> <hr>
<div class=modtime><small> <div class=modtime><small>
<!-- hhmts start --> Last modified: Tue Jun 12 17:32:46 CEST 2012 <!-- hhmts end --> <!-- hhmts start --> Last modified: Wed Jun 13 23:28:06 CEST 2012 <!-- hhmts end -->
</small></div> </small></div>
<address> <address>
<a href="http://www.cse.chalmers.se/~hallgren/">TH</a> <a href="http://www.cse.chalmers.se/~hallgren/">TH</a>

View File

@@ -71,7 +71,7 @@
</div> </div>
<hr> <hr>
<div class=modtime><small>HMTL <div class=modtime><small>HMTL
<!-- hhmts start --> Last modified: Tue Jun 12 16:08:01 CEST 2012 <!-- hhmts end --> <!-- hhmts start --> Last modified: Wed Jun 13 20:21:22 CEST 2012 <!-- hhmts end -->
</small></div> </small></div>
<a href="about.html">About</a> <a href="about.html">About</a>
@@ -80,6 +80,7 @@
<script type="text/javascript" src="../gfse/cloud2.js"></script> <script type="text/javascript" src="../gfse/cloud2.js"></script>
<script type="text/javascript" src="../gfse/localstorage.js"></script> <script type="text/javascript" src="../gfse/localstorage.js"></script>
<script type="text/javascript" src="translator.js"></script> <script type="text/javascript" src="translator.js"></script>
<script type="text/javascript" src="http://api.apertium.org/JSLibrary.js"></script>
<script type="text/javascript" > <script type="text/javascript" >
var translator = new Translator() var translator = new Translator()
</script> </script>

View File

@@ -27,6 +27,8 @@ table.menubar td:hover, table.menubar dt:hover, dl.popupmenu > dt:hover {
} }
table table dl { left: 6em; } table table dl { left: 6em; }
table.menubar dt { white-space: nowrap; } table.menubar dt { white-space: nowrap; }
dt.unsupported { color: #999; }
div.document { div.document {
/*margin-top: 7ex;*/ /*margin-top: 7ex;*/
clear: both; clear: both;

View File

@@ -15,6 +15,7 @@ function Translator() {
this.server.get_grammarlist(bind(this.extend_methods,this)) this.server.get_grammarlist(bind(this.extend_methods,this))
update_language_menu(this,"source") update_language_menu(this,"source")
update_language_menu(this,"target") update_language_menu(this,"target")
if(apertium) this.add_apertium()
this.redraw(); this.redraw();
} }
@@ -23,7 +24,7 @@ function update_language_menu(t,id) {
clear(dl); clear(dl);
for(var i in languages) { for(var i in languages) {
var l=languages[i] var l=languages[i]
dl.appendChild(wrap("dt",radiobutton(id,l.code,l.name,bind(t.change,t)))) dl.appendChild(dt(radiobutton(id,l.code,l.name,bind(t.change,t))))
} }
} }
@@ -40,19 +41,68 @@ Translator.prototype.redraw=function() {
update_radiobutton("target",o.to) update_radiobutton("target",o.to)
update_radiobutton("view",o.view || "segmentbysegment") update_radiobutton("view",o.view || "segmentbysegment")
update_checkbox("cloud",o.cloud || false) update_checkbox("cloud",o.cloud || false)
if(o.method!="Manual") { switch(o.method) {
case "Manual":
case "Apertium":
t.update_language_menus()
t.update_translations()
break;
default: // GF
function cont2(gr_info) { function cont2(gr_info) {
t.grammar_info=gr_info t.grammar_info=gr_info
t.update_language_menus() // needs grammar_info
t.update_translations() t.update_translations()
} }
function cont1() { t.server.grammar_info(cont2)} function cont1() { t.server.grammar_info(cont2)}
t.server.switch_grammar(o.method,cont1) t.server.switch_grammar(o.method,cont1)
} }
else
t.update_translations()
} }
} }
Translator.prototype.update_language_menus=function() {
var t=this
var o=t.document.options
var method=o.method+(o.method=="Apertium"?"/"+o.from:"")
if(t.menus_for==method) return;
t.menus_for=method
function mark_menu(name,supported) {
var menu=document.forms.options[name]
for(var i=0;i<menu.length;i++)
menu[i].parentNode.parentNode.className=
(supported(menu[i].value)?"":"un")+"supported"
}
function mark_menus(ssupport,tsupport) {
mark_menu("source",ssupport)
mark_menu("target",tsupport)
}
switch(o.method) {
case "Manual":
function yes(code) { return true; }
mark_menus(yes,yes)
break;
case "Apertium":
function ssupport(code) {
return apertium.isTranslatable(alangcode(code))
}
function tsupport(code) {
return apertium.isTranslatablePair(alangcode(o.from),alangcode(code))
}
mark_menus(ssupport,tsupport)
break;
default: // GF
var supported=bind(t.gf_supported,t)
mark_menus(supported,supported)
}
}
Translator.prototype.gf_supported=function(langcode) {
var t=this;
var concname=t.grammar_info.name+langcode
var l=t.grammar_info.languages
for(var i in l) if(l[i].name==concname) return true
return false
}
Translator.prototype.update_translations=function() { Translator.prototype.update_translations=function() {
var t=this var t=this
var doc=t.document var doc=t.document
@@ -61,32 +111,51 @@ Translator.prototype.update_translations=function() {
var ds=t.drawing.segments var ds=t.drawing.segments
var ts=t.drawing.targets var ts=t.drawing.targets
function supported(concname) { function replace(i) {
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] var segment=ss[i]
function replace() { if(ds) {
if(ds) { var sd=t.draw_segment(segment,i)
var sd=t.draw_segment(segment,i) var old=ds[i]
var old=ds[i] ds[i]=sd
ds[i]=sd replaceNode(sd,old)
replaceNode(sd,old)
}
else if(ts) {
clear(ts[i])
ts[i].appendChild(text(segment.target+" "))
}
} }
else if(ts) {
clear(ts[i])
ts[i].appendChild(text(segment.target+" "))
}
}
function update_apertium_translation(i,afrom,ato) {
var segment=ss[i]
function upd3(txts) { function upd3(txts) {
segment.target=txts[0]; segment.target=txts[0];
segment.options={method:o.method,from:o.from,to:o.to} // no sharing! segment.options={method:o.method,from:o.from,to:o.to} // no sharing!
if(txts.length>1) segment.choices=txts if(txts.length>1) segment.choices=txts
else delete segment.choices else delete segment.choices
replace() replace(i)
}
function upd1(res) {
//console.log(translate_output)
if(res.translation) upd3([res.translation])
else upd3(["["+res.error.message+"]"])
}
function upd0(source) { apertium.translate(source,afrom,ato,upd1) }
if(apertium.isTranslatablePair(afrom,ato)) {
if(segment.options.method!="Manual"
&& !eq_options(segment.options,o))
upd0(segment.source)
}
else
upd3(["[Apertium does not support "+show_translation(o)+"]"])
}
function update_gf_translation(i,gfrom,gto) {
var segment=ss[i]
function upd3(txts) {
segment.target=txts[0];
segment.options={method:o.method,from:o.from,to:o.to} // no sharing!
if(txts.length>1) segment.choices=txts
else delete segment.choices
replace(i)
} }
function upd2(ts) { function upd2(ts) {
function unlex(txt,cont) { gfshell('ps -unlextext "'+txt+'"',cont) } function unlex(txt,cont) { gfshell('ps -unlextext "'+txt+'"',cont) }
@@ -103,8 +172,8 @@ Translator.prototype.update_translations=function() {
function upd0(source) { function upd0(source) {
t.server.translate({from:gfrom,to:gto,input:source},upd1) t.server.translate({from:gfrom,to:gto,input:source},upd1)
} }
var fls=supported(gfrom) var fls=t.gf_supported(o.from)
var tls=supported(gto) var tls=t.gf_supported(o.to)
if(fls && tls) { if(fls && tls) {
if(segment.options.method!="Manual" if(segment.options.method!="Manual"
&& !eq_options(segment.options,o)) && !eq_options(segment.options,o))
@@ -121,21 +190,36 @@ Translator.prototype.update_translations=function() {
} }
} }
if(doc.options.method!="Manual") { switch(doc.options.method) {
case "Manual":
break;
case "Apertium":
var afrom=alangcode(o.from)
var ato=alangcode(o.to)
for(var i in ss) update_apertium_translation(i,afrom,ato)
break;
default: // GF
var gname=t.grammar_info.name var gname=t.grammar_info.name
var gfrom=gname+o.from var gfrom=gname+o.from
var gto=gname+o.to var gto=gname+o.to
for(var i in ss) update_translation(i) for(var i in ss) update_gf_translation(i,gfrom,gto)
} }
} }
Translator.prototype.add_apertium=function() {
var dl=element("methods")
if(dl)
dl.appendChild(dt(radiobutton("method","Apertium","Apertium",
bind(this.change,this))))
}
Translator.prototype.extend_methods=function(grammars) { Translator.prototype.extend_methods=function(grammars) {
this.grammars=grammars this.grammars=grammars
var dl=element("methods") var dl=element("methods")
if(dl) for(var i in grammars) { if(dl) for(var i in grammars) {
dl.appendChild(wrap("dt",radiobutton("method",grammars[i], dl.appendChild(dt(radiobutton("method",grammars[i],
"GF: "+grammars[i], "GF: "+grammars[i],
bind(this.change,this)))) bind(this.change,this))))
} }
update_radiobutton("method",this.document.options.method) update_radiobutton("method",this.document.options.method)
} }
@@ -444,9 +528,9 @@ type Segment = { source:String, target:String, options:Options }
type DocOptions = Options & { view:View } type DocOptions = Options & { view:View }
type Options = {from: Lang, to: Lang, method:Method} type Options = {from: Lang, to: Lang, method:Method}
type Lang = String // Eng, Swe, Ita, etc type Lang = String // Eng, Swe, Ita, etc
type Method = "Manual" | GrammarName type Method = "Manual" | "Apertium" | GFGrammarName
type View = "segmentbysegment" | "paralleltexts" type View = "segmentbysegment" | "paralleltexts"
type GrammarName = String // e.g. "Foods.pgf" type GFGrammarName = String // e.g. "Foods.pgf"
*/ */
function eq_options(o1,o2) { function eq_options(o1,o2) {
@@ -522,9 +606,9 @@ Translator.prototype.draw_segment_given_target=function(s,target,i) {
var autoB=radiobutton("method","Auto","Auto",change,!manual) var autoB=radiobutton("method","Auto","Auto",change,!manual)
var manualB=radiobutton("method","Manual","Manual",change,manual) var manualB=radiobutton("method","Manual","Manual",change,manual)
return wrap("form", return wrap("form",
[wrap("dt",autoB), [dt(autoB),
wrap("dt",manualB), dt(manualB),
wrap("dt",draw_translation(o))]) dt(draw_translation(o))])
} }
function draw_options(o) { function draw_options(o) {
@@ -548,26 +632,35 @@ function new_segment(source) {
return { source:source, target:"", options:{} } // leave options empty return { source:source, target:"", options:{} } // leave options empty
} }
function draw_translation(o) { function draw_translation(o) { return text(show_translation(o)) }
return text("("+concname(o.from||"?")+" → "+concname(o.to||"?")+")")
function show_translation(o) {
return "("+concname(o.from||"?")+" → "+concname(o.to||"?")+")"
} }
/* --- Auxiliary functions -------------------------------------------------- */ /* --- Auxiliary functions -------------------------------------------------- */
function lang(code,name) { return { code:code, name:name} } function lang(code,name,code2) { return {code:code, name:name, code2:code2} }
function lang1(name) { function lang1(namecode2) {
var nc=namecode2.split(":");
var name=nc[0]
var ws=name.split("/"); var ws=name.split("/");
return ws.length==1 ? lang(name.substr(0,3),name) : lang(ws[0],ws[1]); var code2=nc.length>1 ? nc[1] : ""
return ws.length==1 ? lang(name.substr(0,3),name,code2)
: lang(ws[0],ws[1],code2);
} }
var languages =
map(lang1,"Amharic Arabic Bulgarian Catalan Danish Dutch English Finnish French German Hindi Ina/Interlingua Italian Jpn/Japanese Latin Norwegian Polish Ron/Romanian Russian Spanish Swedish Thai Turkish Urdu".split(" ")); var languages = // [ISO-639-2 code "/"] language name ":" ISO 639-1 code
map(lang1,"Amharic:am Arabic:ar Bulgarian:bg Catalan:ca Danish:da Dutch:nl English:en Finnish:fi French:fr German:de Hindi:hi Ina/Interlingua:ia Italian:it Jpn/Japanese:ja Latin:la Norwegian:nb Polish:pl Ron/Romanian:ro Russian:ru Spanish:es Swedish:sv Thai:th Turkish:tr Urdu:ur".split(" "));
var langname={}; var langname={};
for(var i in languages) var langcode2={}
for(var i in languages) {
langname[languages[i].code]=languages[i].name langname[languages[i].code]=languages[i].name
langcode2[languages[i].code]=languages[i].code2
}
function concname(code) { return langname[code] || code; } function concname(code) { return langname[code] || code; }
function alangcode(code) { return langcode2[code] || code; }
function tr_local() { function tr_local() {
/* /*