Overhead

A long time ago in a galaxy far away, I was a high school student. In hindsight, this involved many artificially tedious things that I took for granted in the same way that I accepted various laws of Physics. The want of superior technology resulted in all manner of needless inefficiencies and cumbersome overhead. Such impedances ultimately drove down the rate at which I could ram knowledge into the confines of my skull. One particularly good example thereof involves my studies of the French language. Specifically, looking up words that I did not know entailed an inordinate amount of work.

When reading a piece of text in a foreign language, one can choose from two modes of operation when dealing with unknown words. Either one can stop at every unknown word and look it up, or one can read all the way through the article without worrying overly much about comprehension, recording unknown words along the way and translating them in batch mode at the end, and then read through the article again. Both have their advantages and drawbacks. The as-you-go approach avoids an extra reading, but the regular interruption to go to a dictionary proves highly disruptive. The batching approach reduces disruption, but in some ways it results in extra work, such as looking up words you might have understood from context if some other word had been known sooner rather than later.

This represents a false dichotomy, thrust upon the student by non-availability of technology that reduces the expense of context switches. Instant translation that does not force a disruptive modality changes the rules of the game. The appearance of online dictionaries improved things significantly. Not having to thumb through a dictionary reduces the context switch overhead enormously. It does, however, still involve an unfortunate amount of clumsiness and flow disruption. Ideally, when reading a sentence that contains an unknown word, one ought with a single keystroke be able to start a parallel process that brings up a translation without disrupting completion of the sentence being read. Having to copy and paste a word into a web browser and then wait for the response soon becomes irksome.

I have for some time now had a decent solution to this problem embodied in an Emacs extension. During my vacation of this week I decided to improve and generalize it. The reader may find the full code at the end of this essay.

The extension makes use of the W3M library for browsing in Emacs and a modicum of custom elisp to glue everything together. The extension exposes a simple plug-in system of its own that allows the user to integrate with different translation systems. The code exhibits sufficient compactness and descriptiveness that its mechanics ought to be mostly self-evident.

The end result is that I can press F5 and enter a URL, say http://www.lemonde.fr, and commence browsing French articles within Emacs. When I encounter a French word that I don’t know, I put the cursor over it and press F6. My current window stays open so I can continue to read. Eventually my definition appears and I peruse it. When finished with it, I press F8 and the definition vanishes. The whole process is the same except for hitting F7 instead of F6 if I happen to be perusing, say, http://www.welt.de.

One mildly obnoxious thing about the W3M library is the inability to specify a callback function to be invoked when an asynchronous W3M function completes. Since I wanted my translation plug-ins to have such a callback faculty, I had to switch the W3M library’s mode of operation from asynchronous to synchronous. A word to the wise for library writers: if you expose an asynchronous API, you should always provide a hook via which “after” callbacks can be invoked. Failure to do so renders the lives of API users needlessly difficult.


(add-to-list 'load-path "~/emacs-w3m-1.4.4")
(require 'w3m-load)

(defcustom w3m-async-exec nil
  "*Non-nil means execute the w3m command asynchronously in Emacs process."
  :group 'w3m
  :type 'boolean)

(defun grab-word-under-cursor ()
  (forward-char 1)
  (backward-word 1)
  (mark-word 1)
  (copy-region-as-kill (point) (mark))
  (car kill-ring))

(setq translator-hash (make-hash-table :test 'equal))

(defun add-translator (source-lang target-lang url-constructor post-load-hook)
  (puthash (concat source-lang ":" target-lang)
           (cons url-constructor (cons post-load-hook nil))
           translator-hash))

(defun get-url-constructor (source-lang target-lang)
  (car (gethash (concat source-lang ":" target-lang) translator-hash)))

(defun get-post-load-hook (source-lang target-lang)
  (car (cdr (gethash (concat source-lang ":" target-lang) translator-hash))))

(defun translate-word (word source-lang target-lang)
  (split-window-vertically)
  (other-window 1)
  (w3m-goto-url-new-session
   (apply (get-url-constructor source-lang target-lang) word nil))
  (let ((post-load-hook (get-post-load-hook source-lang target-lang)))
    (if (not (equal nil post-load-hook)) (apply post-load-hook nil))))

(defun translate-current-word (source-lang target-lang)
  (translate-word (grab-word-under-cursor) source-lang target-lang))

(add-translator
 "french" "english"
 (lambda (word) (concat "http://www.wordreference.com/fren/" word))
 nil)

(add-translator
 "german" "english"
 (lambda (word) (concat "http://dictionary.reverso.net/german-english/" word))
 (lambda () (search-forward "See also:") (recenter 0) (beginning-of-line)))

(defun translate-current-word-french-to-english ()
  (interactive)
  (translate-current-word "french" "english"))

(defun translate-current-word-german-to-english ()
  (interactive)
  (translate-current-word "german" "english"))

(defun kill-and-close ()
  (interactive)
  (kill-this-buffer)
  (delete-window (selected-window)))

(global-set-key [(f5)] 'w3m-goto-url)
(global-set-key [(f6)] 'translate-current-word-french-to-english)
(global-set-key [(f7)] 'translate-current-word-german-to-english)
(global-set-key [(f8)] 'kill-and-close)

— AWG

Leave a Reply