Skip to content

fix(localized): read a record in plain data as its values by locale - #112

Merged
Upd4ting merged 3 commits into
mainfrom
fix/localized-plain-data-records
Nov 11, 2026
Merged

Upd4ting merged 3 commits into
mainfrom
fix/localized-plain-data-records

Conversation

@Upd4ting

Copy link
Copy Markdown
Member

Summary

PlainData<T> says that fromPlainData can read a localized field as one value per locale. The runtime did not do that:

  • fromPlainData({ displayName: { en, fr } }).localize("en") stored { en: { en, fr } }, so the record ended up nested under one locale.
  • With no localize() call, the record stayed floating, and insert stored no text.

LocalizationModifier now has a fromPlainData event handler. When the plain data gives a plain object for a localized field, the handler unlocks that field under "*", so the object is locked as the field's texts by locale. attachModifier registers the method as an event. insert, update and toDatabase call fromPlainData, so they get the same behaviour.

The handler acts only when Localization is the field's last modifier. With @Localized() @Encrypted(), encryption runs inside the locales. Locking under "*" would then store the cipher triple as locales {0, 1, 2}, and that row could never be read back. Such a field keeps its old behaviour.

Release

This is a patch. It makes the runtime match the published PlainData type, and it changes no signature.

These callers can see a difference:

  • A caller that passes a plain object for a localized field through fromPlainData, insert, update or toDatabase. Before, the object was nested under a locale or dropped. Now every locale is stored. On update, the given record replaces the stored texts.
  • A localized field whose own value is an object, such as a localized ImageValue, given as a single value. Before, fromPlainData({ image: { key, alt } }).localize("en") stored { en: { key, alt } }. Now key and alt are read as locales. PlainData already allows both readings, and this PR picks the one the type documents. The DMS data API builds its write instance itself and does not call fromPlainData, so it is not affected. The one DMS fromPlainData call on a localized table (attachments/table-view.ts) runs unlock(…, "*") straight after, so its result does not change.

Rows that are already stored do not change. Only new writes that carry a plain object for a localized field store a different shape.

Scope

  • Out of scope: ANT-1971 (null at the end of a read).
  • Known gap, already on main: for @Localized() @Encrypted(), fromPlainData(…).localize("en") on a new row throws in ModifiersDynamicMetadata.set, for a single text too. That is a separate bug.

Related issue

ANT-1973, found by ANT-1912 (dms-saas).

Verification

The new MongoDB suite src/tests/localized_plain_data.test.ts was run through ajs module test.

Before, on main with only the test commit:

  Localized fields - plain data with values by locale
    1) stores every locale of a record read under one locale
    2) stores every locale of a record read with no locale
    ✔ stores a plain value under the locale it is read with
    ✔ stores a null value under the locale it is read with

  1) AssertionError: expected { Object (en) } to deeply equal { en: 'Members', fr: 'Membres' }
       -  "en": { "en": "Members", "fr": "Membres" }
  2) AssertionError: expected undefined to deeply equal { en: 'Members', fr: 'Membres' }

After:

  Localized fields - plain data with values by locale
    ✔ stores every locale of a record read under one locale
    ✔ stores every locale of a record read with no locale
    ✔ stores a plain value under the locale it is read with
    ✔ stores a null value under the locale it is read with
    ✔ stores no record for a field encrypted inside its locales

  5 passing
flowchart LR
  P["plain data { displayName: { en, fr } }"] --> A[assign fields]
  A --> E{fromPlainData event}
  E -->|"plain object, Localization last"| S["unlock field under '*'"] --> L["lock { en, fr }"]
  E -->|"text, null, or inner modifier"| F[value floats until localize]
Loading

Checklist

  • The pull request title follows Conventional Commits.
  • I added or updated tests when behavior changed.
  • I updated documentation when users or contributors are affected.
  • I documented breaking changes and migration steps.
  • I did not include credentials or unrelated changes.

@Upd4ting
Upd4ting merged commit 2d88b8a into main Nov 11, 2026
8 checks passed
@Upd4ting
Upd4ting deleted the fix/localized-plain-data-records branch November 11, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant