Tags: latin

RSS - Atom - Subscribe via email

Org Mode tables and fill-in quizzes – Latin verb conjugation drills in Emacs

Posted: - Modified: | emacs, org

I was looking for a Latin verb conjugation drill similar to these ones for and nouns and pronouns. I liked the instant feedback and the ability to quickly get hints. I couldn’t find an online drill I liked, though, so I made my own with Emacs and Org. (Because… why not?)

I wrote some code that would take a table like this:

present – 1st sing. – ago / agere agO
present – 2nd sing. – ago / agere agis
present – 3rd sing. – ago / agere agit
present – 1st plu. – ago / agere agimus
present – 2nd plu. – ago / agere agitis
present – 3rd plu. – ago / agere agunt
imperfect – 1st sing. – ago / agere agEbam
imperfect – 2nd sing. – ago / agere agEbAs
imperfect – 3rd sing. – ago / agere agEbat
imperfect – 1st plu. – ago / agere agEbAmus
imperfect – 2nd plu. – ago / agere agEbAtis
imperfect – 3rd plu. – ago / agere agEbant
future – 1st sing. – ago / agere agam
future – 2nd sing. – ago / agere agEs
future – 3rd sing. – ago / agere agEt
future – 1st plu. – ago / agere agEmus
future – 2nd plu. – ago / agere agEtis
future – 3rd plu. – ago / agere agent

I can call my/make-fill-in-quiz to get a quiz buffer that looks like this. If I get stuck, ? shows me a hint in the echo area.

latin-verb-drills-0

To make it easier, I’ve left case-fold-search set to nil so that I don’t have to match the case (uppercase vowels = macrons), but I can set case-fold-search to t if I want to make sure I’ve got the macrons in the right places.

