Uh oh!
There was an error while loading. Please reload this page.
Add gettext support for Dashboard sidebar labels - #522
Merged
Conversation
- Tab and Group structs now accept optional `gettext_backend` and `gettext_domain` fields, defaulting to `nil` and `"default"` (backwards compatible — tabs without a backend render the raw label as before) - New `Tab.localized_label/1`, `Tab.localized_tooltip/1`, `Group.localized_label/1` resolve the current locale via `Gettext.dgettext/3` at render time, with nil-guard so divider tabs and unlabeled groups stay safe - Sidebar / AdminSidebar / TabItem components now route every label and tooltip render site through the localizer (14 sites total) - `Tab.divider/1` and `Tab.group_header/1` helpers also accept and forward the new fields - 40 tests across `test/phoenix_kit/dashboard/` and `test/phoenix_kit_web/components/dashboard/` cover raw fallback, nil label, translated render under `ru` locale, and helper round-trip - Bump `@version` 1.7.105 → 1.8.0; CHANGELOG entry left for maintainer
- New guide `guides/per-module-i18n.md` documenting how each PhoenixKit module sets up its own Gettext backend, ships its own `.po` files, and registers admin/settings/dashboard tabs with `gettext_backend:` so labels translate at render time according to the user's locale (the API shipped in 1.8.0) - Covers: setup checklist, step-by-step `mix.exs` / backend / `.po` flow, dynamic_children locale handling, dividers and group headers, tooltips, greenfield example, retrofitting checklist, smoke test pattern, common pitfalls, and rollout phases - Wired into `mix.exs` ExDoc extras so it ships to hexdocs.pm under "Guides" - Linked from `guides/README.md` index
A parent app whose Phoenix server kept running across the upgrade from phoenix_kit ~> 1.7.x to 1.8 hits FunctionClauseError on every admin or dashboard render. Cause: the Dashboard.Registry GenServer caches Tab and Group structs in ETS for the lifetime of the GenServer process. Hot code reload swaps the new Tab/Group modules into place but the cached struct values keep their pre-1.8 underlying map (no `:gettext_backend` / `:gettext_domain` keys). The 1.8 `localized_label/1` and `localized_tooltip/1` clauses pattern-matched those keys directly and failed to match, raising FunctionClauseError instead of falling back to the raw label. The fix replaces the field-pattern clause with a `Map.get/2` lookup that treats a missing key the same as `nil` — i.e. "no backend configured" — so old-shape structs cached before the upgrade gracefully render the raw label until the parent app is restarted and ETS repopulates with the new shape. - `Tab.localized_label/1`, `Tab.localized_tooltip/1`, `Group.localized_label/1` rewritten to read `gettext_backend` and `gettext_domain` via `Map.get/2` - Behavior unchanged for fresh, well-formed structs (all existing tests still pass) - New regression tests in `tab_test.exs` and `group_test.exs` simulate the stale-struct scenario via `Map.delete/2` of the new keys; without the fix these tests raise FunctionClauseError, with it they return the raw label - Moduledoc on each function explains the resilience contract so future refactors don't accidentally regress to strict pattern matching
The Phase 1 commit (42497487) bumped @Version 1.7.105 → 1.8.0 in mix.exs. That was wrong: per project conventions the package version is set by the upstream maintainer at release time, not by feature PRs adding API surface. The maintainer will pick the appropriate version (patch / minor / major) when shipping the release. - `mix.exs` — `@version` restored to "1.7.105" - `guides/per-module-i18n.md` — replaced concrete `~> 1.8` references and "PhoenixKit ≥ 1.8" framing with version-neutral phrasing that defers to the PhoenixKit CHANGELOG for the exact minimum version once the API ships
…#522) Reviewer caught that `PhoenixKitWeb.Live.Modules.extract_admin_links/1` in `lib/phoenix_kit_web/live/modules.ex` packs `tab.label` raw into a plain map, then `lib/phoenix_kit_web/live/modules.html.heex:706` renders `{link.label}` directly. So every module's quick-link buttons on the `/admin/modules` page render in untranslated English regardless of the user's locale, even when the module ships its own gettext backend on the underlying tab. Phase 1 had explicitly scoped this site as a "documented limitation" not worth touching, but the rendered output IS user-visible — the limitation defeats the feature on this page. Fix is one-line: route the label through `Tab.localized_label/1` at extraction time. Since extraction happens in mount per request, the locale is already set correctly by the parent app's locale plug. - `lib/phoenix_kit_web/live/modules.ex` — `alias PhoenixKit.Dashboard.Tab`, call `Tab.localized_label(tab)` in the map-building step at line 406 - No new tests: the underlying `Tab.localized_label/1` is already covered by 24+ tests in `test/phoenix_kit/dashboard/tab_test.exs` including the raw-fallback / nil / translated paths; this commit is a one-line application of an already-verified API to a missed call site
6 tasks
timujinne added a commit
to timujinne/phoenix_kit_newsletters
that referenced
this pull request
May 8, 2026
Newsletters now ships its own Gettext backend (PhoenixKit.Newsletters.Gettext)
and a `priv/gettext/{en,ru,et}/LC_MESSAGES/default.po` catalogue covering
every admin sidebar tab label registered by the module. Each Tab.new!
registration declares `gettext_backend: PhoenixKit.Newsletters.Gettext`,
so once the consumers `phoenix_kit` dep includes the gettext_backend
API (PR BeamLabEU/phoenix_kit#522 — currently open), the sidebar
resolves labels at request time against the user locale.
Behaviour without the new core API: tabs render raw English (graceful
degradation) — the new struct fields are silently dropped by Tab.new
on pre-API releases. No runtime error.
- lib/phoenix_kit/newsletters/gettext.ex (new) — Gettext.Backend with
otp_app: :phoenix_kit_newsletters, follows guides/per-module-i18n.md
- lib/phoenix_kit/newsletters/newsletters.ex — every Tab.new! (9 sites)
carries gettext_backend: PhoenixKit.Newsletters.Gettext (gettext_domain
defaults to "default")
- priv/gettext/default.pot, priv/gettext/{en,ru,et}/LC_MESSAGES/default.po —
9 msgids for Newsletters, Broadcasts, New/Edit Broadcast, Broadcast
Details, Lists, New/Edit List, List Members; en is 1:1, ru and et
filled
- mix.exs:
extra_applications adds :gettext
deps adds {:gettext, "~> 1.0"}
package files: adds priv (without this the .po files would not
ship to Hex and translation would be silently broken in
production — caught by reviewer pre-merge)
- test/phoenix_kit/newsletters/i18n_test.exs (new) — 4 smoke assertions:
every tab carries the backend, ru and et resolve "Newsletters" to
the expected translation, unknown locale falls back to msgid
- test/test_helper.exs — conditional ExUnit.start that excludes
:requires_phoenix_kit_i18n_api when Tab.localized_label/1 is not
loaded; tests run automatically once the consumers phoenix_kit dep
resolves to a release that ships the API…ernal playbook Two updates carrying over what we learned migrating phoenix_kit_newsletters (BeamLabEU/phoenix_kit_newsletters#12), so the next module follows the same path without re-discovering the traps. guides/per-module-i18n.md (public, ships to hexdocs) - Setup checklist grew from 10 to 11 steps; new explicit steps for priv in package files, conditional CI skip, manual .pot maintenance - New section "Hex package shape" with verify-via-tar recipe — priv in package files: is load-bearing or .po files dont reach Hex - New section "Conditional CI skip" with the Code.ensure_loaded?-then- function_exported? helper and matching @moduletag on the smoke test - New section "Version bump and CHANGELOG (owned packages)" — unlike phoenix_kit core where maintainer owns versioning, every phoenix_kit_<x> fork is maintained by the team that owns it, so version and CHANGELOG entries do go in the same commit - Test pattern updated to include @moduletag :requires_phoenix_kit_i18n_api - Retrofitting checklist updated to match the new step order dev_docs/instructions/2026-05-08-per-module-i18n-procedure.md (new) - End-to-end operational procedure for an agent / developer applying the i18n migration to one specific phoenix_kit_<x> package - Documents 9 gotchas verbatim from the Newsletters pilot: skip-worktree on mix.exs, sparse-checkout error red herring, manual .pot maintenance, priv missing from files:, function_exported? with unloaded modules, _build/ root ownership, mix.lock cherry-pick conflict, no gh CLI in container, dont push local main - Step-by-step git/build/test/PR sequence including the path-dep workflow against /tmp/pk-pr/i18n and the cherry-pick + curl-PR recipe
Merged
4 tasks
Discovered while migrating phoenix_kit_customer_support (BeamLabEU/phoenix_kit_customer_support#3): the package shipped a test/test_helper.exs that called System.cmd("psql", ["-lqt"], ...) without a rescue clause to probe for a test database. On any container without postgresql-client on PATH, System.cmd/3 raises ErlangError :enoent before mix test loads any test, so even the conditionally- skipped i18n tests never get a chance to run. Newsletters did not have this because its pre-migration test_helper.exs was a bare ExUnit.start(). Playbook gotcha 10 documents the symptom, diagnosis, and the try/rescue fix; flags this as a class of bug to audit any package that does System.cmd-style work in test_helper.exs.
This was referenced May 8, 2026
After rolling out the per-module i18n migration to all 7 phoenix_kit_<x> packages (newsletters, customer_support, emails, billing, ecommerce, legal, crm), the implementer + reviewer agents surfaced several patterns the playbook had missed. Capturing them here so the body-string follow-up sweep and any future per-module work has the full picture. - Gotcha 11: working-tree mix.exs vs committed mix.exs — verify committed form via git show <sha>:mix.exs, not by reading file - Gotcha 12: skip-worktree may already be H (no-op step 1) on fresher repos - Gotcha 13: dynamic-label tabs (label: role.name etc.) do NOT receive gettext_backend — runtime strings have no static msgid - Gotcha 14: files using `use PhoenixKitWeb, :live_view` are off-limits — host web module injects Gettext at host-app level, package cannot override - Gotcha 15: body-string PhoenixKitWeb.Gettext references in lib/.../web/* are out of scope for tab-only i18n; document in PR description, separate sweep - Gotcha 16: Tab count vs unique msgid count — N Tab.new! sites often share M ≤ N msgids; count msgids by distinct label values (table of actual rollout counts included)
ddon added a commit
to BeamLabEU/phoenix_kit_emails
that referenced
this pull request
May 8, 2026
Wires PhoenixKit.Modules.Emails.Gettext as the i18n backend for all 10 admin/settings sidebar tabs (Emails, Dashboard, Email Details, Templates, New Template, Edit Template, Queue, Blocklist). Ships en/ru/et catalogues under priv/gettext/. Requires the gettext_backend Tab API from BeamLabEU/phoenix_kit#522; on older releases tabs render raw English msgids (graceful degradation via test_helper.exs conditional skip).
ddon added a commit
that referenced
this pull request
May 8, 2026
…525 Code/doc fixes addressing one finding per PR (or several where trivial). Each closes a NITPICK or IMPROVEMENT-LOW from the matching CLAUDE_REVIEW.md; design-level / breaking / risky items deferred per the FOLLOW_UP.md "Skipped" sections. - #516: Drop dead `String.to_atom` fallback in OAuth interpolate_url - #518: Delete stray 0-byte pages_html.ex - #519: Fix stale `viewer={true}` template comment + login_path trailing-slash self-loop guard - #521: Resolution-order doc on permission_key_for_admin_view/1 - #522: Hot-reload safety pitfall in per-module-i18n.md - #523: KnownPackages — max-pages cap, ensure_table race comment, Logger-levels operational signals in moduledoc - #524: __mix_recompile__?/0 note next to apply/3 explanation - #525: LanguageSwitcher attr doc atom/string keys + DRY resolve_url per-language + JS sortable:flash defensive status check Plus FOLLOW_UP.md per PR enumerating closed vs deferred items. PR #525's FOLLOW_UP also captures the bundled DnD audit trail (table_default drag-handle scoping, sortable:flash, TR cell-width preservation) absent from the original PR body. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ddon added a commit
that referenced
this pull request
May 8, 2026
Three-day window of accumulated work since 1.7.105 (2026-05-05): PRs #516, #518, #519, #521, #522, #523, #524, #525, plus the review-doc suite and post-merge triage closing nitpicks across all eight. Headline changes — V111 PDF library tables, DB module extracted to phoenix_kit_db, MediaBrowser modal viewer, sidebar gettext API, live Hex.pm catalog, publishing routing-strategy shim closing the /:locale/<literal>/... host-route shadowing bug, LanguageSwitcher :per_translation_urls, and bundled DnD improvements (drag-handle scoping, sortable:flash, TR cell-width preservation). All changes are strictly additive / non-breaking; one transitional extraction (DB → phoenix_kit_db) requires the paired Hex package once it ships. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ddon pushed a commit
to BeamLabEU/phoenix_kit_billing
that referenced
this pull request
May 9, 2026
Introduces `PhoenixKitBilling.Gettext` with `en`/`ru`/`et` translation catalogues covering all 13 Tab registrations in `admin_tabs/0`, `settings_tabs/0`, and `user_dashboard_tabs/0`. Every `Tab.new!/1` call now carries `gettext_backend: PhoenixKitBilling.Gettext` and `gettext_domain: "default"`. The parent app's locale mechanism resolves the label at render time via `Tab.localized_label/1` once BeamLabEU/phoenix_kit#522 ships. On older releases (or when the API is absent in CI) all four i18n tests are excluded automatically — tabs continue to display raw English labels (graceful degradation). Translations shipped: - en: identity (msgstr == msgid) - ru: Биллинг, Панель управления, Заказы, Счета, Транзакции, Подписки, Типы подписок, Платёжные профили, Валюты, Платёжные системы, Мои заказы - et: Arveldus, Töölaud, Tellimused, Arved, Tehingud, Püsitellimused, Tellimuste tüübid, Arvelduse profiilid, Valuutad, Maksevahendajad, Minu tellimused Note: Estonian uses "Püsitellimused" (recurring/standing orders) for "Subscriptions" to disambiguate from "Tellimused" used for "Orders".
ddon pushed a commit
to BeamLabEU/phoenix_kit_ecommerce
that referenced
this pull request
May 9, 2026
Introduces `PhoenixKitEcommerce.Gettext` with `en`/`ru`/`et` catalogues covering all 9 msgids registered via `admin_tabs/0`, `settings_tabs/0`, and `user_dashboard_tabs/0`. Tab struct fields `gettext_backend` and `gettext_domain` require `PhoenixKit.Dashboard.Tab.localized_label/1` from BeamLabEU/phoenix_kit#522. Until that API ships on Hex, i18n tests are excluded via a `Code.ensure_loaded?` guard in `test/test_helper.exs` (graceful degradation — tabs render raw English msgids on older deps). Adds `{:gettext, "~> 1.0"}` dep, `:gettext` to `extra_applications`, and `priv` to `package files:` so catalogues ship in the Hex tarball. Bumps version to 0.1.4.
ddon pushed a commit
to BeamLabEU/phoenix_kit_legal
that referenced
this pull request
May 9, 2026
Introduces `PhoenixKit.Modules.Legal.Gettext` with `en`/`ru`/`et`
catalogues covering the single admin settings tab label ("Legal").
Wires `gettext_backend:` + `gettext_domain:` onto the `Tab.new!` call
in `settings_tabs/0`.
Requires `phoenix_kit` release that ships the `gettext_backend` Tab API
(BeamLabEU/phoenix_kit#522). On older releases the tab renders the raw
English msgid — graceful degradation is tested and confirmed.
- `lib/phoenix_kit_legal/gettext.ex` — Gettext backend
- `priv/gettext/` — manually-maintained .pot + en/ru/et .po catalogues
- `test/phoenix_kit_legal/i18n_test.exs` — smoke tests (4 assertions)
- `test/test_helper.exs` — conditional exclude for pre-#522 CI builds
- `mix.exs` — `:gettext` added to `extra_applications`ddon added a commit
to BeamLabEU/phoenix_kit_legal
that referenced
this pull request
May 9, 2026
Flips `use Gettext, backend: PhoenixKitWeb.Gettext` to the module's own backend in legal.ex, web/cookie_consent.ex, web/settings.ex; updates translate_title/2 accordingly. Consent-widget strings and page titles now resolve per-locale on the published phoenix_kit (sidebar tab label still falls back to "Legal" until BeamLabEU/phoenix_kit#522 ships). Auto-extracts priv/gettext/default.pot via mix gettext.extract --merge (126 msgids), drops the manual-maintenance comment, replaces __extract_titles__/0 with __extract_strings__/0 covering both page titles and the tab label. Ships full RU and ET translations for every extracted string; adds Plural-Forms header to en/LC_MESSAGES/default.po. Splits i18n_test.exs into two describe blocks: ungated assertions on the module's own catalogue (page titles, consent-widget banner, category names — runs against any phoenix_kit release) and the tab-label tests that remain gated behind :requires_phoenix_kit_i18n_api. mix precommit clean; mix test 32 passed, 4 excluded (was 23 / 4). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ddon pushed a commit
to BeamLabEU/phoenix_kit_customer_support
that referenced
this pull request
May 9, 2026
Introduces `PhoenixKitCustomerSupport.Gettext` with `en`/`ru`/`et`
catalogues for all 4 admin sidebar tab labels registered by this module.
Changes:
- `lib/phoenix_kit_customer_support/gettext.ex` — new Gettext backend
(`use Gettext.Backend, otp_app: :phoenix_kit_customer_support`)
- `lib/phoenix_kit_customer_support.ex` — `gettext_backend:` added to
all Tab.new! calls in admin_tabs/0, settings_tabs/0, user_dashboard_tabs/0
- `priv/gettext/default.pot` — manually maintained msgid template
- `priv/gettext/{en,ru,et}/LC_MESSAGES/default.po` — translations for
"Customer Support", "Tickets", "My Tickets"
- `mix.exs` — `:gettext` in extra_applications and deps; `priv` added to
package files so .po files ship to Hex consumers
- `test/test_helper.exs` — conditional `:requires_phoenix_kit_i18n_api`
skip (graceful degradation when running against pre-API phoenix_kit)
- `test/phoenix_kit_customer_support/i18n_test.exs` — smoke test for
gettext_backend wiring and ru/et locale resolution
- `CHANGELOG.md` / `@version` — bumped to 0.1.1
Depends on BeamLabEU/phoenix_kit#522 for `Tab.localized_label/1`. On
older `phoenix_kit` releases, tabs render raw English (graceful
degradation via conditional CI skip in test_helper.exs).4 tasks
ddon pushed a commit
to BeamLabEU/phoenix_kit_crm
that referenced
this pull request
May 9, 2026
Introduces PhoenixKitCRM.Gettext (priv/gettext/ with en/ru/et
catalogues) so every admin sidebar tab carries its own translation
backend instead of referencing the host app's PhoenixKitWeb.Gettext.
- lib/phoenix_kit_crm/gettext.ex: new Gettext.Backend for :phoenix_kit_crm
- lib/phoenix_kit_crm.ex: admin_tabs + settings_tab converted from
%Tab{} struct literals with gettext() wrappers to Tab.new!() with
plain string labels and gettext_backend: PhoenixKitCRM.Gettext
- lib/phoenix_kit_crm/column_config.ex, web/column_modal.ex,
web/cell_format.ex: swap use Gettext backend from PhoenixKitWeb.Gettext
to PhoenixKitCRM.Gettext; existing gettext() macro calls preserved
- priv/gettext/default.pot: Tab labels maintained manually (not auto-
extracted); column_modal + cell_format msgids are auto-extracted
- priv/gettext/{en,ru,et}/LC_MESSAGES/default.po: full translations for
Tab labels (CRM/Overview/Organizations); column_modal/cell_format
strings translated for ru, left empty for et (graceful fallback)
- test/test_helper.exs: conditional ExUnit exclude for
:requires_phoenix_kit_i18n_api when Tab.localized_label/1 absent
- test/phoenix_kit_crm/i18n_test.exs: smoke tests for backend wiring
and locale resolution
- mix.exs: add {:gettext, ~> 1.0}, :gettext to extra_applications,
priv to package files:, bump version to 0.2.2
Graceful degradation: on phoenix_kit releases that predate PR #522
(Tab.localized_label/1 not shipped), all i18n tests are auto-excluded
and tab labels render as raw English strings. Refs: BeamLabEU/phoenix_kit#522
This was referenced May 10, 2026
timujinne added a commit
to timujinne/phoenix_kit_legal
that referenced
this pull request
May 11, 2026
Introduces `PhoenixKit.Modules.Legal.Gettext` with `en`/`ru`/`et`
catalogues covering the single admin settings tab label ("Legal").
Wires `gettext_backend:` + `gettext_domain:` onto the `Tab.new!` call
in `settings_tabs/0`.
Requires `phoenix_kit` release that ships the `gettext_backend` Tab API
(BeamLabEU/phoenix_kit#522). On older releases the tab renders the raw
English msgid — graceful degradation is tested and confirmed.
- `lib/phoenix_kit_legal/gettext.ex` — Gettext backend
- `priv/gettext/` — manually-maintained .pot + en/ru/et .po catalogues
- `test/phoenix_kit_legal/i18n_test.exs` — smoke tests (4 assertions)
- `test/test_helper.exs` — conditional exclude for pre-#522 CI builds
- `mix.exs` — `:gettext` added to `extra_applications`timujinne added a commit
to timujinne/phoenix_kit_emails
that referenced
this pull request
May 11, 2026
Wires PhoenixKit.Modules.Emails.Gettext as the i18n backend for all 10 admin/settings sidebar tabs (Emails, Dashboard, Email Details, Templates, New Template, Edit Template, Queue, Blocklist). Ships en/ru/et catalogues under priv/gettext/. Requires the gettext_backend Tab API from BeamLabEU/phoenix_kit#522; on older releases tabs render raw English msgids (graceful degradation via test_helper.exs conditional skip).
timujinne added a commit
to timujinne/phoenix_kit_customer_support
that referenced
this pull request
May 11, 2026
Introduces `PhoenixKitCustomerSupport.Gettext` with `en`/`ru`/`et`
catalogues for all 4 admin sidebar tab labels registered by this module.
Changes:
- `lib/phoenix_kit_customer_support/gettext.ex` — new Gettext backend
(`use Gettext.Backend, otp_app: :phoenix_kit_customer_support`)
- `lib/phoenix_kit_customer_support.ex` — `gettext_backend:` added to
all Tab.new! calls in admin_tabs/0, settings_tabs/0, user_dashboard_tabs/0
- `priv/gettext/default.pot` — manually maintained msgid template
- `priv/gettext/{en,ru,et}/LC_MESSAGES/default.po` — translations for
"Customer Support", "Tickets", "My Tickets"
- `mix.exs` — `:gettext` in extra_applications and deps; `priv` added to
package files so .po files ship to Hex consumers
- `test/test_helper.exs` — conditional `:requires_phoenix_kit_i18n_api`
skip (graceful degradation when running against pre-API phoenix_kit)
- `test/phoenix_kit_customer_support/i18n_test.exs` — smoke test for
gettext_backend wiring and ru/et locale resolution
- `CHANGELOG.md` / `@version` — bumped to 0.1.1
Depends on BeamLabEU/phoenix_kit#522 for `Tab.localized_label/1`. On
older `phoenix_kit` releases, tabs render raw English (graceful
degradation via conditional CI skip in test_helper.exs).timujinne added a commit
to timujinne/phoenix_kit_billing
that referenced
this pull request
May 11, 2026
Introduces `PhoenixKitBilling.Gettext` with `en`/`ru`/`et` translation catalogues covering all 13 Tab registrations in `admin_tabs/0`, `settings_tabs/0`, and `user_dashboard_tabs/0`. Every `Tab.new!/1` call now carries `gettext_backend: PhoenixKitBilling.Gettext` and `gettext_domain: "default"`. The parent app's locale mechanism resolves the label at render time via `Tab.localized_label/1` once BeamLabEU/phoenix_kit#522 ships. On older releases (or when the API is absent in CI) all four i18n tests are excluded automatically — tabs continue to display raw English labels (graceful degradation). Translations shipped: - en: identity (msgstr == msgid) - ru: Биллинг, Панель управления, Заказы, Счета, Транзакции, Подписки, Типы подписок, Платёжные профили, Валюты, Платёжные системы, Мои заказы - et: Arveldus, Töölaud, Tellimused, Arved, Tehingud, Püsitellimused, Tellimuste tüübid, Arvelduse profiilid, Valuutad, Maksevahendajad, Minu tellimused Note: Estonian uses "Püsitellimused" (recurring/standing orders) for "Subscriptions" to disambiguate from "Tellimused" used for "Orders".
ddon pushed a commit
that referenced
this pull request
May 11, 2026
Phase 3 of the per-module i18n migration (Q7 deferred from PR #522). All core admin sidebar tabs now carry gettext_backend: PhoenixKitWeb.Gettext so Tab.localized_label/1 can translate them at render time. Wired tabs: - admin_dashboard, admin_users (admin_tabs.ex explicit Tab literals) - admin_activity, admin_media (admin_tabs.ex explicit Tab literals) - admin_settings, admin_settings_media (admin_tabs.ex explicit Tab literals) - admin_subtab/8 helper already done by orchestrator (covers 13 subtabs) - admin_modules_page already done by orchestrator - dashboard_home, dashboard_settings (registry.ex user-dashboard defaults) - admin_jobs (jobs.ex Module.admin_tabs/0) Translation work (ru + et default.po): - Filled empty msgstr entries for: Permissions, Settings, Health, Integrations, Organization (both locales); Dashboard, Users, Roles, Sessions (et only — ru already had translations) - Appended 13 new entries each locale: Manage Users, Live Sessions, Referral Codes, Activity, Media, General, Authorization, Dimensions, Jobs, Languages, SEO, Sitemap, Home - Modules entry already added by orchestrator (ru only); added for et No @Version bump. No CHANGELOG entry (maintainer-owned).
5 tasks
ddon pushed a commit
to BeamLabEU/phoenix_kit_document_creator
that referenced
this pull request
May 11, 2026
Implements the per-module i18n pattern documented in
`phoenix_kit/guides/per-module-i18n.md`, mirroring the proven shape from
`phoenix_kit_catalogue`. Sidebar tab labels, page titles, buttons, forms,
flash messages, validation errors, and every user-facing string from the
`Errors` atom dictionary now translate to `ru` and `et` at render time
based on the user's locale.
Changes:
* Add `PhoenixKitDocumentCreator.Gettext` backend under
`lib/phoenix_kit_document_creator/gettext.ex` — owns this module's
catalogues under `priv/gettext/`; locale is set per-request by the
parent app.
* `mix.exs`: add `:gettext` to `extra_applications` and to `deps`, and
add `priv` to `package.files` so `.po` files actually ship to Hex —
without this, every consumer installing from Hex would silently get
raw msgids (the guide calls this out explicitly).
* Replace every `use Gettext, backend: PhoenixKitWeb.Gettext` in `lib/`
(`documents_live`, `google_oauth_settings_live`, the modal component,
and `errors`) with `use Gettext, backend: PhoenixKitDocumentCreator.Gettext`.
Published packages cannot rely on the parent app's
`PhoenixKitWeb.Gettext` — that backend belongs to phoenix_kit core,
not to this module.
* Wire `gettext_backend:` + `gettext_domain: "default"` onto all four
`%Tab{}` registrations (3 admin sidebar tabs + 1 settings tab) so
`Tab.localized_label/1` resolves them through this catalogue.
* Add `priv/gettext/{default.pot,en,ru,et}/LC_MESSAGES/default.po`:
~150 msgids covering tab labels, page titles, section headings,
buttons, loading states, form labels, table headers, placeholders,
status badges, flash messages, error messages, and the full
`Errors.message/1` atom dictionary. Maintained manually because
`mix gettext.extract` cannot see plain `%Tab{label: "…"}` strings —
no `dgettext` macro at the call site.
* Move `page_title:` `gettext(...)` call from `mount/3` to
`handle_params/3` in both LiveViews. `mount/3` runs inside the
`[:phoenix, :live_view, :mount]` telemetry span that the parent app's
locale-sync hook listens to via `:stop` — so a `gettext` call inside
`mount/3` resolves before the process-global Gettext locale is set
and captures the raw English msgid. `handle_params/3` runs after the
hook fires and gets the correct translation. For `DocumentsLive` the
title now also reflects `live_action` (Documents vs Templates).
* Add `test/gettext_test.exs`: smoke test asserting ru/et translations
for all four tab labels via `Tab.localized_label/1`, plus fallback
paths (no backend → raw label, unknown msgid → returns msgid).
Tagged `:requires_phoenix_kit_i18n_api`; `test/test_helper.exs`
conditionally excludes the tag when running against a `phoenix_kit`
release that pre-dates the `gettext_backend` API (introduced by
BeamLabEU/phoenix_kit#522).
Per the guide, `@version` and `CHANGELOG.md` are intentionally unchanged
— both are maintainer-owned and derived from commit messages at release
time.
Refs: BeamLabEU/phoenix_kit#522 (gettext_backend API)
See: phoenix_kit/guides/per-module-i18n.md9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
gettext_backendandgettext_domainfields toPhoenixKit.Dashboard.TabandPhoenixKit.Dashboard.Group, plus three resolver functions (Tab.localized_label/1,Tab.localized_tooltip/1,Group.localized_label/1).Map.get/2, so old struct shapes cached in ETS before the upgrade fall back to raw labels rather than raisingFunctionClauseError.Motivation
Today, every parent app that wants translated sidebar tab labels has to hack
PhoenixKit.Dashboard.Registry's ETS table at mount-time per LiveView. That patches exactly one tab, leaves every other module's tabs in English, and races between concurrent users on different locales. With this PR, each module declares its own Gettext backend on its tab/group registrations and the sidebar resolves the locale at request time — no ETS patching, no race, no per-LV mount hook.Changes
API additions (backwards compatible)
Tab.gettext_backend,Tab.gettext_domain(defaultsnil,"default")Group.gettext_backend,Group.gettext_domain(same defaults)Tab.localized_label/1,Tab.localized_tooltip/1,Group.localized_label/1Tab.new/1,Tab.divider/1,Tab.group_header/1, bothGroup.new/1clauses accept the new fieldsRender sites updated
lib/phoenix_kit_web/components/dashboard/tab_item.ex— 7 sites (5 labels, 2 tooltips)lib/phoenix_kit_web/components/dashboard/sidebar.ex— 5 sites (group label + render, more_menu tab, mobile parent, mobile subtab)lib/phoenix_kit_web/components/dashboard/admin_sidebar.ex— 2 sites (group label guard + render)Documentation
guides/per-module-i18n.mdfor module developers — setup checklist, step-by-stepmix.exs/ backend /.poflow,dynamic_children/2locale handling, dividers and group headers, tooltips, greenfield template, retrofitting checklist, smoke test pattern, common pitfallsmix.exsExDoc extras so it ships to hexdocs.pm under "Guides"guides/README.mdindexHot-reload safety
A parent app whose Phoenix server keeps running across the upgrade hits stale
%Tab{}/%Group{}structs cached inRegistry's ETS — the underlying maps lack the new keys. The localizer usesMap.get/2forgettext_backend/gettext_domainso missing keys are treated as "no backend configured", returning the raw label until the parent restarts and ETS repopulates with the new shape.Tests
test/phoenix_kit/dashboard/andtest/phoenix_kit_web/components/dashboard/cover raw fallback, nil label, translated render underrulocale, helper round-trip, divider/group_header gettext support, hot-reload-safe stale-struct regressionasync: false+on_exitto isolateGettext.put_locale/2process stateNon-goals
@version 1.7.105 → 1.8.0; that was reverted because the package version is set by the maintainer at release time. The fourth commit on this branch makes the revert explicit.gettext_backend: PhoenixKitWeb.Gettextto ~19 registrations acrossadmin_tabs.ex,registry.ex,jobs.ex) and is intentionally left for a follow-up so this PR stays scoped to the API.Rollout plan (after this merges)
phoenix_kit_<x>Hex packageguides/per-module-i18n.md— pilot:phoenix_kit_newsletters(in progress)gettext_backend:on own tab registrationsPhase 2 modules are independent — can be migrated in parallel. Phase 3 is independent of Phase 2.
Test plan
mix format --check-formattedcleanmix compile --warnings-as-errorscleanmix credo --strictclean (7211 mods/funs, 516 files, no issues)mix dialyzercleanmix deps.compile phoenix_kit --force+ full server restart, navigate/admin, confirm sidebar renders withoutFunctionClauseError