Skip to content

[Feature] Complete Spanish (es) translation — UI, flash messages, emails, error pages #789

Description

@wintermeyer

Summary

vutuv currently ships in two languages: English (en) and German (de). This request adds a complete Spanish (es) translation. "Complete" means everything a member can see in Spanish: every UI string, every flash/toast message, all transactional emails, error pages, and the language picker — not just the page chrome.

Background: how internationalization works today

vutuv uses Gettext (VutuvWeb.Gettext, backed by priv/gettext) for almost all on-screen text, but emails are a separate mechanism. There are three distinct translation surfaces, and a complete language has to cover all three:

  1. On-screen UI + flash messages → Gettext .po files.
    Every gettext(...)/dgettext(...) call resolves against priv/gettext/<locale>/LC_MESSAGES/*.po. Today only en and de exist:

    • priv/gettext/default.pot — the extracted catalog, currently ~1012 messages.
    • priv/gettext/errors.pot — Ecto/changeset error catalog.
    • priv/gettext/{en,de}/LC_MESSAGES/{default.po,errors.po}.
      Flash messages (put_flash/3) are ordinary gettext(...) strings, so they live in default.po and are covered by translating it. (96 put_flash call sites exist; the bulk are already gettext-wrapped — see the audit task below.)
  2. Emails → per-locale .eex template files (NOT Gettext).
    Vutuv.Notifications.Emailer (lib/vutuv/notifications/emailer.ex) picks the body template by the recipient's locale, building the filename as "<base>_<locale>.text" and rendering it via VutuvWeb.EmailText. The subject line is translated with Gettext.with_locale/3 in the recipient's locale. So each email type has one template file per language under lib/vutuv_web/templates/email/. Today every type has a _de and an _en variant; Spanish needs a matching _es file for each:

    • _signature_es.text.eex (shared signature partial)
    • email_creation_email_es.text.eex
    • login_email_es.text.eex (the login PIN mail — critical path)
    • registration_email_es.text.eex
    • registration_attempt_es.text.eex
    • verification_confirmation_es.text.eex
    • unread_messages_es.text.eex
    • user_deletion_email_es.text.eex
    • moderation_warning_es.text.eex
    • moderation_frozen_es.text.eex
    • moderation_review_es.text.eex
    • moderation_revised_es.text.eex
    • moderation_suspension_es.text.eex
    • moderation_deactivation_es.text.eex
    • moderation_admin_digest_es.text.eex / moderation_admin_urgent_es.text.eex (admin-facing moderation queue mails — in scope, must be translated)
    • _footer.text.eex is locale-neutral and shared; check whether it contains any language-specific text.
    • ad_booking_de.text.eex currently exists only in German — confirm whether it is user-facing before deciding to translate.

    Ordering matters: Emailer.get_locale/1 falls back to "en" for any locale not in the configured :locales list. The moment es is added to config, the Emailer will look for *_es.text.eex and crash if the file is missing. So the config change and the email templates must ship in the same deploy.

  3. Config → the master locale list.
    VutuvWeb.Plug.Locale (request locale from the user's locale field or Accept-Language) and VutuvWeb.Live.LiveLocale (LiveView re-apply) both gate on config[:locales]:

    • config/config.exslocales: ~w(en de)
    • config/prod.exslocales: ~w(en de)

Scope — what "complete" covers

  • All UI text (default.po, ~1012 messages) translated to Spanish.
  • All validation/error text (errors.po) translated.
  • All flash/toast messages render in Spanish (covered by default.po; audit that every user-facing put_flash is gettext-wrapped — fix any that are not as part of this work).
  • All transactional emails (subjects + bodies + signature) have Spanish templates, including the login-PIN mail and the admin-facing moderation queue mails (moderation_admin_digest, moderation_admin_urgent).
  • Error pages (404/403/500, VutuvWeb.ErrorHTML / errors.po) render in Spanish.
  • Spanish is selectable and persists per user (see implementation).

Explicitly out of scope (stay German only)

  • lib/vutuv_web/templates/page/datenschutzerklaerung.html.heex (privacy policy / Datenschutzerklärung)
  • lib/vutuv_web/templates/page/impressum.html.heex (imprint / Impressum)

These are hardcoded German legal pages and must remain German regardless of the member's chosen language.

Implementation checklist (where the work lives)

  1. Add the locale to configconfig/config.exs and config/prod.exs: locales: ~w(en de es). (Ship together with the email templates, per the crash note above.)
  2. Scaffold and translate the Gettext catalogs:
    • mix gettext.extract to refresh default.pot, then mix gettext.merge priv/gettext --locale es to create priv/gettext/es/LC_MESSAGES/{default.po,errors.po}.
    • Translate every msgstr. (See the "German UI vocabulary" conventions in the project memory for the kind of care German got; Spanish needs equivalent native-quality choices, not machine output.)
  3. Add the Spanish email templates — one *_es.text.eex per type listed above, plus _signature_es.text.eex, under lib/vutuv_web/templates/email/.
  4. Add Spanish to the language pickerlib/vutuv_web/templates/user/form_content.html.heex currently hardcodes [{gettext("English"), "en"}, {gettext("German"), "de"}]; add {gettext("Spanish"), "es"}.
  5. Add the endonym for the agent-doc language hint@language_names in lib/vutuv_web/agent_docs/agent_docs.ex (%{"en" => "English", "de" => "Deutsch"}): add "es" => "Español". Once es PO files exist, the public pages' ?lang=es agent format and the cross-language <link>/hint work automatically (AgentDocs.doc_locale/2 keys off Gettext.known_locales).
  6. No JS string work needed — the only dynamic user-facing JS text (the live username-availability verdict in assets/js/app.js) comes from the server response (data.message), so it is already Gettext-translated server-side. Confirm this still holds.

Verification (do not rely on mix test alone)

Per CLAUDE.md: the Swoosh test adapter never renders a real email, so green tests do not prove the Spanish emails work. After implementing:

  • Set a test user's locale to es, walk the main flows in a browser, and trigger flashes (login, post, follow, errors) to confirm Spanish copy.
  • Trigger and read the actual Spanish emails — especially the login PIN mail — via the dev /sent_emails inbox, to confirm the _es templates render and the subjects are Spanish.
  • Hit a public page as …/<slug>.md?lang=es to confirm the agent-format Spanish path resolves.

Acceptance criteria

  • Setting a user's language to Spanish shows the entire UI, all flashes, and all emails in Spanish.
  • No untranslated (English/German) strings leak on any member-facing page or email when Spanish is active.
  • The login PIN email and all moderation emails render correctly in Spanish.
  • Error pages render in Spanish.
  • Impressum and Datenschutzerklärung remain German.
  • Config, PO files, and email templates ship together (no missing-template crash).

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions