Looking for the editor’s own language — translating “Publish” into German,
or getting AI responses back in the user’s language? That is
Internationalization. This page is about content that exists in more
than one language.
The mismatch
Avocado’s content model has no locale axis:title, one slug, and props are flat values. A CMS with field-level
i18n stores the opposite shape — one document per page, and every translatable
field is an object:
The shape that works: one page per language
An Avocado page is one language of one page. Fifteen tri-lingual documents become forty-fivePageDocs. /de/prices and /fr/prix are two Avocado pages
backed by the same CMS document.
- Read projects each localised field down to the plain string that language renders. The editor then sees ordinary monolingual pages and the planner never meets a locale object.
- Write merges the edited value back into that language’s slot only.
- The language rides along in the slug, and in whatever context your publisher
carries (
diffPage’sctxis generic — put alangin it).
/de/prices is a page identity even on a
site that serves German at the root and answers 404 for that path. Put the URL a
visitor actually uses in meta.path;
absent means “the slug is the path”. Without it, an agent handed the slug and
told to open it lands on a 404 and cannot tell it was given an id.
Keep the source block. Carry the untouched CMS block along in the props (a
__source or _src key) so publish can diff against what it was projected
from, rather than against a projection of a projection. Note that undeclared
props are stripped unless the block’s schema is registered with a catchall —
see Registering your own block schemas.
Two rules, or the round trip corrupts content
Both of these were found by a round-trip check on a real tri-lingual site, not by reasoning about the model. A projection without them looks correct and silently rewrites the dataset.Build the round-trip check first
Before wiring the editor to anything, write a script that projects every document in every language, rehydrates it with no edits, and asserts the result is byte-identical to the source:What is not solved
- There is no UI affordance for “the same page in another language”. The editor sees forty-five independent pages and its page list is forty-five entries long; grouping them is up to your slugs.
- Nothing prevents the agent from being asked to edit the German page and writing English into it. The language is a property of which page is open.
duplicate_pageacross languages does what it says — it copies a page, not a translation.
Rich text
Rich text can round-trip if the CMS content was authored through a parser you can invert. Write the inverse serialiser and keep the original tree whenever re-serialising returns it unchanged; that makes rich text lossless unless it was actually edited. Where no such parser exists, the usual lossy flatten applies — see@avocadostudio-ai/richtext for the converters
that pivot through a ProseMirror document, and prefer a document-valued prop over
a markdown string when the CMS has real rich text.