Translations

French, Dutch and German. Source labels are in English. Subjects live in the template registration as msgids and are translated per recipient language at send time — there is no metadata sidecar and no front-matter round-trip.

make i18n              # update the catalogs

The one failure that looks like success

Two things follow from that, and both are already done for you:

  • The kit's layout emits i18n:domain, so no author has to remember it.
  • The test suite asserts that FR and NL output actually differ. That assertion is the only thing standing between you and a site quietly sending English to every commune.

In a template

<p i18n:translate="">email_intro_item_published</p>
<img src="${theme/logo_url}" i18n:attributes="alt logo_alt" alt="iMio" />

Use i18n:translate="" with the msgid as the element's text. For attributes, i18n:attributes.

In a registration

The subject and the optional preheader are msgids:

from imio.pm.notifications import _

emailkit = {
    "directory": "templates",
    "templates": {
        "item_published": {
            "subject": _("email_subject_item_published"),
            "preheader": _("email_preheader_item_published"),
        },
    },
}

They are translated once per language group, so an FR member and an NL member get different subject lines from one .send() call.

Pass msgids, not translated strings

This is why the content-rule action passes cta_label as a msgid rather than a translated string. The same trap applies to anything you pass through .with_context() yourself.

Language resolution

render() translates into the language it was given, or the negotiated one. Inside .send(), that language comes from each recipient's own preference, and recipients without one are grouped under the site's default.

fr and fr-BE are two distinct groups, with no normalisation: Belgian French groups thousands differently from French French, so they are genuinely two different renders.

Dates and numbers

Do not format them by hand in a template. The three locale helpers are already bound to the render language:

<td>${python: format_date(item.effective)}</td>
<td>${python: format_number(amount)}</td>

See render() for the full list and the fr / en output of each.

  • Testing — the FR ≠ NL assertion, and why it is not optional.
  • Recipients — how each recipient's language is resolved.