Duplex Lectio is a web app to read Latin texts with inline translations. After finishing DuoLingo’s Latin course, I wanted to read some Latin texts, but I found it hard to find easy enough texts and going back and forth to a dictionary was cumbersome.

The idea of the app is to have a Latin text with translation of each word under the paragraph line, which makes it easy to grasp the meaning but also focuses on reading the original Latin text.

I created the app for mobile first, so it is optimized for reading on a phone.

Open the App

Translation of a Latin Text

As it happens, finding a Latin text with a translation is not easy. Actually didn’t find any. As we live in the age of AI, I experimented a little with Google Translate, but the results didn’t look that good.

Tried it with OpenAI’s GPT-3, and it was better, at least it appeared better for me. I used the Wiki Source Easy Latin Stories by George Bennett as a source text. For now is the only one in the app, after I finish reading it, I might add more texts.

The code to partially automate the translation is pretty straightforward, is in the text folder of the repo. I used a pretty specific prompt because I wanted a phrase by phrase translation, but also a word by word translation.

You are an expert translator for latin language.
Translated text is like in a translated book, without any additional explanations, just the translation.
The book is a collection of easy latin stories for beginners from 1838 and contains 
many expressions specific to the time and the context of the stories.
You receive input text and provide output as JSON format according to the specifications.
Translate paragraph by paragraph, each paragraph can be either in latin or english.
If the paragraph is in english, translate it to latin. If the paragraph is in latin, translate it to english.
The JSON output contains all the paragraphs.
Each output paragraph includes both the text used as input and the translated text.
The output also includes a translation of each latin word from the latin text to english as an array. 
Even if the original translation was from english to latin, the words section contains latin to english words 
from the latin text. 
The JSON output template is:
{
  "paragraphs": [
    {
      "text": {
        "eng": "<english_text>",
        "lat": "<latin_text>",
      },
      "words: [
        ["<latin_word_1>", "<english_translation_1>"]
        ["<latin_word_2>", "<english_translation_2>"]
      ]
    }
  ]
}
Do not add additional explanations and prefer single word or minimal expression for english translation.
If there are latin alternatives for the english word, pick the one that fits best.
For words make sure to keep the same case as the original text, examples: \`["Est", "Is"], ["Perraro", "Very rarely"], ["Aegyptum", "to Egypt"],\`.

The model (I used gpt-4o) is not perfect, but it outputs JSON almost perfectly and was able to cope with english and latin text in the source text.

I noticed that sometimes it skips some words for translation when the original text is in english, so unfortunately, the translation contains some english to english words. Also, sometimes it puts “```” but these are easy to remove in post-processing.

The pipeline is something like:

  1. Get the text from the source,
  2. Translate it with GPT-3,
  3. Parse the JSON output and match the words with the original text with some basic tokenization,
  4. Transform the model and chop the text in chapters, paragraphs, and words for the app.

Point 4 is composed of a couple of scripts I run back and forth until I get the desired output, it can be better automated, but for a single source, it works well enough.

The result is not perfect, but certainly is something that I wouldn’t have completed if not for the AI model.

The App

I used React with TypeScript for the app without any external libraries. I wanted something very clean but also with the ability to switch translation on and off and hide it without affecting the layout. I’ve put the buttons in the bottom bar to be easily accessible from the phone.

Read View of the App

The text display was pretty straightforward, the most I struggled with making a layout that is able to wrap but also to show word by word translation.

In the end I went for something simple that seems to work well: each word is an inline-block span which contains the latin word and the translation in a flex column. The (simplified) code looks like this:

<p className={classes.paragraph}>
    <span className={classes.word}>
        <span className={classes.text}>{word.lat}</span>
        <span className={classes.translation}>{word.eng}</span>
    </span>
    {/*...*/}
</p>
.paragraph {
  display: flex;
  flex-wrap: wrap;
}
.word {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.text {
  display: inline-block;
  font-size: 1.1em;
  line-height: 2rem;
}

.translation {
  display: inline-block;
  font-size: 0.9em;
  padding: 0.1rem 0 0.7rem 0;
  color: var(--dx-fg-color--dimmed);
  user-select: none;
}

You can find the entire source code of the app and the text translation pipeline in GitHub.