GamendWeb.ContentText (gamend_web v1.0.1216)

Copy Markdown View Source

Translates the user-facing text stored on quests, leaderboards and tournaments.

The database stores one string per field — the source text, in English. It never stores a translation. Quests and leaderboards used to keep a per-locale copy in metadata["titles"][locale], which drifted exactly like the per-locale theme files did: editing the title left 29 stale copies and nothing said which were stale. mix gamend.content.migrate_metadata lifts any of those into the PO files.

Translations come from the content domain, whose msgids mix gamend.content.extract reads back out of the database. That covers both origins:

  • defined in code (a plugin's after_startup, a seed) — extraction picks the row up, and a translator fills it in like any other string. A plugin cannot use dgettext_noop even if it wanted to: it is a separate Mix project with no :gettext dependency, so the macro is not in scope;
  • typed in the admin UI — extractable too, but until someone translates it dgettext falls back to the stored string, so it renders in whatever language it was written in.

The one thing never to do is translate on the way in:

create_quest(title: gettext("Welcome aboard"))   # freezes one locale

That stores whichever locale the server booted in, for every user. Store the source, translate on the way out — which is what this module is for.

Applied where a player-facing LiveView assigns its records. Admin pages deliberately do not use it: an admin editing a quest has to see the string that is actually stored, or they will "fix" a translated title and overwrite the source.

Placeholders

A repeat quest's title carries %{n} (see Gamend.Quests.resolve_counter/2) and that has to survive until here, because the msgid is the string with the placeholder in it. So the number is passed to Gettext as a binding and interpolated into the translation. Substituting it earlier looked right in English and quietly broke every other locale: "Treasures x 3" matches no msgid, so the lookup missed and the card fell back to English — while the bare msgid reaching dgettext/3 without bindings logged a missing Gettext bindings: [:n] error on every anonymous page view.

Summary

Functions

Translates one stored string into the caller's locale, falling back to the string itself.

Translates :title and :description on a record, a list of records, or a map of them. Anything else is returned untouched, so this is safe to apply to a whole assign.

Functions

t(text, bindings \\ %{})

@spec t(String.t() | nil, map()) :: String.t() | nil

Translates one stored string into the caller's locale, falling back to the string itself.

bindings are interpolated into the translation. Pass them for any stored string that contains a %{placeholder}; Gettext logs an error and leaves the placeholder raw on the page when one is missing.

translate(records)

@spec translate(term()) :: term()

Translates :title and :description on a record, a list of records, or a map of them. Anything else is returned untouched, so this is safe to apply to a whole assign.