Skip to content

feat(i18n): wrap all remaining admin UI components with Lingui macros#528

Merged
ascorbic merged 3 commits intomainfrom
i18n/remaining-ui-components
Apr 13, 2026
Merged

feat(i18n): wrap all remaining admin UI components with Lingui macros#528
ascorbic merged 3 commits intomainfrom
i18n/remaining-ui-components

Conversation

@ascorbic
Copy link
Copy Markdown
Collaborator

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:

  • Media: MediaLibrary, MediaDetailPanel, MediaPickerModal
  • Menus: MenuList, MenuEditor
  • Structure: Sections, SectionEditor, SectionPickerModal, Redirects
  • Taxonomies: TaxonomyManager, TaxonomySidebar
  • Content types: ContentTypeList, FieldEditor
  • Plugins: PluginManager, MarketplaceBrowse, MarketplacePluginDetail, ThemeMarketplaceBrowse, ThemeMarketplaceDetail, CapabilityConsentDialog
  • Content management: RevisionHistory, RepeaterField, ConfirmDialog, ContentPickerModal
  • SEO: SeoPanel, SeoImageField, editor/ImageDetailPanel, editor/DocumentOutline
  • Settings: AllowedDomainsSettings, PasskeyItem, SeoSettings, SocialSettings
  • One-time flows: WordPressImport, SetupWizard, SignupPage, DeviceAuthorizePage
  • Auth: PasskeyLogin, PasskeyRegistration

Patterns used:

  • t\...`` tagged template for inline strings
  • plural(count, { one: "# item", other: "# items" }) for count-based strings
  • Each sub-component has its own useLingui() call
  • User-generated content (names, slugs, body text) left unwrapped

Type of change

  • Refactor (no behavior change)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • User-visible strings in the admin UI are wrapped for translation and pnpm locale:extract has been run (if applicable)
  • I have added a changeset (if this PR changes a published package)

AI-generated code disclosure

  • This PR includes AI-generated code

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.
Copilot AI review requested due to automatic review settings April 13, 2026 13:53
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 13, 2026

🦋 Changeset detected

Latest commit: 1079df3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@emdash-cms/admin Patch
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/plugin-embeds Patch

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

@github-actions
Copy link
Copy Markdown
Contributor

Scope check

This 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.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 13, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 1079df3 Apr 13 2026, 02:17 PM

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 13, 2026

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
packages/admin/src/locales/ar/messages.po Localization changed, will be marked as complete.
packages/admin/src/locales/de/messages.po Localization changed, will be marked as complete.
packages/admin/src/locales/en/messages.po Source changed, localizations will be marked as outdated.
packages/admin/src/locales/fr/messages.po Localization changed, will be marked as complete.
packages/admin/src/locales/pseudo/messages.po Localization changed, will be marked as complete.
packages/admin/src/locales/pt-BR/messages.po Localization changed, will be marked as complete.
packages/admin/src/locales/zh-CN/messages.po Localization changed, will be marked as complete.
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 13, 2026

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@528

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@528

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@528

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@528

emdash

npm i https://pkg.pr.new/emdash@528

create-emdash

npm i https://pkg.pr.new/create-emdash@528

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@528

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@528

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@528

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@528

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@528

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@528

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@528

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@528

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@528

commit: 1079df3

@github-actions
Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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/admin documenting 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.

Comment on lines 334 to 337
<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>
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 286 to 289
{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>
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 65 to 69
<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>
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
<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" />
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
<Input label={t`Label`} name="label" required placeholder="Primary Navigation" />
<Input label={t`Label`} name="label" required placeholder={t`Primary Navigation`} />

Copilot uses AI. Check for mistakes.
</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" />
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
<Input label={t`Label`} name="label" required placeholder="Home" />
<Input label={t`Label`} name="label" required placeholder={t`Home`} />

Copilot uses AI. Check for mistakes.
Comment on lines 128 to +131
{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>
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +176 to 178
title={t`Rename`}
aria-label={t`Rename ${passkey.name || "passkey"}`}
>
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +187 to 189
title={t`Remove`}
aria-label={t`Remove ${passkey.name || "passkey"}`}
>
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +203 to +205
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`}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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"
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
placeholder="https://example.com or /about"
placeholder={t`https://example.com or /about`}

Copilot uses AI. Check for mistakes.
- 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
@ascorbic ascorbic merged commit ba0a5af into main Apr 13, 2026
29 checks passed
@ascorbic ascorbic deleted the i18n/remaining-ui-components branch April 13, 2026 14:54
@emdashbot emdashbot bot mentioned this pull request Apr 13, 2026
fmhall pushed a commit to fmhall/emdash that referenced this pull request Apr 13, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants