Skip to content

Add per-module Gettext backend for sidebar tab labels - #5

Merged
ddon merged 1 commit into
BeamLabEU:mainfrom
timujinne:feature/per-module-i18n
May 9, 2026
Merged

Add per-module Gettext backend for sidebar tab labels#5
ddon merged 1 commit into
BeamLabEU:mainfrom
timujinne:feature/per-module-i18n

Conversation

@timujinne

Copy link
Copy Markdown
Contributor

Summary

  • Adds PhoenixKitCRM.Gettext backend (lib/phoenix_kit_crm/gettext.ex) — module owns its own translation catalogue.
  • All 4 Tab.new! registrations in lib/phoenix_kit_crm.ex (3 admin + 1 settings) declare gettext_backend: PhoenixKitCRM.Gettext.
  • Pre-existing gettext() wrappers around Tab labels stripped to plain strings — the wrappers translated at registration time against whatever process locale was active during boot (wrong behaviour). Now Tab.localized_label/1 resolves at request time per the user's locale.
  • All 4 internal-translation files swapped from PhoenixKitWeb.Gettext to PhoenixKitCRM.Gettext: phoenix_kit_crm.ex, web/column_modal.ex, column_config.ex, web/cell_format.ex. Once published, this module no longer depends on the parent app's gettext backend for its own UI strings.
  • Translation catalogues for en, ru, et shipped under priv/gettext/<locale>/LC_MESSAGES/default.po — 19 unique msgids covering Tab labels and the gettext() calls auto-extracted from column_modal.ex / cell_format.ex.

CRM was the most complex module in the per-module-i18n rollout — pre-existing PhoenixKitWeb.Gettext references in 4 files plus gettext() wrappers around static Tab labels. Both concerns are addressed in this PR.

Behaviour matrix

phoenix_kit resolution Sidebar tab labels Internal CRM strings i18n tests
Currently published (≤ 1.7.105) raw English (graceful — gettext_backend: field silently dropped by Tab.new) translated via PhoenixKitCRM.Gettext (locale plug already in effect) excluded
Future release that ships PR #522 API translated per user locale translated per user locale run automatically

Files (14)

M  CHANGELOG.md                                # +0.2.2 entry
M  mix.exs                                     # +:gettext app/dep, +priv in package files, @version 0.2.2
M  mix.lock
A  lib/phoenix_kit_crm/gettext.ex              # Gettext.Backend
M  lib/phoenix_kit_crm.ex                      # Tab labels stripped + gettext_backend on every Tab.new! (4 sites); host-app use Gettext removed
M  lib/phoenix_kit_crm/column_config.ex        # PhoenixKitWeb.Gettext → PhoenixKitCRM.Gettext (incl. explicit Gettext.gettext call)
M  lib/phoenix_kit_crm/web/column_modal.ex     # PhoenixKitWeb.Gettext → PhoenixKitCRM.Gettext
M  lib/phoenix_kit_crm/web/cell_format.ex      # PhoenixKitWeb.Gettext → PhoenixKitCRM.Gettext
A  priv/gettext/default.pot                    # 19 msgids (3 Tab + 16 from column_modal/cell_format)
A  priv/gettext/en/LC_MESSAGES/default.po      # en (1:1)
A  priv/gettext/ru/LC_MESSAGES/default.po      # ru (full)
A  priv/gettext/et/LC_MESSAGES/default.po      # et (Tab labels translated; column_modal/cell_format strings fall back to English msgid)
A  test/phoenix_kit_crm/i18n_test.exs          # 4 smoke assertions
M  test/test_helper.exs                        # conditional :requires_phoenix_kit_i18n_api skip

Translations (Tab labels — full set)

msgid ru et
CRM CRM CRM
Overview Обзор Ülevaade
Organizations Организации Organisatsioonid

CRM stays as "CRM" in all locales — industry acronym, not translated. Plus 16 additional msgids from gettext() calls in column_modal.ex and cell_format.ex (e.g. "Apply", "Cancel", "Customize columns", "Yes", "No") — fully translated to Russian; Estonian translations for those left as fallback-to-msgid where idiomatic translation was non-obvious.

