Skip to content

fix(taxonomies): respect active locale in admin surfaces - #2342

Open
fbartolitsch wants to merge 2 commits into
emdash-cms:mainfrom
fbartolitsch:codex/2224-taxonomy-locale-normalization
Open

fix(taxonomies): respect active locale in admin surfaces#2342
fbartolitsch wants to merge 2 commits into
emdash-cms:mainfrom
fbartolitsch:codex/2224-taxonomy-locale-normalization

Conversation

@fbartolitsch

@fbartolitsch fbartolitsch commented Aug 5, 2026

Copy link
Copy Markdown

What does this PR do?

Summary

  • Resolves localized taxonomy definitions once per logical taxonomy before rendering admin navigation and editor choices.
  • Uses one shared fallback policy: exact active locale, then the configured default locale, then a deterministic definition.
  • Scopes visible taxonomy term counts and their cache entries to the resolved content locale.

Scope

  • Admin sidebar taxonomy navigation and content-editor taxonomy choices.
  • Runtime manifest taxonomy identity metadata (id, locale, and translationGroup).
  • Core taxonomy count queries and cache keys.
  • DE, EN, and FR regression coverage for the sidebar, editor choices, manifest metadata, and term counts.
  • Patch changesets for @emdash-cms/admin and emdash.

Validation

  • pnpm format and pnpm format:check
  • pnpm typecheck
  • pnpm lint
  • JSON lint: 0 diagnostics
  • Admin regression tests: 23 passed
  • Core regression tests: 14 passed
  • pnpm build
  • git diff --check
  • Local visual verification for DE, EN, and FR: one logical taxonomy per locale in the sidebar, only the active-locale taxonomy in the editor, and locale-specific visible counts (DE 1, EN 2, FR 3).

Risk and rollback

  • The resolver groups explicit translations by translationGroup; legacy definitions without that metadata fall back to their taxonomy name.
  • Fallback selection is deterministic, but installations with incomplete locale metadata should verify which default definition is selected.
  • Count queries gain a locale predicate and locale-aware cache key; no schema migration or data rewrite is included.
  • The repository's fixed-package and dependent-bump policy expands the two direct changeset entries to a 17-package patch release set; Changesets bot confirmed this on the PR.
  • Upstream main advanced by two commits after local validation. A Git merge-tree check reports no conflict; CI remains the final integration check.
  • Rollback is a direct revert of this commit.

Security/privacy impact

No new external calls, credentials, permissions, or personal-data processing are introduced.

Fixes #2224
Fixes #2338

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

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
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). No new user-visible strings or messages.po changes are included.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: N/A — this is a bug fix.

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: OpenAI Codex (GPT-5)

Screenshots / test output

Targeted automated tests:

Admin: 2 files, 23 tests passed
Core: 3 files, 14 tests passed

Manual demo verification used one consistent multilingual taxonomy fixture across DE, EN, and FR. The sidebar and editor each displayed one active-locale definition per logical taxonomy; visible term counts were DE 1, EN 2, and FR 3.

Group translated definitions by logical identity before rendering admin navigation and editor choices. Scope visible term counts and cache entries to the resolved content locale.
@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f488ff5

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

This PR includes changesets to release 17 packages
Name Type
@emdash-cms/admin Patch
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site 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/auth-atproto 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 github-actions Bot added the review/needs-review No maintainer or bot review yet label Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@fbartolitsch

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Aug 5, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a focused bug fix with the right shape: normalize localized taxonomy definitions to one logical row per locale, then use that resolved locale in admin labels, editor choices, manifest metadata, and visible term counts. I read the full diff, the changed source files, and the relevant route/handler call sites.

Headline: the approach is sound, but the sidebar navigation only respects the active locale for the label — the taxonomy-management link it produces drops the ?locale= parameter, so clicking a DE item lands on the default-locale page. That undermines the stated goal and should be fixed before merge.

I also found a smaller edge where the API term-list/term-get handlers still scope visible counts off the lowest-locale taxonomy definition rather than the active-locale one. That is safe when translations share collections, but it is inconsistent with the public taxonomies/index.ts helpers and worth aligning.

No SQL-injection issues: the dynamic locale predicate is interpolated as a Kysely parameter. No new logged-out round-trips are introduced; the count query just gains a locale predicate and a locale-aware cache key. Tests cover the new resolver, editor filtering, manifest identity, and locale-scoped counts.


Findings

  • [needs fixing] packages/admin/src/components/Sidebar.tsx:254

    The sidebar resolves the active-locale taxonomy label using routeLocale, but the generated taxonomy nav items omit the locale, so the link navigates to e.g. /taxonomies/course without ?locale=de. Clicking a German taxonomy in the sidebar will open the taxonomy-management route in the configured default/locale-less fallback, which partially defeats the PR's goal of respecting the active content locale on admin surfaces.

    Extend NavItem to carry optional query params and append them in resolveItemPath so taxonomy links preserve the active locale:

    // in the manageItems construction
    ...getSidebarTaxonomies(manifest.taxonomies, routeLocale, manifest.i18n?.defaultLocale).map(
    	(tax) => ({
    		to: "/taxonomies/$taxonomy" as const,
    		label: tax.label,
    		icon: getTaxonomyNavIcon(tax.name),
    		params: { taxonomy: tax.name },
    		search: routeLocale ? { locale: routeLocale } : undefined,
    		minRole: ROLE_EDITOR,
    	}),
    ),
    
    // interface NavItem { ... search?: Record<string, string>; ... }
    
    function resolveItemPath(item: NavItem): string {
    	let path = item.to;
    	if (item.params) {
    		for (const [key, value] of Object.entries(item.params)) {
    			path = path.replace(`$${key}`, value);
    		}
    	}
    	if (item.search && Object.keys(item.search).length > 0) {
    		const q = new URLSearchParams(item.search).toString();
    		path += `?${q}`;
    	}
    	return path;
    }
  • [suggestion] packages/core/src/api/handlers/taxonomies.ts:393

    handleTermList resolves the taxonomy with requireTaxonomyDef(db, taxonomyName) (no locale), which returns the lowest-locale row, and then scopes visible counts to defCollections(lookup.def). If a taxonomy's translations declare different collections, the active-locale term list will count the wrong collections. The public helper getTaxonomyDef(name, options) already resolves the active-locale definition.

    Suggestion: pass the resolved locale to requireTaxonomyDef here (and in handleTermGet at line 659) so counts use the same definition the user is actually viewing:

    const lookup = await requireTaxonomyDef(db, taxonomyName, locale);

@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review overlap and removed review/needs-review No maintainer or bot review yet labels Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant