feat: Add content engine Stage 5D — Localized Search and Hardening - #740
Open
aXenDeveloper wants to merge 2 commits into
Open
feat: Add content engine Stage 5D — Localized Search and Hardening#740aXenDeveloper wants to merge 2 commits into
aXenDeveloper wants to merge 2 commits into
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
aXenDeveloper
marked this pull request as ready for review
August 7, 2026 19:54
…ontent Engine Stage 5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lifts the last capability boundary:
localizationnow combines withsearch. 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
contentTranslationSearchDocumentmerges the two halves and gates onisContentTranslationPubliclyVisible, the same subordination the public read enforces in SQL. A translation of a draft record is never indexed.createdAtis that language's publication date, so a late translation sorts by "newest" where it actually appeared.search.pathTemplatemust 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.deletetakes 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.syncContentLocalizedSearchmoves 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.Rebuild
createContentLocalizedSearchIndexerpages 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.itemsReadcounts translation rows andcount()reports published translations, so the coverage bar compares like with like.Diagnostics and AdminCP
/search/statusreports 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.Missingincluded, and lives in the URL so it survives a reload.No migration:
core_search_indexhas stored one row per(itemType, itemId, languageCode)since it was created.Improving Documentation
pnpm lint:fixto fix formatting issues before opening the PR.Description
What?
Why?