Skip to content

Add gettext support for Dashboard sidebar labels - #522

Merged
ddon merged 8 commits into
BeamLabEU:devfrom
timujinne:feature/dashboard-i18n
May 8, 2026
Merged

Add gettext support for Dashboard sidebar labels#522
ddon merged 8 commits into
BeamLabEU:devfrom
timujinne:feature/dashboard-i18n

Conversation

@timujinne

@timujinnetimujinne commented May 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds optional gettext_backend and gettext_domain fields to PhoenixKit.Dashboard.Tab and PhoenixKit.Dashboard.Group, plus three resolver functions (Tab.localized_label/1, Tab.localized_tooltip/1, Group.localized_label/1).
  • Sidebar / AdminSidebar / TabItem components route every label and tooltip render through the localizer (14 sites total) so per-module Gettext backends translate at request time.
  • Backwards compatible — tabs without a backend continue rendering raw labels; existing call sites keep their behavior.
  • Hot-reload safe — the localizer reads the new fields via Map.get/2, so old struct shapes cached in ETS before the upgrade fall back to raw labels rather than raising FunctionClauseError.

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 (defaults nil, "default")
  • Group.gettext_backend, Group.gettext_domain (same defaults)
  • Tab.localized_label/1, Tab.localized_tooltip/1, Group.localized_label/1
  • Tab.new/1, Tab.divider/1, Tab.group_header/1, both Group.new/1 clauses accept the new fields

Render 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

  • New guide guides/per-module-i18n.md for module developers — setup checklist, step-by-step mix.exs / backend / .po flow, dynamic_children/2 locale handling, dividers and group headers, tooltips, greenfield template, retrofitting checklist, smoke test pattern, common pitfalls
  • Wired into mix.exs ExDoc extras so it ships to hexdocs.pm under "Guides"
  • Linked from guides/README.md index
  • Version-neutral phrasing — defers to the PhoenixKit CHANGELOG for the minimum version once shipped

Hot-reload safety

A parent app whose Phoenix server keeps running across the upgrade hits stale %Tab{} / %Group{} structs cached in Registry's ETS — the underlying maps lack the new keys. The localizer uses Map.get/2 for gettext_backend / gettext_domain so missing keys are treated as "no backend configured", returning the raw label until the parent restarts and ETS repopulates with the new shape.

Tests

  • 43 unit + render tests in test/phoenix_kit/dashboard/ and test/phoenix_kit_web/components/dashboard/ cover raw fallback, nil label, translated render under ru locale, helper round-trip, divider/group_header gettext support, hot-reload-safe stale-struct regression
  • async: false + on_exit to isolate Gettext.put_locale/2 process state

Non-goals

  • No version bump. A first revision of this branch bumped @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.
  • No CHANGELOG entry. Maintainer-owned per project conventions. The four commit messages on this branch contain the material for the entry.
  • PhoenixKit's own admin/user tabs are NOT migrated in this PR. Their labels still render raw English. Migrating them is straightforward (add gettext_backend: PhoenixKitWeb.Gettext to ~19 registrations across admin_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)

PhaseRepoWhat
2each phoenix_kit_<x> Hex packageapply guides/per-module-i18n.md — pilot: phoenix_kit_newsletters (in progress)
3parent appsdrop ETS-patching hacks, set gettext_backend: on own tab registrations

Phase 2 modules are independent — can be migrated in parallel. Phase 3 is independent of Phase 2.

Test plan

  • mix format --check-formatted clean
  • mix compile --warnings-as-errors clean
  • mix credo --strict clean (7211 mods/funs, 516 files, no issues)
  • mix dialyzer clean
  • 43 dashboard tests pass
  • CI re-verifies on the PR branch
  • Maintainer review of API shape and docs
  • Manual verification in a parent app: mix deps.compile phoenix_kit --force + full server restart, navigate /admin, confirm sidebar renders without FunctionClauseError

- 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
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
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.
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
ddon merged commit cf23cf6 into BeamLabEU:devMay 8, 2026
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).
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
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).
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.md
@timujinne
timujinne deleted the feature/dashboard-i18n branch June 25, 2026 08:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@timujinne@ddon