feat(admin): add Lingui i18n infrastructure#234
Conversation
|
b4b7a60 to
ffa976f
Compare
There was a problem hiding this comment.
Pull request overview
Adds Lingui-based internationalization plumbing to the EmDash admin UI, including server-side locale resolution and client-side catalog activation, while wrapping existing admin strings with Lingui macros for future translation support.
Changes:
- Introduces Lingui configuration, dependencies, and an admin locale API (
resolveLocale,useLocale, supported locales config). - Wires Lingui into the admin shell (Astro route → plugin registry wrapper →
AdminAppwithI18nProvider). - Wraps/admin UI strings across routes and components using
t,<Trans>, and<Plural>macros.
Reviewed changes
Copilot reviewed 82 out of 84 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| skills/adding-admin-locale/SKILL.md | Adds internal documentation for adding locales and using Lingui macros. |
| pnpm-workspace.yaml | Adds Lingui packages to the pnpm catalog. |
| packages/core/src/astro/routes/PluginRegistry.tsx | Passes locale/messages props through to AdminApp. |
| packages/core/src/astro/routes/admin.astro | Resolves locale server-side and imports the locale catalog for the admin shell. |
| packages/admin/tsdown.config.ts | Expands build entrypoints to include locales index. |
| packages/admin/tsconfig.json | Adds vite/client types for import.meta.env typing. |
| packages/admin/src/routes/users.tsx | Wraps user management route strings with Lingui macros. |
| packages/admin/src/routes/bylines.tsx | Wraps bylines route strings and labels with Lingui macros. |
| packages/admin/src/locales/useLocale.ts | Adds a hook for client-side locale switching + cookie persistence + dynamic catalog import. |
| packages/admin/src/locales/index.ts | Adds locale-related barrel exports. |
| packages/admin/src/locales/config.ts | Defines supported locales and server-side locale resolution from cookie/Accept-Language. |
| packages/admin/src/index.ts | Re-exports locale APIs from the package root. |
| packages/admin/src/components/WelcomeModal.tsx | Wraps welcome modal strings and role labels with Lingui macros. |
| packages/admin/src/components/users/UserList.tsx | Wraps user list UI strings with Lingui macros. |
| packages/admin/src/components/users/UserDetail.tsx | Wraps user detail panel strings with Lingui macros. |
| packages/admin/src/components/users/InviteUserModal.tsx | Wraps invite modal strings with Lingui macros. |
| packages/admin/src/components/ThemeToggle.tsx | Wraps theme toggle labels/tooltips with Lingui macros. |
| packages/admin/src/components/ThemeMarketplaceDetail.tsx | Wraps theme marketplace detail strings with Lingui macros. |
| packages/admin/src/components/ThemeMarketplaceBrowse.tsx | Wraps theme marketplace browse strings with Lingui macros. |
| packages/admin/src/components/TaxonomySidebar.tsx | Wraps taxonomy sidebar strings and aria-labels with Lingui macros. |
| packages/admin/src/components/Sidebar.tsx | Wraps sidebar navigation group/item labels with Lingui macros. |
| packages/admin/src/components/SetupWizard.tsx | Wraps setup wizard strings and introduces pluralization with Lingui macros. |
| packages/admin/src/components/settings/SocialSettings.tsx | Wraps social settings strings with Lingui macros. |
| packages/admin/src/components/settings/SeoSettings.tsx | Wraps SEO settings strings with Lingui macros. |
| packages/admin/src/components/settings/SecuritySettings.tsx | Wraps security settings strings with Lingui macros. |
| packages/admin/src/components/settings/PasskeyItem.tsx | Wraps passkey item strings with Lingui macros. |
| packages/admin/src/components/settings/GeneralSettings.tsx | Wraps general settings strings with Lingui macros. |
| packages/admin/src/components/settings/EmailSettings.tsx | Wraps email settings strings with Lingui macros. |
| packages/admin/src/components/settings/ApiTokenSettings.tsx | Wraps API token settings strings with Lingui macros. |
| packages/admin/src/components/Settings.tsx | Adds admin-language selector UI (gated) and wraps strings with Lingui macros. |
| packages/admin/src/components/SeoPanel.tsx | Wraps SEO panel labels/descriptions with Lingui macros. |
| packages/admin/src/components/Sections.tsx | Wraps sections UI strings with Lingui macros. |
| packages/admin/src/components/SectionPickerModal.tsx | Wraps section picker modal strings with Lingui macros. |
| packages/admin/src/components/SectionEditor.tsx | Wraps section editor strings with Lingui macros. |
| packages/admin/src/components/SaveButton.tsx | Wraps save button labels with Lingui macros. |
| packages/admin/src/components/SandboxedPluginWidget.tsx | Wraps sandboxed widget empty/error strings with Lingui macros. |
| packages/admin/src/components/SandboxedPluginPage.tsx | Wraps sandboxed page error heading/message with Lingui macros. |
| packages/admin/src/components/RevisionHistory.tsx | Wraps revision history UI strings (incl. conditional plural forms) with Lingui macros. |
| packages/admin/src/components/PluginFieldErrorBoundary.tsx | Wraps plugin widget error fallback strings with Lingui macros. |
| packages/admin/src/components/MenuList.tsx | Wraps menu list UI strings with Lingui macros. |
| packages/admin/src/components/MediaPickerModal.tsx | Wraps media picker strings and pluralizes item count via Lingui macros. |
| packages/admin/src/components/MediaLibrary.tsx | Wraps media library strings and introduces Lingui plural helpers for upload messaging. |
| packages/admin/src/components/MediaDetailPanel.tsx | Wraps media detail panel labels/buttons with Lingui macros. |
| packages/admin/src/components/MarketplaceBrowse.tsx | Wraps plugin marketplace browse strings and plural forms with Lingui macros. |
| packages/admin/src/components/LoginPage.tsx | Wraps login strings with Lingui macros and adds (gated) locale selector. |
| packages/admin/src/components/LocaleSwitcher.tsx | Wraps locale switcher labels/tooltips with Lingui macros. |
| packages/admin/src/components/Header.tsx | Wraps header menu items with Lingui macros. |
| packages/admin/src/components/editor/PluginBlockNode.tsx | Wraps editor plugin block node labels/tooltips with Lingui macros. |
| packages/admin/src/components/editor/ImageNode.tsx | Wraps image node editor labels/tooltips with Lingui macros. |
| packages/admin/src/components/editor/DocumentOutline.tsx | Wraps document outline strings with Lingui macros. |
| packages/admin/src/components/editor/BlockMenu.tsx | Wraps block menu strings with Lingui macros and refactors transform labels. |
| packages/admin/src/components/DeviceAuthorizePage.tsx | Wraps device authorization strings with Lingui macros. |
| packages/admin/src/components/Dashboard.tsx | Wraps dashboard UI strings and count labels with Lingui macros. |
| packages/admin/src/components/ContentTypeList.tsx | Wraps content type list strings with Lingui macros. |
| packages/admin/src/components/ContentPickerModal.tsx | Wraps content picker modal strings with Lingui macros. |
| packages/admin/src/components/ConfirmDialog.tsx | Wraps confirm dialog cancel label with Lingui macros. |
| packages/admin/src/components/comments/CommentDetail.tsx | Wraps comment detail panel strings with Lingui macros. |
| packages/admin/src/components/BlockKitFieldWidget.tsx | Wraps block-kit widget fallback/select labels with Lingui macros. |
| packages/admin/src/components/auth/PasskeyRegistration.tsx | Wraps passkey registration strings with Lingui macros and adds default button text handling. |
| packages/admin/src/components/auth/PasskeyLogin.tsx | Wraps passkey login strings with Lingui macros and adds default button text handling. |
| packages/admin/src/components/AdminCommandPalette.tsx | Wraps command palette strings and navigation labels with Lingui macros. |
| packages/admin/src/App.tsx | Adds I18nProvider and activates Lingui with server-provided locale/messages. |
| packages/admin/package.json | Adds locale exports and Lingui scripts/dependencies. |
| lingui.config.ts | Adds Lingui extraction configuration (English-only catalog). |
| demos/simple/package.json | Adds Lingui tooling dependencies for the demo app. |
| demos/simple/astro.config.mjs | Configures Babel macro plugin + Vite Lingui plugin in the demo Astro pipeline. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)
packages/admin/tsdown.config.ts:8
- The published admin package won’t be able to load locale catalogs:
useLocale()dynamically imports./${code}/messages.porelative to the builtdist/locales/*files, but the build config only emits JS/DTs and doesn’t copy.pocatalogs intodist. This will make locale switching (and any runtime/SSR catalog import that targetsdist) fail in consumer projects. Consider copyingsrc/locales/**/messages.pointodist/locales/**/messages.poas part of the build (or changing the runtime import strategy) so the catalogs exist alongside the compiled locale modules.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/admin/package.json
Outdated
| "./locales/*": "./src/locales/*" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsdown && npx @tailwindcss/cli -i src/styles.css -o dist/styles.css --minify", |
There was a problem hiding this comment.
exports["./locales/*"] points at ./src/locales/*, but this package’s files list only publishes dist, so consumers won’t get src/locales/** (including messages.po). Additionally, the runtime locale loader in dist/locales/useLocale.js uses relative imports, so catalogs need to be present in dist anyway. Fix by either (a) publishing the .po catalogs (and any needed source) via files, or preferably (b) copying catalogs into dist/locales/** and exporting ./locales/* from dist.
| "./locales/*": "./src/locales/*" | |
| }, | |
| "scripts": { | |
| "build": "tsdown && npx @tailwindcss/cli -i src/styles.css -o dist/styles.css --minify", | |
| "./locales/*": "./dist/locales/*" | |
| }, | |
| "scripts": { | |
| "build": "tsdown && npx @tailwindcss/cli -i src/styles.css -o dist/styles.css --minify && node -e \"const fs=require('fs'); fs.mkdirSync('dist/locales', { recursive: true }); fs.cpSync('src/locales', 'dist/locales', { recursive: true });\"", |
| import { resolveLocale } from "@emdash-cms/admin/locales"; | ||
| const resolvedLocale = resolveLocale(Astro.request); | ||
| const { messages } = await import(`@emdash-cms/admin/locales/${resolvedLocale}/messages.po`); |
There was a problem hiding this comment.
admin.astro now imports .po catalogs directly. That requires @lingui/vite-plugin to be present in the host app’s Vite pipeline for both dev and production builds; otherwise the .po import won’t be understood and the admin route build will fail. Since the EmDash integration’s Vite config currently only adds the virtual-modules plugin, consider either adding the Lingui Vite plugin at the integration level or switching to importing precompiled JS catalogs (so host apps don’t need extra Vite config).
There was a problem hiding this comment.
@ascorbic
The Lingui Vite plugin is currently configured in demos/simple only. For other demos, templates, and published sites, admin.astro's .po import will fail without it. We could inject it from the EmDash integration in astro:config:setup, but lingui() eagerly searches for a lingui.config.ts.
Which direction do you see this resolved? We can ship a default config from core, or resolve the catalog paths programmatically, unless you have other idea in mind?
There was a problem hiding this comment.
Is there any downside to injecting it in the integration? That's generally the approach we'd take
There was a problem hiding this comment.
When someone installs emdash from npm and creates a site, they won't have a lingui.config.ts in their project. The integration would call lingui(), which calls getConfig(), which searches up from cwd and finds nothing.
In the monorepo (all demos, templates, CI), the root lingui.config.ts is always reachable. So it's only a problem for published consumers.
There was a problem hiding this comment.
Will this then cause it to fail? I wonder if we can conditionally add the integration if the config exists, or add the config ourselves. We could at least add it to the default templates. Maybe it should be a config option for the integration.
There was a problem hiding this comment.
Here's a working implementation: ophirbucai/emdash@feat/admin-i18n-lingui...spike/lingui-integration-injection
The lingui.config.ts is distributed through the core build - tsdown.config.ts
There was a problem hiding this comment.
That's looking good in part. However instead of auto-injecting the Vite plugin into the user's site, I'd pre-compile the .po files to JS. I think this could be done with he CLI as part of the admin build?
There was a problem hiding this comment.
That makes absolute sense - I'll see that it works, all ping you once I am confident in the solution
There was a problem hiding this comment.
All done - consuming sites use the pre-built messages.mjs, which runs through lingui compile during build, no Lingui config or Vite plugin needed for consumers.
Dev mode imports .po via the Vite plugin so changes are reflected immediately during development.
@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: |
146b661 to
0c53403
Compare
|
@ascorbic Hi Matt, the Lingui macros compile to useLingui() which requires I18nProvider in the React tree. To make component tests work, I've: Created a shared test render utility (tests/utils/render.tsx) that wraps with I18nProvider - import { render } from "vitest-browser-react";
+ import { render } from "../utils/render.js";No test logic changes — just the import path. The render utility initializes Lingui with empty English messages so source strings render as-is. |
|
All 707 admin component tests passing locally, including the 36 test files updated to use the I18nProvider wrapper. Core tests (2,095) also pass. |
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. |
|
This is looking great so far. I just need to get some clarity on how the setup will need to be handled for sites. Generally I want to avoid the need to install Vite plugins for core features |
827623e to
bf8cf11
Compare
|
Good idea to separate the PRs. We're very close! |
This reverts commit 33594c1.
Restores admin source aliasing for instant HMR in monorepo dev mode. Lingui macros are compiled via @babel/core dynamically imported from admin's devDependencies — zero new deps in core's package.json, nothing ships to end users.
ascorbic
left a comment
There was a problem hiding this comment.
Thanks for all of your work on this!
Sure! It was fun working on it. |
First community translation of the EmDash admin interface into German. Uses formal 'Sie' form appropriate for DACH business context. Covers all currently extracted admin strings (Settings, Login, LocaleSwitcher). Follows the i18n infrastructure from PR emdash-cms#234.
First community translation of the EmDash admin interface into German. Uses formal 'Sie' form appropriate for DACH business context. Covers all currently extracted admin strings (Settings, Login, LocaleSwitcher). Follows the i18n infrastructure from PR #234. Co-authored-by: Matt Kane <mkane@cloudflare.com>
What does this PR do?
Adds Lingui i18n infrastructure to the admin UI — plumbing only, no bulk string wrapping. Zero user-visible change. String wrapping will follow in a separate PR once this merges.
What's included
@lingui/core,@lingui/react,@lingui/macrowith pnpm catalogresolveLocale()server-side,useLocale()client hook, cookie persistencePluginRegistryandApp.tsxlingui compileproduces.mjsat build time — consuming sites need zero Lingui configSUPPORTED_LOCALES.length > 1, invisible until a second locale ships)I18nProviderwrapper in test render utilityWhat's NOT included (separate PRs)
feat/admin-i18n-strings)feat/admin-i18n-skills)Related
Design decisions
@lingui/*versions managed via pnpm catalog.lingui compileruns at admin build time. Consuming sites import.mjs, no Lingui config or Vite plugin needed.Review feedback addressed
locale:copyscript to./scripts/copy-locales.jslinguiBabelPlugintolinguiMacroPluginfor clarity (it's a rolldown plugin)@babel/coreor@lingui/vite-pluginin the published packageType of change
Checklist
pnpm typecheckpassespnpm --silent lint:json | jq '.diagnostics | length'returns 0pnpm testpasses (713 tests)pnpm formathas been runAI-generated code disclosure
Screenshots / test output
pnpm typecheck— passespnpm test— 48 test files, 713 tests passlingui extract— 40 strings in catalog (3 proof-of-concept components)