Translation workflow

A translation in Emet is an ordinary entry that happens to live in another language’s directory. There’s no special translation state and nothing to approve; the workflow is just a quicker way of getting a correctly placed copy to start from, with the original beside you while you work.

Creating a translation#

  1. In the entry list, open the card’s More actions menu (the three dots) and click Translate. The action only appears on sites with more than one language.
  2. In the Create Translation dialog, pick the Target Language. The dropdown lists only languages this entry doesn’t have a translation in yet; if it has them all, the dialog says so and there’s nothing to do.
  3. Choose what to Start from:
    • Copy of source (the default) duplicates the whole source file, frontmatter and body, so you translate in place with the original text in front of you.
    • Blank from archetype gives you a fresh entry from the section’s archetype (the same template a new entry gets), keeping only the source’s title and its translationKey if it has one. Pick this when the body is going to be rewritten rather than translated line by line.
  4. Leave Open side-by-side with source ticked if you want the original visible while you translate. It’s on by default.
  5. Click Start Translation.

The translation goes to the same relative path under the target language’s directory, so content/en/posts/hello.md becomes content/fr/posts/hello.md. The whole path below the language directory is kept, subfolders and page bundles included: content/en/posts/2024/hello.md becomes content/fr/posts/2024/hello.md, and a bundle’s content/en/posts/hello/index.md becomes content/fr/posts/hello/index.md. On a site using translation by filename the copy stays in the same directory and gets the target language’s suffix instead: content/posts/hello.md becomes content/posts/hello.fr.md, and content/posts/hello/index.de.md translated into the default language becomes content/posts/hello/index.md.

Nothing is committed at this point. On a site that deploys on every push, committing the copy straight away would put the English text live at the French URL, which is nobody’s idea of a translation. So the new entry opens as a pending file instead: it shows in the target language’s list with the Unsaved badge, but it doesn’t exist in the repository until you save it.

Save when the page is ready, or at least when it’s a draft you’re happy to have in the repository, and the first save creates the file with a “Create hello” commit. Until then you can leave the tab open as long as you like. Closing the entry without saving throws it away, after the usual unsaved-changes prompt.

Three details worth knowing:

  • The copy is a copy. No machine translation happens here; for that, see Translating with AI.
  • You can translate from any language into any other, not only from the default one, so a German post written first can be the source for its English version.
  • If the source has a translationKey in its frontmatter, the copy keeps it, so the two stay linked even if you rename one of them later. Hugo links pages by that key whatever their filenames, and so does Emet.

If the target file already exists (someone else created it a moment ago, say) you get “Translation already exists” rather than an overwritten file. If the file appears in the repository between starting the translation and saving it, the save is refused and the editor shows the remote version so you can decide what to keep.

Working side by side#

With the side-by-side option on, the source entry appears in an amber panel on the left of the editor. Its header reads Source followed by the source language’s name and code. Below that are the source’s Fields, in the same order and with the same labels as the form on the right (every visible field that has a value, plus any extra frontmatter keys), and its Body. The body is shown rendered by default; the Rendered / Raw switch in the Body header flips it to the raw Markdown when you need to see exactly what was written. Both sections collapse, and the refresh icon reloads the source if someone’s edited it meanwhile. Drag the panel’s right edge to make it wider or narrower; the width is remembered. It’s read-only; edits go in the main editor on the right.

The panel is a preference, not a one-off: once it’s on, it stays on as you move between entries, and it always shows the source for the entry you’re currently editing. Open a German post and it shows the English original; open an unrelated English post and it shows that post’s German translation if there is one, or disappears until you open an entry that has one. Which language counts as “the source” is the language you translated from, falling back to the site’s default language, and you can change it from the translation menu (see below).

The close button in the panel’s header turns it off. To bring it back, open the translation menu in the fields bar and choose Show source.

Switching between translations#

In the editor, the fields bar has a translation menu: a globe icon with the current entry’s language code. Open it to see every language this entry exists in, with the one you’re editing marked Current, and click another language to open that translation. Under Add Translation, Create new translation… opens the same dialog as the entry list, for the entry you have open. That part of the menu disappears once the entry exists in every language.

The menu’s Source panel section has the Show source toggle described above. When the entry exists in more than two languages, the languages are listed underneath it so you can pick which one the panel shows; picking one also turns the panel on.

Entry cards have a badge for each language too: click a solid one to open that translation, or a dashed, greyed-out one to open the Create Translation dialog with that language already picked. Alternatively, pick the language in the header switcher and open the entry from the list like any other.

Opening a translation is subject to the same rules as opening any entry. If the one you have open has unsaved changes, you’re asked to save or discard them first; and if the translation is already open with edits of its own, clicking its badge switches to it rather than reloading it over your work.

The translation you have open is highlighted in the list even when the list is showing a different language, so you can always find your way back.

Translating several entries at once#

Turn on selection mode in the entry list, select the entries, and choose Create translations from the bulk actions. The dialog asks for a target language and tells you what will happen before you commit: “Will create 4 translations · 2 already exist (skipped)”. Entries with unsaved changes are skipped too.

Unlike the single-entry workflow, this one commits straight away, all the copies in one commit (“Create 4 fr translations”). To keep them off the live site, leave Mark copies as drafts ticked: each copy gets draft: true, and the commit carries your skip-CI marker so the site isn’t rebuilt. Nothing is opened in the editor afterwards. This is the tool for bootstrapping a new language across a whole section, not for translating anything. See Bulk actions for the mechanics.

Keeping translations up to date#

Two questions come up once a site has been multilingual for a while: which entries still lack a translation, and which translations have fallen behind their source. The entry list answers both.

What’s missing#

The status filter on a multilingual site has a Missing in option for each language other than the one you’re viewing. Untranslated anywhere finds the entries that exist in no other language at all, and is only offered on sites with three or more languages, where it means something different from Missing in. The line under the list header (DE 9/12 · FR 12/12) shows how complete each language is for the entries you’re looking at, with incomplete languages tinted amber. It only counts what’s already loaded, so it costs nothing, and clicking a language applies its Missing in filter. Combine a Missing in filter with selection mode and Create translations to bootstrap the gaps in one commit: the bulk dialog preselects the filtered language. See The entry list for the filters and Bulk actions for the bulk step.

What’s outdated#

Missing translations are easy to spot. The harder problem is the French page that was translated in March from an English page that has been rewritten twice since. Once the translation statuses have loaded, Emet checks in the background whether each default-language source has changed since its translation was last edited.

In the default language’s list, an outdated translation’s badge turns amber with a small history icon, and hovering it says “Source changed on after this translation was last edited”. In another language’s list, the entry itself gets an amber Outdated chip. The Translation outdated filter shows just those entries.

The automatic check only reads frontmatter, so it costs at most one file read per translation:

  1. lastmod. When both files have one, that decides it. If you keep lastmod up to date, this is all the check ever needs.
  2. date. Otherwise date is compared when both files have one. It’s a weak signal, since a copied translation keeps the source’s date, so only a source whose date was bumped counts as newer.

On GitHub-hosted sites there’s also a deeper check that compares the dates of the latest commits touching each file, for every pair where either file lacks a lastmod. That’s one request per file, so it never runs by itself: click Check freshness in the line under the list header, or pick the Translation outdated filter, which runs it for you. While it runs, the line reads “Checking freshness 3/10…”, and once every pair has been looked up it reads Freshness checked. The results are cached per file version, so coming back to the list doesn’t repeat them. Local and desktop sites have no commit history, so they stop at the frontmatter checks.

Equal dates never count as outdated, and an entry with no comparable dates is simply not flagged. The Refresh button in the list header clears the caches, commit dates included, and runs the automatic check again.

Translating with AI#

If you’ve configured an AI provider, the editor’s AI menu has a Translate action that translates the frontmatter and body of the open entry into another language and opens the result for you to review and save. That’s a different workflow from the copy-and-edit one above, and it’s described in AI translation.