Skip to content

feat: Add content engine Stage 5D — Localized Search and Hardening - #740

Open
aXenDeveloper wants to merge 2 commits into
feat/Universal-Content-Engine-mvp-5cfrom
feat/Universal-Content-Engine-mvp-5d
Open

feat: Add content engine Stage 5D — Localized Search and Hardening#740
aXenDeveloper wants to merge 2 commits into
feat/Universal-Content-Engine-mvp-5cfrom
feat/Universal-Content-Engine-mvp-5d

Conversation

@aXenDeveloper

Copy link
Copy Markdown
Owner

Lifts the last capability boundary: localization now combines with search. A localized record is indexed once per published translation, each document built from the two halves the public read joins - shared values off the base row, localized ones off the translation.

Documents

  • contentTranslationSearchDocument merges the two halves and gates on isContentTranslationPubliclyVisible, the same subordination the public read enforces in SQL. A translation of a draft record is never indexed.
  • createdAt is that language's publication date, so a late translation sorts by "newest" where it actually appeared.
  • search.pathTemplate must carry {locale} on a localized content type and may not on any other: two languages routinely answer to the same slug, so one template without it would give every translation the same link.

Synchronisation

  • SearchModel.delete takes an optional language. Unpublishing the Polish copy must leave the English document exactly where it is; the Elasticsearch adapter narrows its delete-by-query the same way.
  • syncContentLocalizedSearch moves one language for a translation mutation and every language for a mutation of the record - its publication state gates them all, and a shared field is in all of them.
  • The generated routes and the scheduled-transition handler pass the model, which is what enumerates the translations.

Rebuild

  • createContentLocalizedSearchIndexer pages over translations with a keyset cursor on (itemId, languageId): a page can neither overlap nor skip while rows are published underneath it, and Postgres seeks rather than counts. itemsRead counts translation rows and count() reports published translations, so the coverage bar compares like with like.

Diagnostics and AdminCP

  • /search/status reports documents alongside distinct items and a per-language breakdown; coverage is measured in documents when a collection has languages, read off the data rather than configured.
  • A localized content type's list gets a language selector - a view control, not a filter: it adds a column with each record's title and status in that language, Missing included, and lives in the URL so it survives a reload.

No migration: core_search_index has stored one row per (itemType, itemId, languageCode) since it was created.

Improving Documentation

Description

What?

Why?

Lifts the last capability boundary: `localization` now combines with `search`.
A localized record is indexed once per published translation, each document
built from the two halves the public read joins - shared values off the base
row, localized ones off the translation.

Documents
- `contentTranslationSearchDocument` merges the two halves and gates on
  `isContentTranslationPubliclyVisible`, the same subordination the public read
  enforces in SQL. A translation of a draft record is never indexed.
- `createdAt` is that language's publication date, so a late translation sorts
  by "newest" where it actually appeared.
- `search.pathTemplate` must carry `{locale}` on a localized content type and
  may not on any other: two languages routinely answer to the same slug, so one
  template without it would give every translation the same link.

Synchronisation
- `SearchModel.delete` takes an optional language. Unpublishing the Polish copy
  must leave the English document exactly where it is; the Elasticsearch adapter
  narrows its delete-by-query the same way.
- `syncContentLocalizedSearch` moves one language for a translation mutation and
  every language for a mutation of the record - its publication state gates them
  all, and a shared field is in all of them.
- The generated routes and the scheduled-transition handler pass the model, which
  is what enumerates the translations.

Rebuild
- `createContentLocalizedSearchIndexer` pages over translations with a keyset
  cursor on `(itemId, languageId)`: a page can neither overlap nor skip while
  rows are published underneath it, and Postgres seeks rather than counts.
  `itemsRead` counts translation rows and `count()` reports published
  translations, so the coverage bar compares like with like.

Diagnostics and AdminCP
- `/search/status` reports documents alongside distinct items and a per-language
  breakdown; coverage is measured in documents when a collection has languages,
  read off the data rather than configured.
- A localized content type's list gets a language selector - a view control, not
  a filter: it adds a column with each record's title and status in that
  language, `Missing` included, and lives in the URL so it survives a reload.

No migration: `core_search_index` has stored one row per
`(itemType, itemId, languageCode)` since it was created.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
vitnode-docs Ready Ready Preview Aug 8, 2026 7:44am

@github-actions github-actions Bot added the 💡 Feature A new feature label Aug 7, 2026
@aXenDeveloper
aXenDeveloper marked this pull request as ready for review August 7, 2026 19:54
@aXenDeveloper aXenDeveloper changed the title feat(content): index one search document per published translation feat: Add content engine Stage 5D — Localized Search and Hardening Aug 7, 2026
@github-actions github-actions Bot added 💡 Feature A new feature and removed 💡 Feature A new feature labels Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 Feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant