Skip to content

fix(dashboard): Localize configurable operation selector buttons#4914

Merged
michaelbromley merged 1 commit into
vendurehq:masterfrom
grolmus:mgrolmus/oss-405-dashboard-i18n-regression-collections-add-collection-filter
Jul 9, 2026
Merged

fix(dashboard): Localize configurable operation selector buttons#4914
michaelbromley merged 1 commit into
vendurehq:masterfrom
grolmus:mgrolmus/oss-405-dashboard-i18n-regression-collections-add-collection-filter

Conversation

@grolmus

@grolmus grolmus commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes a Dashboard i18n regression where the "Add collection filter" button — and the equivalent buttons on promotion conditions/actions and the payment/shipping method selectors — stayed in English after switching the UI language.

Root cause

The two shared selector components rendered the button/empty labels as:

<Trans>{buttonText}</Trans>

buttonText is a runtime string prop. Lingui's <Trans> macro only extracts static literals at compile time, so wrapping a variable produces a placeholder message that is never translated — the raw English prop was rendered regardless of locale.

Change

  • configurable-operation-multi-selector.tsx / configurable-operation-selector.tsx: buttonText / dropdownTitle / emptyText prop types → ReactNode; render {buttonText} directly; default emptyText<Trans>No options found</Trans>.
  • The 7 call sites (collection filters, promotion conditions/actions, payment handler + eligibility checker, shipping calculator + eligibility checker) now pass static <Trans>…</Trans> elements, which extract correctly.
  • Ran lingui extract and translated the 11 new message ids across all 26 locale catalogs.

Test plan

Automated

  • tsc (tsconfig.check.json) passes.
  • lingui extract is idempotent → the check-i18n-sync CI gate stays green.

Manual (screenshots on the Linear issue / attached to this PR)

  • Drove the live dashboard (/collections/new) via the e2e harness and switched UI language:
    • es → button renders "Añadir filtro de colección"
    • de → button renders "Sammlungsfilter hinzufügen"
    • asserted the old English label is absent under es.

Notes / out of scope

  • The dropdown option names and the labels inside an added filter card are server-sourced (ConfigurableOperationDef.description / arg.label) and are not addressed here — that is the separate configurable-operation server-translation gap.
  • product-multi-selector-input.tsx has the same <Trans>{buttonText}> anti-pattern but in a template-literal-with-interpolation form; left as a follow-up.

Fixes #4410

Manual-verification screenshots (en/es/de) are attached on the Linear issue OSS-405.

@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vendure-storybook Ready Ready Preview, Comment Jul 9, 2026 9:37am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ee0d2c09-0851-47be-9408-20e80a553d09

📥 Commits

Reviewing files that changed from the base of the PR and between 574055e and 27ef613.

📒 Files selected for processing (31)
  • packages/dashboard/src/app/routes/_authenticated/_promotions/components/promotion-actions-selector.tsx
  • packages/dashboard/src/app/routes/_authenticated/_promotions/components/promotion-conditions-selector.tsx
  • packages/dashboard/src/i18n/locales/ar.po
  • packages/dashboard/src/i18n/locales/bg.po
  • packages/dashboard/src/i18n/locales/cs.po
  • packages/dashboard/src/i18n/locales/de.po
  • packages/dashboard/src/i18n/locales/en.po
  • packages/dashboard/src/i18n/locales/es.po
  • packages/dashboard/src/i18n/locales/fa.po
  • packages/dashboard/src/i18n/locales/fr.po
  • packages/dashboard/src/i18n/locales/he.po
  • packages/dashboard/src/i18n/locales/hr.po
  • packages/dashboard/src/i18n/locales/hu.po
  • packages/dashboard/src/i18n/locales/it.po
  • packages/dashboard/src/i18n/locales/ja.po
  • packages/dashboard/src/i18n/locales/nb.po
  • packages/dashboard/src/i18n/locales/ne.po
  • packages/dashboard/src/i18n/locales/nl.po
  • packages/dashboard/src/i18n/locales/pl.po
  • packages/dashboard/src/i18n/locales/pt_BR.po
  • packages/dashboard/src/i18n/locales/pt_PT.po
  • packages/dashboard/src/i18n/locales/ro.po
  • packages/dashboard/src/i18n/locales/ru.po
  • packages/dashboard/src/i18n/locales/sv.po
  • packages/dashboard/src/i18n/locales/tr.po
  • packages/dashboard/src/i18n/locales/uk.po
  • packages/dashboard/src/i18n/locales/uz.po
  • packages/dashboard/src/i18n/locales/zh_Hans.po
  • packages/dashboard/src/i18n/locales/zh_Hant.po
  • packages/dashboard/src/lib/components/shared/configurable-operation-multi-selector.tsx
  • packages/dashboard/src/lib/components/shared/configurable-operation-selector.tsx
 ___________________________________________________
