i18n Locale-Scoped Operations#
The Document Service supports internationalized (i18n) content types where each document can have per-locale versions. A recurring class of bugs has emerged around locale scoping: operations meant to affect one locale accidentally affect others, or operations on non-i18n content types fail because locale logic is applied where it shouldn't be.
The three problem surfaces are:
- Cross-locale data overwrite — publishing/updating one locale syncs non-localized fields to all other locales even when those fields didn't change, or misclassifies component/dynamiczone fields as non-localized.
- Null locale errors on non-i18n types — relation transforms pass
locale: nullwhen querying non-i18n content types, causing "Document not found" errors. - Broken delete/query for non-i18n types — locale-aware query helpers return empty results when applied to content types that have no locale field.
Non-Localized Field Syncing#
When a localized content type is mutated, the i18n plugin propagates any non-localized field changes to all other locales via a Document Middleware registered in bootstrap.ts. The middleware fires on create, update, discardDraft, and publish actions, but skips non-localized content types entirely .
Determining which fields are non-localized is done by isLocalizedAttribute in content-types.ts. An attribute is localized (and therefore excluded from syncing) if it carries the pluginOptions.i18n.localized: true option, is relational, or is a uid type. getNonLocalizedAttributes returns the inverse: all visible attributes that are not localized.
Fix: components and dynamiczones are now treated as localized (PR #25922)#
Before PR #25922, isLocalizedAttribute did not check for component or dynamiczone types. This caused syncNonLocalizedAttributes to treat component fields (e.g., an SEO component) as non-localized, silently overwriting them across all locales whenever any locale was published. The fix added two isTypedAttribute checks to the function .
Fix: sync only when non-localized fields actually changed (PR #24648)#
Before PR #24648, syncNonLocalizedAttributes was called unconditionally on every mutation. The fix introduced a shouldSync guard that compares the pre- and post-operation values of non-localized fields using deep equality (lodash/fp isEqual). Syncing is skipped unless at least one non-localized field value actually changed . originalData is fetched before the operation proceeds so the comparison is valid .
Locale Resolution Failures on Non-i18n Content Types#
Two separate bugs caused failures when locale-aware logic ran against content types that have no locale field.
Null locale in relation transforms (PR #24923): The Document Service's ID-map transform (packages/core/core/src/services/document-service/transform/id-map.ts) converts document IDs to entity IDs by querying the database. When the calling context had no locale, locale: null was being forwarded into the WHERE clause. For non-i18n content types — which store no locale column — this produced "Document with id …, locale 'null' not found" errors. PR #24923 fixed this by conditionally omitting the where.locale parameter altogether for non-i18n models.
Empty locale array in delete handler (PR #26308): The Content Manager delete handler (collection-types.ts) resolves the locale from the query, then calls documentManager.findLocales to enumerate locale-specific documents before deletion . For non-i18n collection types with Draft & Publish enabled, passing locale=* to findLocales returned an empty array (since those documents have no locale field), causing a spurious 404. PR #26308 fixes this by checking isLocalizedContentType(model) first and calling documentManager.delete directly — bypassing locale resolution — for non-i18n types.
Key Files and Entry Points#
| File | Role |
|---|---|
packages/plugins/i18n/server/src/services/content-types.ts | isLocalizedAttribute, isLocalizedContentType, getNonLocalizedAttributes — the source of truth for which attributes/types are locale-scoped |
packages/plugins/i18n/server/src/bootstrap.ts | Document middleware that conditionally syncs non-localized fields across locales |
packages/core/content-manager/server/src/controllers/collection-types.ts | Delete/publish handlers in the Content Manager; locale resolution via getDocumentLocaleAndStatus |
packages/core/core/src/services/document-service/transform/id-map.ts | Document-to-entity ID resolution; must avoid setting where.locale for non-i18n models |
Related PRs:
- fix: modifying & publishing locale should only update that locale (#24648)
- fix(document-service): resolve locale "null" errors when updating non-i18n relations (#24923)
- fix(i18n): treat component and dynamiczone as localized (#25922)
- fix(content-manager): skip locale resolution for non-i18n content types in delete (#26308)