fix(16.2): WR-06 hand a translatable Decode only strings

liftMLValues validated only object-shaped submissions, so a scalar
submitted for a translatable plugin field reached Decode as nil, a
float64 or a bool although pact promises a string; a type that asserts
a string panicked into an opaque 500. decodePluginFields now passes a
string through, turns null into the empty string and refuses any other
value with a 422 on the field before Decode runs.
This commit is contained in:
2026-10-10 16:45:05 +02:00
parent c532529d18
commit f388d27604
5 changed files with 60 additions and 3 deletions

View File

@@ -100,7 +100,7 @@ A notice is how a type that changes an accepted value tells the administrator wh
## Translatable types
A type with `Translatable: true` holds one string per content locale, like `mlmarkdown`. The admin sends and receives a map of locale to string, and cabana stores it through its own multilingual path: the default locale's value goes to the host column, every other locale through the published `cabana.TranslationWriter`, inside the same permissioned save transaction. The plugin implements no locale storage.
A type with `Translatable: true` holds one string per content locale, like `mlmarkdown`. The admin sends and receives a map of locale to string, and cabana stores it through its own multilingual path: the default locale's value goes to the host column, every other locale through the published `cabana.TranslationWriter`, inside the same permissioned save transaction. The plugin implements no locale storage. `Decode` of a translatable type always receives a string: a client that submits a plain value instead of a locale map has it decoded for the default locale, `null` as the empty string, and any other JSON value (a number, a boolean) is a 422 on the field before `Decode` runs.
`Decode` runs once per submitted locale, with `Locale` set to that content locale and `Default` true for the host column's locale; its `Value` must be a string. Each locale's notices carry that locale. `Load` runs once per locale of the record response. `Translated` reads another attribute of the same record in the same locale, exactly and without fallback, which a type that keeps two columns in step (a source and a rendered copy, for example) uses to compare them. Without a translation writer a locale map is a 422, as for the built-in multilingual types.