Deliberately untouched

  • lib/phoenix_kit_crm/web/settings_live.ex uses use PhoenixKitWeb, :live_view (host app's web module). The host web module injects Gettext, router helpers, and other compile-time macros at the host-app level; the package cannot and must not override that with its own backend. Migrating this file would break runtime translations for host apps that haven't configured PhoenixKitCRM.Gettext. Left as-is — this is intentional, not an oversight.
  • lib/phoenix_kit_crm/sidebar_bootstrap.ex builds %Tab{label: role.name} (a runtime user-supplied string) for dynamic role tabs. Adding gettext_backend: here would be meaningless — there's no static msgid to look up; the role name is whatever the operator named it. Left as-is — Tab.localized_label/1 correctly returns the raw label when no backend is set, so dynamic labels render verbatim.

Test plan

  • mix test with phoenix_kit resolved to PR #522 branch via path: override — i18n tests + module's existing tests, all pass.
  • mix test with phoenix_kit resolved to currently published 1.7.105 — i18n tests excluded automatically; other tests unaffected.
  • mix.exs package files: includes priv.po files will ship to Hex consumers.
  • Tab labels are now plain strings (no gettext() wrappers); gettext_backend: field set on each.
  • grep -rn "use Gettext, backend: PhoenixKitWeb.Gettext" lib/ returns zero in committed form.
  • CI re-verification on this PR.
  • Maintainer review.

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
@ddon
ddon merged commit 0233209 into BeamLabEU:main May 9, 2026
1 check passed
ddon added a commit that referenced this pull request May 9, 2026
Retrospective review of the per-module-i18n PR using `elixir:phoenix-thinking`
+ `elixir:elixir-thinking` skills. Verdict: approve as merged with one real
translation gap to clean up in a follow-up.

Issues raised:
1. ColumnConfig standard column labels (Email, Username, Full Name, …) are
   translated at runtime via `Gettext.gettext/2` but are not in default.pot
   — `mix gettext.extract` can't see module-attribute literals. Real bug:
   role/Organizations table headers stay English in ru/et.
2. i18n_test.exs `gettext_domain == "default"` asserts Core defaults; could
   use `Gettext.with_locale/3` scoping for `async: true`.
3. Long-form `Gettext.gettext/2` in column_config.ex deserves a comment
   explaining why the macro can't be used.
4. Drive-by `decimal` 2 → 3 transitive bump unannounced in PR description.
5. `then(fn e -> ... end)` chain in test_helper.exs is verbose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ddon added a commit that referenced this pull request May 9, 2026
Folded into the unreleased 0.2.2 (PR #5 set the version but never published
to Hex), so no version bump is needed.

Fixes from the review:

- Issue 1 (real): adds nine column-label msgids — Contact, Email, Full Name,
  Last Confirmed, Location, Organization, Registered, Status, Username — to
  default.pot under a new "Column labels" manually-maintained section, with
  full en/ru/et translations. These come from `@role_standard` and
  `@organizations_standard` in column_config.ex and are translated at runtime
  via the long-form `Gettext.gettext/2`. Without these msgids, role and
  Organizations table headers rendered raw English in ru/et regardless of
  locale. Pot header comment updated to document both manually-maintained
  groups (Tab labels + column labels).
- Issue 3: short comment above `translate_labels/1` explaining why the
  macro form can't be used here and where the msgids live.
- Issue 2: i18n_test.exs switched to `async: true`; `setup/on_exit` +
  `Gettext.put_locale/2` replaced with scoped `Gettext.with_locale/3`;
  tautological `gettext_domain == "default"` assertion dropped.
- Issue 5: flattened the `then(fn e -> ... end)` chain in test_helper.exs
  to a list + `Enum.reject(&is_nil/1)`.

CHANGELOG [0.2.2] entry extended with the column-translation coverage; date
updated to today (2026-05-09).

`mix compile --warnings-as-errors`, `mix format --check-formatted`, and
`mix credo --strict` all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@timujinne
timujinne deleted the feature/per-module-i18n branch August 6, 2026 09:53
Sign up for free to 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