< Your TODOs are starting to look like a manifesto. >
 ---------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
📝 Walkthrough

[!WARNING]

Walkthrough skipped

File diffs could not be summarized.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@grolmus

grolmus commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author
01-en-add-collection-filter 02-es-add-collection-filter 03-de-add-collection-filter

@grolmus grolmus requested a review from michaelbromley July 2, 2026 12:41

@biggamesmallworld biggamesmallworld left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed before I spotted that master had already fixed part of this — see my follow-up comment for the full picture. Superseding this review body, and I've deleted the inline suggestions since they assumed the ReactNode shape.

Two points survive regardless of which shape you land:

  • The @example blocks show raw strings (configurable-operation-selector.tsx:69, configurable-operation-multi-selector.tsx:83-84,95). Whatever the final API is, the examples should demonstrate the localized form — they're the copy-paste source for extension authors, and a raw string type-checks while silently never translating.
  • The prop doc comments say "Text to display" (configurable-operation-selector.tsx:32,34, multi-selector :34,36,38). Worth a pass once the prop types settle.

Otherwise: root cause is correctly diagnosed, call sites are all covered, and the catalogs match lingui extract.

@biggamesmallworld

biggamesmallworld commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

(Edited: my original wording claimed master's fix "works by accident" via a failed catalog lookup. That's wrong — corrected below.)

