feat(i18n): wrap all remaining admin UI components with Lingui macros#528
feat(i18n): wrap all remaining admin UI components with Lingui macros#528
Conversation
Covers: Media (Library, DetailPanel, PickerModal), Menus, Sections, Redirects, TaxonomyManager, TaxonomySidebar, SectionPickerModal, ContentTypeList, FieldEditor, PluginManager, Marketplace, Themes, CapabilityConsentDialog, RevisionHistory, RepeaterField, ConfirmDialog, ContentPickerModal, SeoPanel, SeoImageField, ImageDetailPanel, DocumentOutline, WordPressImport, SetupWizard, SignupPage, DeviceAuthorizePage, PasskeyLogin, PasskeyRegistration, and all settings sub-pages. Catalog grows from 296 to 1216 message IDs (+920). All components now use t`...` tagged template macros with plural() for count-based strings. Sub-components each have their own useLingui() call.
🦋 Changeset detectedLatest commit: 1079df3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Scope checkThis PR changes 36,317 lines across 52 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | 1079df3 | Apr 13 2026, 02:17 PM |
Lunaria Status Overview🌕 This pull request will trigger status changes. Learn moreBy default, every PR changing files present in the Lunaria configuration's You can change this by adding one of the keywords present in the Tracked Files
Warnings reference
|
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/blocks
@emdash-cms/cloudflare
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
There was a problem hiding this comment.
Pull request overview
This PR completes Lingui i18n coverage for the remaining Admin UI components by wrapping user-visible strings with t\...`(andplural()` where applicable), ensuring they are extracted into locale catalogs and translated at runtime.
Changes:
- Added
useLingui()usage across remaining Admin components and wrapped UI strings with Lingui macros. - Introduced
plural()in several places for count-based strings and adjusted various aria-labels/placeholders. - Added a changeset for
@emdash-cms/admindocumenting the i18n completion and catalog growth.
Reviewed changes
Copilot reviewed 45 out of 52 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/admin/src/components/users/UserDetail.tsx | Wrapes user detail panel labels/status text with t. |
| packages/admin/src/components/users/InviteUserModal.tsx | Wraps invite modal strings, aria labels, and helper text with t. |
| packages/admin/src/components/ThemeMarketplaceDetail.tsx | Wraps theme detail UI strings (errors, buttons, headings, lightbox labels). |
| packages/admin/src/components/ThemeMarketplaceBrowse.tsx | Wraps theme browse UI strings and theme card labels. |
| packages/admin/src/components/TaxonomySidebar.tsx | Wraps taxonomy sidebar strings and aria-labels; minor variable renames. |
| packages/admin/src/components/SignupPage.tsx | Wraps signup flow step copy, validation errors, and buttons with t. |
| packages/admin/src/components/SetupWizard.tsx | Wraps setup wizard text and adds plural() for collection counts. |
| packages/admin/src/components/settings/SocialSettings.tsx | Wraps Social settings page UI strings and save status messages. |
| packages/admin/src/components/settings/SeoSettings.tsx | Wraps SEO settings page UI strings and save status messages. |
| packages/admin/src/components/settings/SecuritySettings.tsx | Wraps passkey/security settings UI strings and status messages. |
| packages/admin/src/components/settings/PasskeyItem.tsx | Wraps passkey list item UI strings, aria-labels, and confirm dialog text. |
| packages/admin/src/components/settings/GeneralSettings.tsx | Wraps general settings page UI strings, including media picker titles. |
| packages/admin/src/components/settings/EmailSettings.tsx | Wraps email settings UI strings, pipeline status text, and error fallbacks. |
| packages/admin/src/components/settings/AllowedDomainsSettings.tsx | Wraps allowed domains settings UI strings, dialog copy, and aria-labels. |
| packages/admin/src/components/SeoPanel.tsx | Wraps compact SEO panel labels/descriptions; adds character count string via t. |
| packages/admin/src/components/SeoImageField.tsx | Wraps OG image field labels/buttons/aria-labels and picker title. |
| packages/admin/src/components/Sections.tsx | Wraps sections library UI strings, dialog copy, select options, and toasts. |
| packages/admin/src/components/SectionPickerModal.tsx | Wraps section picker modal strings (title, search, empty/loading, buttons). |
| packages/admin/src/components/SectionEditor.tsx | Wraps section editor UI strings, toasts, and empty/error states. |
| packages/admin/src/components/RevisionHistory.tsx | Wraps revision history UI strings, dialog labels, and diff view text. |
| packages/admin/src/components/RepeaterField.tsx | Wraps repeater UI strings and per-item labels/aria-labels. |
| packages/admin/src/components/Redirects.tsx | Wraps redirects UI strings; adds plural() for redirect-loop warning. |
| packages/admin/src/components/MenuList.tsx | Wraps menu list UI strings, dialogs, toasts, and confirm dialog copy. |
| packages/admin/src/components/MenuEditor.tsx | Wraps menu editor UI strings, dialogs, toasts, and control aria-labels. |
| packages/admin/src/components/MediaPickerModal.tsx | Wraps picker modal strings, URL validation errors, and count labels via plural(). |
| packages/admin/src/components/MediaLibrary.tsx | Wraps media library UI strings; uses plural() for upload summaries. |
| packages/admin/src/components/MediaDetailPanel.tsx | Wraps media detail panel strings, field labels, and confirm dialog copy. |
| packages/admin/src/components/MarketplacePluginDetail.tsx | Wraps plugin detail UI strings, screenshot lightbox labels, and metadata copy. |
| packages/admin/src/components/MarketplaceBrowse.tsx | Wraps marketplace browse UI strings and audit badge labels/tooltips. |
| packages/admin/src/components/editor/ImageDetailPanel.tsx | Wraps image settings panel labels, descriptions, and confirm dialog strings. |
| packages/admin/src/components/editor/DocumentOutline.tsx | Wraps outline UI strings and empty-state messaging. |
| packages/admin/src/components/DeviceAuthorizePage.tsx | Wraps device authorization flow UI strings and status copy. |
| packages/admin/src/components/ContentTypeList.tsx | Wraps content type list UI strings and orphaned table warning text. |
| packages/admin/src/components/ContentPickerModal.tsx | Wraps content picker modal strings, status labels, and loading copy. |
| packages/admin/src/components/ConfirmDialog.tsx | Wraps the default Cancel button label with t. |
| packages/admin/src/components/comments/CommentInbox.tsx | Wraps comment inbox UI strings; adds plural() for counts/selected text. |
| packages/admin/src/components/comments/CommentDetail.tsx | Wraps comment detail panel strings and status badge labels. |
| packages/admin/src/components/CapabilityConsentDialog.tsx | Wraps capability consent dialog strings, headings, and actions. |
| packages/admin/src/components/auth/PasskeyRegistration.tsx | Wraps passkey registration flow messages; makes default button text translatable. |
| packages/admin/src/components/auth/PasskeyLogin.tsx | Wraps passkey login flow messages; makes default button text translatable. |
| .changeset/lucky-hats-sing.md | Adds a patch changeset documenting completed Admin i18n coverage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <div className="flex items-center justify-between"> | ||
| <div className="text-xs font-medium text-kumo-subtle"> | ||
| {changedCount} change{changedCount === 1 ? "" : "s"} from next revision | ||
| {t`${changedCount} change${changedCount === 1 ? "" : "s"} from next revision`} | ||
| </div> |
There was a problem hiding this comment.
Pluralization is currently handled by appending an English "s" inside the t template. This won’t translate correctly for many locales. Prefer plural(changedCount, { one: "# change …", other: "# changes …" }) (or equivalent) so translators can supply correct plural forms.
| {plugin.capabilities.length > 0 && ( | ||
| <span className="text-xs text-kumo-subtle"> | ||
| {plugin.capabilities.length} permission{plugin.capabilities.length !== 1 ? "s" : ""} | ||
| {t`${plugin.capabilities.length} permission${plugin.capabilities.length !== 1 ? "s" : ""}`} | ||
| </span> |
There was a problem hiding this comment.
Pluralization is implemented by conditionally appending an English "s" ("permission" vs "permissions") inside a t template. This won’t work for many locales. Use Lingui’s plural() macro for the whole phrase so translators get correct plural forms.
| <div> | ||
| <code className="text-sm font-medium">{orphan.slug}</code> | ||
| <span className="text-xs text-kumo-subtle ml-2"> | ||
| ({orphan.rowCount} items) | ||
| ({orphan.rowCount} {t`items`}) | ||
| </span> |
There was a problem hiding this comment.
The orphaned table row count uses ({orphan.rowCount} {titems}), translating only the noun. This won’t localize plural rules or word order properly. Prefer plural(orphan.rowCount, { one: "(# item)", other: "(# items)" }) (or similar) for the whole count phrase.
| <div> | ||
| <Input label="Label" name="label" required placeholder="Primary Navigation" /> | ||
| <p className="text-sm text-kumo-subtle mt-1">Display name for admin interface</p> | ||
| <Input label={t`Label`} name="label" required placeholder="Primary Navigation" /> |
There was a problem hiding this comment.
The Label input’s placeholder ("Primary Navigation") is user-visible but is still a raw string, so it won’t be extracted/translated. Wrap it with t (or provide a translated default) to keep i18n coverage consistent with the rest of the dialog.
| <Input label={t`Label`} name="label" required placeholder="Primary Navigation" /> | |
| <Input label={t`Label`} name="label" required placeholder={t`Primary Navigation`} /> |
| </div> | ||
| <form onSubmit={handleAddCustomLink} className="space-y-4"> | ||
| <Input label="Label" name="label" required placeholder="Home" /> | ||
| <Input label={t`Label`} name="label" required placeholder="Home" /> |
There was a problem hiding this comment.
The Label field placeholder ("Home") is user-visible but still a raw string, so it won’t be extracted/translated. Wrap the placeholder with t (or replace with translated helper text).
| <Input label={t`Label`} name="label" required placeholder="Home" /> | |
| <Input label={t`Label`} name="label" required placeholder={t`Home`} /> |
| {items.length > 0 && ( | ||
| <span className="ml-2 text-kumo-subtle font-normal">({items.length} items)</span> | ||
| <span className="ml-2 text-kumo-subtle font-normal"> | ||
| ({items.length} {t`items`}) | ||
| </span> |
There was a problem hiding this comment.
The item count label translates only the word "items" (({items.length} {titems})). This won’t handle plural rules or languages that need different word order. Prefer plural(items.length, { one: "# item", other: "# items" }) (or similar) for the whole count phrase.
| title={t`Rename`} | ||
| aria-label={t`Rename ${passkey.name || "passkey"}`} | ||
| > |
There was a problem hiding this comment.
aria-label={tRename ${passkey.name || "passkey"}} uses a hard-coded English fallback ("passkey") inside the interpolation, which won’t be extracted/translated. Prefer a conditional message (named vs unnamed) or translate the fallback separately so it’s localizable.
| title={t`Remove`} | ||
| aria-label={t`Remove ${passkey.name || "passkey"}`} | ||
| > |
There was a problem hiding this comment.
aria-label={tRemove ${passkey.name || "passkey"}} uses a hard-coded English fallback ("passkey") inside the interpolation, so that fallback won’t be translated. Consider conditional messages (e.g., Remove passkey vs Remove ${name}) instead.
| title={t`Remove passkey?`} | ||
| description={t`You won't be able to use "${passkey.name || "this passkey"}" to sign in anymore. This action cannot be undone.`} | ||
| confirmLabel={t`Remove`} |
There was a problem hiding this comment.
The confirmation description interpolates the English fallback "this passkey" inside the template (${passkey.name || "this passkey"}), which won’t be extracted/translated. Use a conditional description (named vs unnamed) or translate the fallback separately.
| pattern="(https?://.+|/.*)" | ||
| title="Enter a URL (https://…) or a relative path (/…)" | ||
| title={t`Enter a URL (https://…) or a relative path (/…)`} | ||
| placeholder="https://example.com or /about" |
There was a problem hiding this comment.
The URL field placeholder ("https://example.com or /about") is user-visible but remains unwrapped, so it won’t be extracted/translated. Consider wrapping the placeholder with t (or using translated helper text instead of an English example).
| placeholder="https://example.com or /about" | |
| placeholder={t`https://example.com or /about`} |
- Use plural() for changedCount in RevisionHistory; unify Show/Hide unchanged label as a single plural phrase instead of 3 fragments - Use plural() for capability count in MarketplaceBrowse - Use plural() for orphan row count in ContentTypeList - Use plural() for item count in RepeaterField - Wrap placeholder strings in MenuList and MenuEditor - Resolve MediaPickerModal title default inside component (not param default) - Use conditional messages in PasskeyItem for named vs unnamed passkey so the 'passkey' fallback string is translatable
…emdash-cms#528) * feat(i18n): wrap all remaining admin UI components with Lingui macros Covers: Media (Library, DetailPanel, PickerModal), Menus, Sections, Redirects, TaxonomyManager, TaxonomySidebar, SectionPickerModal, ContentTypeList, FieldEditor, PluginManager, Marketplace, Themes, CapabilityConsentDialog, RevisionHistory, RepeaterField, ConfirmDialog, ContentPickerModal, SeoPanel, SeoImageField, ImageDetailPanel, DocumentOutline, WordPressImport, SetupWizard, SignupPage, DeviceAuthorizePage, PasskeyLogin, PasskeyRegistration, and all settings sub-pages. Catalog grows from 296 to 1216 message IDs (+920). All components now use t`...` tagged template macros with plural() for count-based strings. Sub-components each have their own useLingui() call. * chore: add changeset for i18n PR 2 * fix(i18n): address Copilot review feedback on PR 528 - Use plural() for changedCount in RevisionHistory; unify Show/Hide unchanged label as a single plural phrase instead of 3 fragments - Use plural() for capability count in MarketplaceBrowse - Use plural() for orphan row count in ContentTypeList - Use plural() for item count in RepeaterField - Wrap placeholder strings in MenuList and MenuEditor - Resolve MediaPickerModal title default inside component (not param default) - Use conditional messages in PasskeyItem for named vs unnamed passkey so the 'passkey' fallback string is translatable
What does this PR do?
Completes i18n coverage of the admin UI. Every user-visible string in every admin component is now wrapped with Lingui's
t\...`` macro and will appear translated when a locale catalog covers it.This is part 2 of the i18n rollout (part 1 was #521).
Catalog growth: 296 → 1,216 message IDs (+920)
Components covered:
Patterns used:
t\...`` tagged template for inline stringsplural(count, { one: "# item", other: "# items" })for count-based stringsuseLingui()callType of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runpnpm locale:extracthas been run (if applicable)AI-generated code disclosure