Here’s the code to display the quiz buffer.

     (require 'widget)
     (defun my/check-widget-value (widget &rest ignore)
       "Provide visual feedback for WIDGET."
       (cond
        ((string= (widget-value widget) "?")
         ;; Asking for hint
         (message "%s" (widget-get widget :correct))
         (widget-value-set widget ""))
        ;; Use string-match to obey case-fold-search 
        ((string-match 
          (concat "^"
                  (regexp-quote (widget-get widget :correct))
                  "$")
          (widget-value widget))
         (message "Correct")
         (goto-char (widget-field-start widget))
         (goto-char (line-end-position))
         (insert "✓")
         (widget-forward 1)
         )))

   (defun my/make-fill-in-quiz (&optional quiz-table)
     "Create an fill-in quiz for the Org table at point.
The Org table's first column should have the questions and the second column 
should have the answers."
     (interactive (list (org-babel-read-table)))
     (with-current-buffer (get-buffer-create "*Quiz*")
       (kill-all-local-variables)
       (let ((inhibit-read-only t))
         (erase-buffer))
       (remove-overlays)
       (mapc (lambda (row)
               (widget-insert (car row))
               (widget-insert "\t")
               (widget-create 'editable-field
                              :size 15
                              :correct (cadr row)
                              :notify 'my/check-widget-value)
               (widget-insert "\n"))    
             quiz-table)
       (widget-create 'push-button
                      :table quiz-table
                      :notify (lambda (widget &rest ignore)
                                (my/make-fill-in-quiz (widget-get widget :table))) 
                      "Reset")
       (use-local-map widget-keymap)
       (widget-setup)
       (goto-char (point-min))
       (widget-forward 1)
       (switch-to-buffer (current-buffer))))

Incidentally, I generated the table above from a larger table of Latin verb conjugations in the appendix of Wheelock’s Latin, specified like this:

#+NAME: present-indicative-active
| laudO    | moneO   | agO    | audiO   | capiO   |
| laudAs   | monEs   | agis   | audIs   | capis   |
| laudat   | monet   | agit   | audit   | capit   |
| laudAmus | monEmus | agimus | audImus | capimus |
| laudAtis | monEtis | agitis | audItis | capitis |
| laudant  | monent  | agunt  | audiunt | capiunt |

#+NAME: imperfect-indicative-active
| laudAbam   | monEbam   | agEbam   | audiEbam   | capiEbam   |
| laudAbas   | monEbas   | agEbAs   | audiEbAs   | capiEbas   |
| laudAbat   | monEbat   | agEbat   | audiEbat   | capiEbat   |
| laudAbAmus | monEbAmus | agEbAmus | audiEbAmus | capiEbAmus |
| laudAbAtis | monEbAtis | agEbAtis | audiEbAtis | capiEbAtis |
| laudAbant  | monEbant  | agEbant  | audiEbant  | capiEbant  |

#+NAME: future-indicative-active
| laudAbO    | monEbO    | agam   | audiam     | capiam     |
| laudAbis   | monEbis   | agEs   | audiEs     | capiEs     |
| laudAbit   | monEbit   | agEt   | audiet     | capiet     |
| laudAbimus | monEbimus | agEmus | audiEmus   | capiEmus   |
| laudAbitis | monEbitis | agEtis | audiEtis   | capiEtis   |
| laudAbunt  | monEbunt  | agent  | audient    | capient    |

with the code:

#+begin_src emacs-lisp :var present=present-indicative-active :var imperfect=imperfect-indicative-active :var future=future-indicative-active
  (defun my/label-latin-with-verbs (table verbs persons tense)
    (apply 'append
           (-zip-with (lambda (row person) 
                        (-zip-with (lambda (word verb)
                                     (list word (format "%s - %s - %s" tense person verb)))
                                   row verbs))
                      table (-cycle persons))))
  (apply 'append 
         (mapcar (lambda (tense)
                   (my/label-latin-with-verbs 
                    (symbol-value tense)
                    '("laudo / laudare" "moneo / monEre" "ago / agere" "audiO / audIre" "capiO / capere")
                    '("1st sing." "2nd sing." "3rd sing." "1st plu." "2nd plu." "3rd plu.")
                    (symbol-name tense)))
                 '(present imperfect future)))

#+end_src

This uses dash.el for the -zip-with and -cycle functions. There’s probably a much better way to process the lists, but I’m still getting the hang of thinking properly functionally… =)

Anyway, I’m sure it will be handy for a number of other quiz-like things. org-drill and org-drill-table will probably come in handy for flashcards, too!

Sketchnote: Fun With Dead Languages: Damian Conway

Posted: - Modified: | geek, sketchnotes

Here are my notes from Damian Conway’s talk “Fun With Dead Languages”. =) I heard him give an older version of this talk years ago, and I’m amused to find that my Latin dabbling gave me a much deeper appreciation of this talk.

As always, click on the image to view a larger version, which you can print out if you want.

20130806 Fun with Dead Languages - Damian Conway

Please feel free to share this under the Creative Commons Attribution License! =)

If you like this, check out Damian Conway’s site or this paper on Lingua::Latina::Perligata. Like these sketches? Check out my other sketchnotes and visual book reviews.

Cattus Petasatus

Posted: - Modified: | learning

On a whim, W- and I are learning Latin. We figure that schoolkids used to learn Latin and Greek, so we should be able to hack it too. So we’ve signed up for an Internet study group, borrowed books from the library, and looked for other Latin resources.

We were delighted to find Cattus Petasatus, a Latin translation of Dr. Seuss’ The Cat in the Hat. There are even translations for some of the other books, like Green Eggs and Ham. I like reading them in addition to our textbooks. They make Latin feel more contemporary.

Learning Latin with W- is a lot of fun. He shares the ways Latin reminds him of French. He thinks I find it easier to say Latin than he does because of my background in Filipino, which also has a lot of short syllables. We review our study group homework together, laugh at the contrived examples, and look around for other resources. I’m so lucky my husband is a geek. =)

We’ll gradually work our way up to Winnie ille Pu. Maybe we’ll even put together our own Latin projects!

"An Easy Method for Beginners in Latin" and macron-insensitive search for Tiddlywiki

Posted: - Modified: | geek

As previously mentioned, W- and I are re-typing parts of Albert Harkness’ 1822 textbook "An Easy Method for Beginners in Latin", which was digitized and uploaded to Google Books as a PDF of images. The non-searchable book was driving W- mad, so we’re re-typing up lessons. It’s a decent way to review, and I’m sure it will be a great resource for other people too.

Here’s what we have so far: An Easy Method for Beginners in Latin, Lessons 1-9

We’re starting off using Tiddlywiki because it’s a wiki system that W-‘s been using a lot for his personal notes. He’s familiar with the markup. It’s not ideal because Google doesn’t index it, the file size is bigger than it needs to be (0.5MB!), and it’s Javascript-based. It’s a good start, though, and I should be able to convert the file to another format with a little scripting. My first instinct would be to start with Org Mode for Emacs, of course, but we already know what W- thinks of Emacs. ;)

Most of the text was easy to enter. Harkness is quite fond of footnotes, numbered sections, and lots of bold and italic formatting. We’re going to skip the illustrations for now.