Heads up — dbc31ffb5 (#4074) landed after you branched and fixed the same bug via the t macro, on 5 of your 7 call sites. That's the conflict source. Neither PR referenced the other.

#4410 is genuinely fixed on master. <Trans>{buttonText}</Trans> isn't broken — it extracts to an identity passthrough:

#: src/lib/components/shared/configurable-operation-multi-selector.tsx:265
#: src/lib/components/shared/configurable-operation-selector.tsx:140
msgid "{buttonText}"
msgstr "{buttonText}"

It renders the prop verbatim in every locale. Where the caller passes t`Add collection filter` the string arrives pre-translated, so it works. The wrapper is a no-op, not a bug.

Still worth landing this, for three reasons:

  1. Promotions are actually broken. promotion-conditions-selector.tsx:35-36 and promotion-actions-selector.tsx:35-36 pass raw strings into that no-op and render English forever. None of the 4 msgids exist in en.po.
  2. The wrapper is a decoy. It looks like localization, does nothing, and mints junk msgids. There are four: {buttonText}, {label} (data-table-bulk-action-item), {title} (base-widget), {operator} (human-readable-operator). Both selectors are exported from lib/index.ts, so extension authors will read <Trans> and reasonably assume a plain string gets translated.
  3. emptyText still defaults to an untranslated 'No options found'.

Suggested reshape — rebase, then do only what master didn't:

  • Delete the <Trans> wrapper in both shared components; render {buttonText} directly.
  • Convert the 2 promotions selectors to t, matching master.
  • Translate the emptyText default via useLingui().

That lets you drop the stringReactNode widening. Both macros only extract what's statically visible at the call site, so a prop like buttonText has to be translated by the caller — which makes t the idiomatic choice here, not a workaround. <Trans> earns its keep for static JSX content and rich text, not for a value arriving through a prop. The ReactNode element form would also work, but it widens a public API to buy nothing.

Diff shrinks to ~15 lines, and most .po conflicts evaporate: master already carries the 5 msgids, so you'd only add the 4 promotions ids + No options found.

I've deleted my inline suggestions above — they assumed the ReactNode shape.

The `<Trans>{buttonText}</Trans>` in the two configurable operation
selectors extracted to an identity passthrough (`msgid "{buttonText}"`,
`msgstr "{buttonText}"`), so it rendered the prop verbatim in every
locale. Callers that passed a pre-translated string via the `t` macro
worked; callers that passed a raw string silently shipped English.

The promotion conditions and actions selectors did the latter, so their
"Add condition" / "Add action" buttons and dropdown titles never
translated. `emptyText` had the same problem via its default value.

Remove the no-op `<Trans>` wrapper, translate the promotion call sites
with the `t` macro (matching the other five call sites), and translate
the `emptyText` default at render.

Relates to vendurehq#4410

Co-authored-by: Martin Grolmus <martin@stagro.sk>
@biggamesmallworld biggamesmallworld force-pushed the mgrolmus/oss-405-dashboard-i18n-regression-collections-add-collection-filter branch from 574055e to 27ef613 Compare July 9, 2026 09:36
@biggamesmallworld

Copy link
Copy Markdown
Collaborator

Took you up on the offer — force-pushed a reshaped commit over 574055eea. Your original is preserved as the co-author trailer, and in this PR's force-push history if you want it back.

What changed vs. your version:

  • Dropped the stringReactNode widening. Both selectors are exported from lib/index.ts, and with every caller passing a t string there was nothing to widen for.
  • Kept master's t macro at the 5 call sites it already fixed, so no churn there.
  • Converted the 2 promotions selectors (Add condition / Add action + dropdown titles) — the ones master missed, and the only user-visible bug left.
  • Removed the no-op <Trans>{buttonText}</Trans> from both shared components.
  • emptyText now falls back to t`No options found` at render instead of an untranslated default.
  • Fixed the three @example blocks to show the t form, and the prop docs now say the value must arrive pre-translated.

Result: 4 source files, ~30 lines, +5 msgids across the catalogs. Conflicts resolved. tsc clean, lingui extract idempotent (so check-i18n-sync stays green), lingui compile passes. Verified no existing msgstr was clobbered — the only catalog deletions are the two stale source-refs on {buttonText}.

Retitled in the commit since it's no longer about collection filters — master's dbc31ffb5 already fixed #4410, so this is Relates to rather than Fixes.

Still open as follow-ups, not touched here: product-multi-selector-input.tsx:499 (same anti-pattern, template-literal form — it's why msgid "{buttonText}" survives in the catalog), the {label} / {title} / {operator} equivalents, and the orphaned ko.po.

Thanks for the original diagnosis — the root cause writeup is what made this quick.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@grolmus grolmus left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @biggamesmallworld — this is a clean fix. <Trans>{buttonText}</Trans> was the classic lingui anti-pattern (the macro can't extract a runtime variable, so those button/dropdown strings were never actually translated). Moving the translation to the static call sites with the t macro, rendering the passed string plainly, and translating the internal emptyText fallback via useLingui is exactly the right approach. Good call documenting "must be pre-translated" on the buttonText prop too.

I checked it out and verified:

  • ✅ Ran the exact CI check (scripts/check-i18n-sync.sh) → i18n catalogs are in sync, so lingui extract produces no drift.
  • ✅ No leftover <Trans> references after the import swap in either shared selector — clean.
  • ✅ All configurable-operation selector call sites are localized: the two promotion selectors here, plus the payment/shipping/collection selectors which were already migrated on master — so coverage is complete, not just promotions.
  • dropdownTitle is rendered plainly, consistent with it now being passed pre-translated.

No regression for any caller (the old <Trans>{var}</Trans> didn't translate either), and the anti-pattern is gone repo-wide for these components. LGTM 🚀

@grolmus grolmus requested review from BibiSebi and michaelbromley and removed request for BibiSebi and michaelbromley July 9, 2026 10:28
@michaelbromley michaelbromley merged commit 56a41bf into vendurehq:master Jul 9, 2026
26 of 27 checks passed
@vendure-ci-automation-bot vendure-ci-automation-bot Bot locked and limited conversation to collaborators Jul 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard i18n regression: Collections “Add collection filter” UI remains in English after switching language (new collection page)

3 participants