Launching external apps and scripts from OmegaT

OmegaT can launch external programs and scripts and pass certain project-specific data as parameters, and this is quite useful.
There are three ways to do this:

  1. External searches (global or project specific)
  2. Post-processing commands (global or project specific)
  3. OmegaT scripts

External searches can pass the text selected in OmegaT’s editor to the web browser as a URL to open. Such URLs consist of a fixed part (e.g. https://en.wikipedia.org/wiki/) and the selected text inserted somewhere in the URL instead of the placeholder ({target} in the External Search configuration). It’s also possible to open any other program instead of a web browser. This makes sense if the program you want to run is a dictionary application or other reference software that can accept parameters from standard input. An excellent example of such software is Goldendict.
What this approach lacks is the capability to pass other useful information, such as project languages or file locations. Not a big problem, especially if you are translating from one or two languages. But even with two, if you want to use a multilingual resource, it becomes necessary to create a separate external searches for each language, though the difference often is just a language code. There are ways to work around this limitation, but I’ll perhaps discuss that another time.

Post-processing commands have a much bigger list of supported variables, and they are great for tasks like file format conversion or live preview. Despite their versatility, these commands cannot receive input directly from OmegaT’s editor. Moreover, they only execute when generating target files, making them less suitable for tasks like lookups or note-taking that may need to be performed multiple times even while working on a single segment.

With OmegaT scripts it’s possible to execute an external application and pass almost anything related to the project: source and target text of the current segment, selection in the editor, paths to the various files in the project that OmegaT is aware of, project languages along with their country variants, current file, and more.
Years ago I did write a script exactly for that purpose, and I think it was my very first script in Groovy. It’s quite straightforward: it only collects a whole bunch of variables and passes them to an external script or app that can decide how to handle them. That external part was written with Linux in mind. GNU/Linux readily provides Bash shell (or a number of other shells to chose from) that makes it possible to combine multiple components into a practical routine tailored to specific requirements, and Zenity, a handy utility that simplifies the creation of basic GUI dialog windows for seamless interaction with Bash scripts. The concept was very simple: OmegaT gets all the needed variables and launches my Bash+Zenity script. This script, in turn, presents a list of various actions, and once a choice is made, the corresponding action is executed. It worked beautifully (at least according to my definition of beauty). Over time, the list of actions expanded, and I relied on it daily.
Recently I switched to macOS, and at first those thing were badly missed while working in OmegaT. But it turned out that Zenity is available through Homebrew, while Bash is already available out of the box. Thus, the only task remaining was making sure my old creation is compatible with both operating systems.

So here I present that combination now working equally well on Linux and macOS.

Nota bene:

  • <omegat_config> below refers to OmegaT configuration folder. You can access it by pressing OptionsAccess Configuration Folder in OmegaT.

  • If the window called Scripting that pops up by pressing ToolsScripting is empty for some reason, you may want to go to <omegat_config> and create a folder called scripts there (note the plural).
    Then in the Scripting window, click FileSet Scripts Folder…, and select the newly created folder (under Linux and Mac, you can simply drag the folder onto buttons in the file chooser, and that folder will be selected automatically)

  • Any new scripts should be placed into this folder. If you need any of the scripts bundled with OmegaT (there are a few useful ones), copy them over from where OmegaT is installed (OmegaT.app/Contents/Java/scripts on Mac)

So, here we go:

  1. Add the Groovy script to your OmegaT scripts.
    • The script is called utils_external_opener.groovy, and it can be downloaded here and here.
    • The script expects an executable called opener to be located in folder <omegat_config>/external-openers/. It will not continue if neither that folder, nor the executable is found. See p. 2 for details about that external part.
    • When the groovy script is successfully run, it creates a file called opener.vars in <omegat_config>/external-openers/. It is a plain text file containing the collected variables in this simple format:
      variable='value'
    • The script then executes <omegat_config>/external-openers/opener and passes the location of the opener.vars to it.
  2. Copy my opener to <omegat_config>/external-openers/
    • To do so, download the zip file containing the external launcher from here or here.
      Place the zip inside <omegat_config> and unpack there. It will create <omegat_config>/external-openers with the file opener. After that, the zip file can be deleted.
    • opener is a Bash script that doesn’t do anything useful on it’s own. It simply lists everything it finds in <omegat_config>/external-openers/actions/ via a selection list dialog (if you used the zip file, the subfolder actions/ and its contents were added too). Once the selection is made, the selected item is executed. Items under <omegat_config>/external-openers/actions/ can be scripts written in various scripting languages (as long as your OS knows how to execute them), or compiled binaries, but not application bundles.
    • I offer three such action scripts: “Open Directories”, “Rename target files”, and “Web Lookup”. “Rename target files” asks several questions when it’s executed, the other two present further list (but that’s as far as the lists go in the bundle I here present). The Web Lookup works with the selected text, or, if no selection is made, with current segment’s source text. Wikipedia languages, and DeepL and Google language pairs are taken from the current project language settings.
    • Make sure that <omegat_config>/external-openers/opener and everything under <omegat_config>/external-openers/actions/ is set to be executable.
  3. Make sure Zenity is installed.
    • On macOS, you may need to enable Homebrew and install Zenity with
      brew install ncruces/tap/zenity
      (this is a Zenity rewrite that doesn’t depend on GTK+; I haven’t checked the GTK+ version available through Homebrew and Macports.)
    • On Linux, Zenity is most likely already installed, but if not, just install it using your package manager.
  4. There’s Bash and Zenity for Windows, but there’s no time whatsoever to make sure this setup runs on Windows too. If anyone is interested to check and adapt it as needed, I’ll be happy to incorporate their findings.
  5. The script described here makes the following OmegaT info accessible to the external opener:
    • selected text (if nothing selected, the current segment’s source will be used)
    • URL-encoded selected text
    • current segment’s target
    • URL-encoded target
    • current segment source
    • URL-encoded source
    • project’s path
    • path to the omegat subfolder of the current project
    • path to the current project source folder
    • path to the current project target folder
    • path to the current project tm folder
    • path to the current project glossaries folder
    • path to the current project dictionaries folder
    • path to the current file open in the editor
    • path to the writable glossary
    • path to the OmegaT configuration folder
    • path to the OmegaT scripts folder
    • source language code
    • target language code
    • source language country variant
    • target language country variant
    • source language name
    • target language
    • language in which OmegaT is run

If you have questions, you can always leave them in comments. Happy translating!

OmegaT Live preview (based on LibreOffice)

Below you’ll find a quick and dirty live preview solution for OmegaT on GNU/Linux.

In order for it to work, you’ll need any command line converter to convert your target files to PDF, and any PDF viewer to view the converted file. In the solution provided here Zathura PDF viewer is used. It is a very lightweight, keyboard-driven (albeit with vi-like keybindings) application that can invert document colors using a custom color scheme, and, most importantly, it reloads documents as they are changed, but keeps the previously open position, which makes it ideal for live previewing. Target files are converted using LibreOffice since I had it installed anyway; but any other command line tool that converts to PDF would do.

Continue reading

Voice Input in Translation Work (#Linux + Chrome + #OmegaT), Take 1

I always was rather skeptical about using dictate software in my translation work. But recently I read a success story where a person started to use Dragon Naturally Speaking, and it boosted his productivity by ungodly high percentage. Though it didn’t shake the deep skepticism of a die-hard Linux fanatic whose main target language isn’t supported by the major dictate software vendors, it doesn’t hurt to fool around and try a few things, does it?

As it turns out, one can save quite a few keystrokes by speaking into the cloud, and it can even be used on Linux in OmegaT. Google’s speech recognition supports my target language, several Chromium/Chrome browser’s apps and extensions kindly try to make written words out of my utterances, and then it’s up to me how I put it all together to be able to dictate instead of typing.

My working recipe is based on using SpeechPad – new voice notebook for voice input. This little thing can be installed as a Chrome app and can work in background, putting the recognized pieces into the clipboard. To enable that, one needs to put ticks in ” Restart on errors” and ” Transfer to clipboard”. It’s best to register with this application to be able to add new languages not listed by default (limited to what Google supports), add terms to the custom replacement list (to enable punctuation by voice for some languages, for instance), and do other things. It’s all done in the user’s profile (called “User data” on the main page). When the SpeechPad is fired up and listening in the background, you can switch to the app where you need to type (OmegaT in my case), dictate a logical chunk and press Ctrl+V. Some of the repeated mistakes in the text can be fixed with replace_with_template.groovy (see here for details on how to use the script). Or pasting and fixing can be done with one OmegaT script insert_modify_clipboard.groovy (the above link with details still applies, but substitution template should be named .ini/clipboard_substitution.ini).

I’ve noticed that in Ukrainian the speech gets recognized much better when I chant it (and that’s where my passion for the byzantine rite liturgical chanting comes real handy, although one of my buddies said that Rammstein style singing provides similar results). With all of it I did manage to get a productivity boost (and unplanned chanting practice). I’d be happy to hear suggestions on how to improve this recipe or change the ingredients to be able to type less and produce more.


But as of now,
Good luck

Customizing #OmegaT Kaptain Launcher (GNU/Linux)

OmegaT for GNU/Linux comes with a nifty launcher that gives you a comprehensive GUI to most of the startup parameters without needing to do anything on the command line. Along with that I’m not sure that many Linux user use this script. I think, one of the reasons for that is that the script doesn’t save your choices and you have to enter them at each run, which isn’t too bad if OmegaT was installed by the provided installation script (a rare case, as far as I can tell). And then the launcher is written in a somewhat obscure scripting language that requires some familiarization if the defaults are to be edited. In this article I’ll show which parts of the code correspond to the respective GUI elements and what can be edited to make this script customized. Continue reading

Running Wordfast Pro on 64-bit Linux

Wordfast Pro isn’t supported on 64-bit Linux, but that can be solved.
Continue reading

Live OmegaT Statistics and Wages Calculator (Linux)

This post is to announce Dimitry Prihodko’s nice little program that shows live OmegaT statistics and calculates wages based on it. All of that can be done in a spreadsheet, of course, but Dimitry’s solution is faster both in that it doesn’t require any additional preparation of a spreadsheet and copying data from OmegaT’s project_stats.txt, and in the way in constantly updates data without any intervention on a user’s part.
OTStats window showing a project statistics Continue reading

lame GUI update to the new TMX export

This is an update to the previous post about exporting new translations to a TMX.
The script doesn’t have a GUI to select date and time or to specify whether it should work globally or on selected files. This update still doesn’t have that GUI, but provides for an external program to fill that gap. In this post I’m sharing the updated groovy script and a simple bash+zenity wizard-like script for Linux that acquires necessary data.
If no such external program/script exists, the groovy script continues to work as before without any extra fuss.

  • write_new_trans2TMX_extGUI.groovy

    /*
     * Purpose:	 Export new translations completed after the specified 
     * 	 date (line 21) either for the entire project or for the
     * 	 selected files ("select_files" must be set to 'yes' — line 27)
     * 	 to TMX file
     * #Files:	 Writes 'translated_after_<date_time>.tmx'
     * 	 in the current project's root
     * #File format:	 TMX v.1.4
     * #Details:	http:/ /wp.me / p3fHEs-6z
     *
     * @author  Kos Ivantsov
     * @date    2013-08-12
     * @version 0.3
     */
    
    /*
     * The date should be specified as "year-month-day HOURS:minutes"
     * If not specified or specified wrongly, the script will look for
     * translations that are newer than one day. 
     */ 
    def newdate = ''
    /*
     * Set "select_files" to 'yes' if you want to use file selector
     * to specify files for export. If anything else is specified, the script
     * will work with the complete project.
     */ 
    select_files = ''
    
    import javax.swing.JFileChooser
    import org.omegat.util.StaticUtils
    import org.omegat.util.TMXReader
    import static javax.swing.JOptionPane.*
    import static org.omegat.util.Platform.*
    def prop = project.projectProperties
    
    if (!prop) {
    	final def title = 'Export new translation'
    	final def msg   = 'Please try again after you open a project.'
    	showMessageDialog null, msg, title, INFORMATION_MESSAGE
    	return
    }
    
    /*
     * If you want to use an external date and time selector and a window to
     * ask whether you want to select individual files, specify the whole path
     * to that program/script. It should print out date in the proper format
     * on the first line of the stout, and "yes" or anything else on the second
     * line. 
     */
    def command = "/home/user/.omegat/script/new2tmx_tweak"
    try {
    	proc = command.execute()
    	proc.waitFor()
    	//console.println "${proc.in.text}"
    	def lines = "${proc.in.text}".readLines()
    	newdate = lines[0]
    	select_files = lines[1]
    	}
    catch(java.io.IOException ex){
    if (ex.getMessage() =~ 'error=13'){
    	console.println "The program is not executable"
    	}
    if (ex.getMessage() =~ 'error=2'){
    	console.println "The program is not found"
    	}
    }
    
    try {
    	newdate = new Date().parse("yyyy-MM-dd HH:mm", newdate)
    	}
    	catch (java.text.ParseException e) {
    		newdate = new Date().minus(1)
    		final def title = 'Wrong date format'
    		final def msg   = """\
    The date has been specified in a wrong format.
    The script will work with entries exactly one day old,
    i.e. changed after $newdate\
    """
    		console.println msg
    		showMessageDialog null, msg, title, INFORMATION_MESSAGE
    		}
    
    namedate = new Date().parse("E MMM dd H:m:s z yyyy", newdate.toString()).format("MMM-dd-yyyy_HH.mm")
    
    def fileloc = prop.projectRoot+'translated_after_'+namedate+"${ (select_files == 'yes') ? "_select" : ''}"+'.tmx'
    exportfile = new File(fileloc)
    
    if (prop.isSentenceSegmentingEnabled())
    	segmenting = TMXReader.SEG_SENTENCE
    	else
    	segmenting = TMXReader.SEG_PARAGRAPH
    
    def sourceLocale = prop.getSourceLanguage().toString()
    def targetLocale = prop.getTargetLanguage().toString()
    
    exportfile.write("", 'UTF-8')
    exportfile.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", 'UTF-8')
    exportfile.append("<!DOCTYPE tmx SYSTEM \"tmx11.dtd\">\n", 'UTF-8')
    exportfile.append("<tmx version=\"1.4\">\n", 'UTF-8')
    exportfile.append(" <header\n", 'UTF-8')
    exportfile.append("  creationtool=\"OmegaTScripting\"\n", 'UTF-8')
    exportfile.append("  segtype=\"" + segmenting + "\"\n", 'UTF-8')
    exportfile.append("  o-tmf=\"OmegaT TMX\"\n", 'UTF-8')
    exportfile.append("  adminlang=\"EN-US\"\n", 'UTF-8')
    exportfile.append("  srclang=\"" + sourceLocale + "\"\n", 'UTF-8')
    exportfile.append("  datatype=\"plaintext\"\n", 'UTF-8')
    exportfile.append(" >\n", 'UTF-8')
    
    def hitcount = 0
    
    if ((select_files == 'yes')) {
    	srcroot = new File(prop.getSourceRoot())
    	sourceroot = prop.getSourceRoot().toString() as String
    	JFileChooser fc = new JFileChooser(
    	currentDirectory: srcroot,
    	dialogTitle: "Choose files to export",
    	fileSelectionMode: JFileChooser.FILES_ONLY, 
    	//the file filter must show also directories, in order to be able to look into them
    	multiSelectionEnabled: true)
    
    	if(fc.showOpenDialog() != JFileChooser.APPROVE_OPTION) {
    	console.println "Canceled"
    	return
    	}
    
    	if (!(fc.selectedFiles =~ sourceroot.replaceAll(/\\+/, '\\\\\\\\'))) {
    		console.println "Selection outside of ${prop.getSourceRoot()} folder"
    		final def title = 'Wrong file(s) selected'
    		final def msg   = "Files must be in ${prop.getSourceRoot()} folder."
    		showMessageDialog null, msg, title, INFORMATION_MESSAGE
    		return
    	}
    
    	fc.selectedFiles.each {
    		fl = "${it.toString()}" - "$sourceroot"
    		exportfile.append("  <prop type=\"Filename\">" + fl + "</prop>\n", 'UTF-8')
    	}
    	exportfile.append(" </header>\n", 'UTF-8')
    	exportfile.append("  <body>\n", 'UTF-8')
    
    	fc.selectedFiles.each{
    		fl = "${it.toString()}" - "$sourceroot" 
    		files = project.projectFiles
    		files.each{
    			if ( "${it.filePath}" != "$fl" ) {
    			println "Skipping to the next file"
    			}else{
    		it.entries.each {
    		def info = project.getTranslationInfo(it)
    		def changeId = info.changer
    		def changeDate = info.changeDate
    		def creationId = info.creator
    		def creationDate = info.creationDate
    		def alt = 'unknown'
    		if (info.isTranslated()) {
    			if (newdate.before(new Date(changeDate))){
    				hitcount++
    				source = StaticUtils.makeValidXML(it.srcText)
    				target = StaticUtils.makeValidXML(info.translation)
    				exportfile.append("    <tu>\n", 'UTF-8')
    				exportfile.append("      <tuv xml:lang=\"" + sourceLocale + "\">\n", 'UTF-8')
    				exportfile.append("        <seg>" + "$source" + "</seg>\n", 'UTF-8')
    				exportfile.append("      </tuv>\n", 'UTF-8')
    				exportfile.append("      <tuv xml:lang=\"" + targetLocale + "\"", 'UTF-8')
    				exportfile.append(" changeid=\"${changeId ?: alt }\"", 'UTF-8')
    				exportfile.append(" changedate=\"${ changeDate > 0 ? new Date(changeDate).format("yyyyMMdd'T'HHmmss'Z'") : alt }\"", 'UTF-8')
    				exportfile.append(" creationid=\"${creationId ?: alt }\"", 'UTF-8')
    				exportfile.append(" creationdate=\"${ creationDate > 0 ? new Date(creationDate).format("yyyyMMdd'T'HHmmss'Z'") : alt }\"", 'UTF-8')
    				exportfile.append(">\n", 'UTF-8')
    				exportfile.append("        <seg>" + "$target" + "</seg>\n", 'UTF-8')
    				exportfile.append("      </tuv>\n", 'UTF-8')
    				exportfile.append("    </tu>\n", 'UTF-8')
    						}
    					}
    				}
    			}
    		}
    	}
    } else {
    	exportfile.append(" </header>\n", 'UTF-8')
    	exportfile.append("  <body>\n", 'UTF-8')
    	files = project.projectFiles
    		files.each {
    			it.entries.each {
    			def info = project.getTranslationInfo(it)
    			def changeId = info.changer
    			def changeDate = info.changeDate
    			def creationId = info.creator
    			def creationDate = info.creationDate
    			def alt = 'unknown'
    			if (info.isTranslated()) {
    				if (newdate.before(new Date(changeDate))){
    				hitcount++
    				source = StaticUtils.makeValidXML(it.srcText)
    				target = StaticUtils.makeValidXML(info.translation)
    				exportfile.append("    <tu>\n", 'UTF-8')
    				exportfile.append("      <tuv xml:lang=\"" + sourceLocale + "\">\n", 'UTF-8')
    				exportfile.append("        <seg>" + "$source" + "</seg>\n", 'UTF-8')
    				exportfile.append("      </tuv>\n", 'UTF-8')
    				exportfile.append("      <tuv xml:lang=\"" + targetLocale + "\"", 'UTF-8')
    				exportfile.append(" changeid=\"${changeId ?: alt }\"", 'UTF-8')
    				exportfile.append(" changedate=\"${ changeDate > 0 ? new Date(changeDate).format("yyyyMMdd'T'HHmmss'Z'") : alt }\"", 'UTF-8')
    				exportfile.append(" creationid=\"${creationId ?: alt }\"", 'UTF-8')
    				exportfile.append(" creationdate=\"${ creationDate > 0 ? new Date(creationDate).format("yyyyMMdd'T'HHmmss'Z'") : alt }\"", 'UTF-8')
    				exportfile.append(">\n", 'UTF-8')
    				exportfile.append("        <seg>" + "$target" + "</seg>\n", 'UTF-8')
    				exportfile.append("      </tuv>\n", 'UTF-8')
    				exportfile.append("    </tu>\n", 'UTF-8')
    					}
    				}
    			}
    		}
    }
    
    exportfile.append("  </body>\n", 'UTF-8')
    exportfile.append("</tmx>", 'UTF-8')
    
    final def title = 'TMX file written'
    final def msg   = "$hitcount TU's written to " + exportfile.toString()
    console.println msg
    showMessageDialog null, msg, title, INFORMATION_MESSAGE
    return
    

    The only difference compared to the previous version is lines 43-66. The external program is specified in line 50.

  • new2tmx_tweak
    #!/bin/bash
    DATE=$(zenity --calendar \
    --title "Select date" \
    --text="TU's newer than the selected date will be used for export" \
    --date-format=%F)
    
    HRS=({00..24})
    MINS=({00..59})
    
    HRS=$(zenity --entry --title "Select time" \
    --text="Hours" \
    --entry-text="${HRS[@]}" )
    
    MINS=$(zenity --entry --title "Select time" \
    --text="Minutes" \
    --entry-text="${MINS[@]}" )
    
    zenity --question --title="Select Files" \
    --text="Do you want to select individual files for export?"
    if [ $? == "0" ]; then 
    FILESEL='yes'
    else
    FILESEL='no'
    fi
    
    echo "$DATE $HRS:$MINS"
    echo $FILESEL
    

    This script should be saved somewhere (in this example it’s /home/user/.omegat/script/new2tmx_tweak) and made executable (chmod +x /home/user/.omegat/script/new2tmx_tweak). Zenity should be installed for it to work. One can cook up a nicer GUI using other tools, of course, but this serves just as a quick example. I guess, similar can be done using AutoIt or AutoHotKey on MS Windows or AppleScript on OSX.
    If you happen to come up with your own date and time picker for this, feel free to share in comments or link to your solution.

But as of now,


wordpress visitor

Good luck!

File Renamer (Bash from within OmegaT)

Situation

You have a client who loves to give his files very descriptive names. That’s understandable as mostly the files you get to translate from him are lessons, lectures, howtos, manuals and so on. It makes sense to distribute them digitally with localized filenames.

Problem

What you need is a way to translate filenames in OmegaT thus keeping consistency with the contents of the translated files and past/future files from the same client.
Continue reading

OmegaT match insert/replace without tags

Situation

After having translated a complete user manual that you converted from PDF to ODT to be able to work on it in OmegaT, you receive another manual from the same client, but this time it’s a DOCX file. Great! You can start right away, without converting anything. That should be a peace of cake — half of the manual looks almost the same as the one you have just done.

Problem

After starting to work with it you find out that getting a lot of 95-97% would be really awesome, if it wasn’t for all those nasty tags that are very different in the source and in the match. And there is no “Insert match without tags” menu item in OmegaT (yet).

Continue reading

Bash (Perl, Python, Tcl/Tk and what not) Scripting from within OmegaT

Situation

So, right now you’re using quite a few scripts while working in OmegaT. Some of them are the ones included in the Scripting Plugin, others are taken from the Internet, several of them were written on your own, and a couple are still cooking in your head, promising to be something that will save you a couple hours of work everyday in future and now hindering you from concentrating on what is at hand. To run them from with a key shortcut you had to assign global key combinations, as from withing OmegaT you can run only 5 custom scripts with a key combo, and those are not just any scripts but the ones that the Scripting Plugin can run.

Problem

Now, with many other scripts and actions used elsewhere for your work/leisure you’re running out of available key combinations, plus you get more and more questions like, “Dad, what you just did doesn’t work on my computer. Do I press it wrong or what’s the matter?” from your elementary-school-aged son.
What you want is an ability to run any script from within OmegaT, not just the ones that the Scripting Plugin can run, as you don’t want to be limited to Java-like languages, but you look for a way to use anything that you’re comfortable with. Besides, these custom scripts should be aware of your current OmegaT project’s variables and settings (like project folders, language pairs etc.) Then at least you’ll be able to say to your son, “Boy, you don’t use OmegaT yet. Let me better show you this combo that you can use on your computer.”
Continue reading