Typing all of this in and using it as our own reference, though, we quickly ran into a limitation of the standard TiddlyWiki engine (and really, probably all wiki engines): you had to search for the exact word to find something. In order to find poēta, you had to type poēta, not poeta. That’s because ē and e are two different characters.

We wanted to keep the macrons as pronunciation and grammar guides. We didn’t want to require people to know or type letters with macrons. Hmm. Time to hack Tiddlywiki.

TiddlyWiki plugins use Javascript. I found a sample search plugin that showed me the basics of what I needed.

I considered two approaches:

  1. Changing the search text to a regular expression that included macron versions of each vowel
  2. Replacing all vowels in the Tiddler texts with non-macron vowels when searching

The first approach was cleaner and looked much more efficient, so I chose that route. If the search text contained a macron, I assumed the searcher knew what he or she was doing, so I left the text alone. If the text did not contain a macron, I replaced every vowel with a regular expression matching the macron equivalents. Here’s what that part of the code looked like:

s = s.replace(/(.)/g, "['/]*$1");
if (!s.match(macronPattern)) {
  // Replace the vowels with the corresponding macron matchers
  s = s.replace(/a/, "[aāĀA]");
  s = s.replace(/e/, "[eēĒE]");
  s = s.replace(/i/, "[iīĪI]");
  s = s.replace(/o/, "[oōŌO]");
  s = s.replace(/u/, "[uūŪU]");
}

That got me almost all the way there. I could search for most of the words using plain text (so poeta would find poēta and regina would find rēgīnae), but some words still couldn’t be found.

A further quirk of the textbook is that the characters in a word might be interrupted by formatting. For example, poēt<strong>am</strong> is written as =poēt”am”= in Tiddlywiki markup. So I also inserted a regular expression matching any number of ‘ or / (bold or italic markers when doubled) between each letter:

s = s.replace(/(.)/g, "['/]*$1");

It’s important to do this before the macron substitution, or you’ll have regexp classes inside other classes.

That’s the core of the macron search. Here’s what it looks like. I was so thrilled when I got all of this lined up! =)

image

And the source code:

// Macron Search Plugin
// (c) 2011 Sacha Chua - Creative Commons Attribution ShareAlike 3.0 License
// Based on http://devpad.tiddlyspot.com/#SimpleSearchPlugin by FND

if(!version.extensions.MacronSearchPlugin) { //# ensure that the plugin is only installed once
version.extensions.MacronSearchPlugin = { installed: true };

if(!config.extensions) { config.extensions = {}; }

config.extensions.MacronSearchPlugin = {
  heading: "Search Results",
  containerId: "searchResults",
  btnCloseLabel: "Close search",
  btnCloseTooltip: "dismiss search results",
  btnCloseId: "search_close",
  btnOpenLabel: "Open all search results",
  btnOpenTooltip: "Open all search results",
  btnOpenId: "search_open",

  displayResults: function(matches, query) {
    story.refreshAllTiddlers(true); // update highlighting within story tiddlers
    var el = document.getElementById(this.containerId);
    query = '"""' + query + '"""'; // prevent WikiLinks
    if(el) {
      removeChildren(el);
    } else { //# fallback: use displayArea as parent
      var container = document.getElementById("displayArea");
      el = document.createElement("div");
      el.id = this.containerId;
      el = container.insertBefore(el, container.firstChild);
    }
    var msg = "!" + this.heading + "\n";
    if(matches.length > 0) {
        msg += "''" + config.macros.search.successMsg.format([matches.length.toString(), query]) + ":''\n";
      this.results = [];
      for(var i = 0 ; i < matches.length; i++) {
        this.results.push(matches[i].title);
        msg += "* [[" + matches[i].title + "]]\n";
      }
    } else {
      msg += "''" + config.macros.search.failureMsg.format([query]) + "''\n"; // XXX: do not use bold here!?
    }
    wikify(msg, el);
    createTiddlyButton(el, "[" + this.btnCloseLabel + "]", this.btnCloseTooltip, config.extensions.MacronSearchPlugin.closeResults, "button", this.btnCloseId);
    if(matches.length > 0) { // XXX: redundant!?
      createTiddlyButton(el, "[" + this.btnOpenLabel + "]", this.btnOpenTooltip, config.extensions.MacronSearchPlugin.openAll, "button", this.btnOpenId);
    }
  },

  closeResults: function() {
    var el = document.getElementById(config.extensions.MacronSearchPlugin.containerId);
    removeNode(el);
    config.extensions.MacronSearchPlugin.results = null;
    highlightHack = null;
  },

  openAll: function(ev) {
    story.displayTiddlers(null, config.extensions.MacronSearchPlugin.results);
    return false;
  }
};

// override Story.search()
Story.prototype.search = function(text, useCaseSensitive, useRegExp) {
  var macronPattern = /[āĀēĒīĪōŌūŪ]/;
  var s = text;
  // Deal with bold and italics in the middle of words
  s = s.replace(/(.)/g, "['/]*$1");
  if (!s.match(macronPattern)) {
    // Replace the vowels with the corresponding macron matchers
    s = s.replace(/a/, "[aāĀA]");
    s = s.replace(/e/, "[eēĒE]");
    s = s.replace(/i/, "[iīĪI]");
    s = s.replace(/o/, "[oōŌO]");
    s = s.replace(/u/, "[uūŪU]");
  }
  var searchRegexp = new RegExp(s, "img");
  highlightHack = searchRegexp;
  var matches = store.search(searchRegexp, null, "excludeSearch");
  config.extensions.MacronSearchPlugin.displayResults(matches, text);
};

// override TiddlyWiki.search() to ignore macrons when searching
TiddlyWiki.prototype.search = function(s, sortField, excludeTag, match) {
    // Find out if the search string s has a macron
    var candidates = this.reverseLookup("tags", excludeTag, !!match);
    var matches = [];
    for(var t = 0; t < candidates.length; t++) {
        if (candidates[t].title.search(s) != -1 ||
            candidates[t].text.search(s) != -1) {
            matches.push(candidates[t]);
        }
    }
    return matches;
};

} //# end of "install only once"

To add this to your Tiddlywiki, create a new tiddler. Paste in the source code. Give it the systemConfig tag (the case is important). Save and reload your Tiddlywiki file, and it should be available.

It took me maybe 1.5 hours to research possible ways to do it and hack the search plugin together for Tiddlywiki. I’d never written a plugin for Tiddlywiki before, but I’ve worked with Javascript, and it was easy to pick up. I had a lot of fun coding it with W-, who supplied plenty of ideas and motivation. =) It’s fun geeking out!

Writing macrons in Linux for Latin pronunciation

| emacs, geek, learning

Frustrated with the inability to search the scanned images of the 1822 Latin textbook we’re using (Albert Harkness’ An Easy Method for Beginners in Latin – get the PDF, the full-text version is badly OCRed), W- has taken it upon himself to recreate the public-domain textbook as a fully searchable TiddlyWiki (sans illustrations). This meant that he needed to type in a great number of macrons in the words, and that meant finding a better way than copying and pasting from KDE’s character map.

Macrons turn up in many languages. In Japanese, you use them to indicate that vowels are doubled. 大阪(おおさか)can be romanized as Oosaka or Ōsaka. In Latin, beginner textbooks often use macrons (macra) to indicate pronunciation. (Why do we care about pronunciation for a dead language used mostly in church hymns? W- and I actually want to be able to use this conversationally, at least with each other. After all, if you don’t use it, you lose it.)

I suggested Emacs. In Emacs, it’s just a matter of using M-x set-input-method to choose latin-alt-postfix. With that input method, you can add macrons to letters by typing – after them. For example, typing “a -” will result in ā. Not only that, dynamic abbreviations (M-/) make it easier to retype words you’ve already written before.

W- wouldn’t hear of using Emacs, being almost as firmly wedded to vi as he is to me. ;)

Instead, we spent some time figuring out how to set up KDE and gvim to make it easier for him to type in macrons. HTML character sequences were out of the question, of course. W- used KDE’s settings to map his unused Windows key and menu key to compose keys. That made it easier to produce ē, ī, ō, and ū using the key sequence “Compose + hyphen + vowel”. However, “Compose + hyphen + a” produced ã, not ā. This was probably a bug based on some issue reports we found on the Net, but the suggested fix didn’t work (im-switch -c to change to default-xim). I found a page describing an .XCompose fix, customizing the key sequences. He copied the relevant key sequences from en-US’s locale settings for Compose in /usr/share/X11, restarted X, and it worked.

Now he’s off and typing!

2011-04-24 Sun 23:21

Quid est nōmen tuum? Nōmen meum est “Sacha”

Posted: - Modified: | learning

Latīnum studémus. Monē mē!

The Latin textbooks that W- ordered from the library have arrived, and we’re slowly making our way through both Wheelock’s Latin and an online copy of a Latin textbook from the 1880s. Writing is probably going to be painfully slow and ungrammatic for a while, but hey, it’s worth a try. =)

Why Latin? Geek quirkiness. Secret languages for greater connection. Potential classical education.

It will be interesting. Let’s see if my blog can handle the characters…