WPML (multilingual sites)
Status (0.8.0): built on WPML's documented public hooks. Tested with WPML 5.0.2 and WPML String Translation on a single site (Dutch default, English second language): posts, terms, attachments, all four translation modes, edit screens, Settings Pages per language, translated field labels and bulk loading. Not tested yet: WPML's Advanced Translation Editor, the WPML Media add-on, and WPML on multisite. Run the manual test on your own WPML site before relying on it in production.
CAFE works without WPML exactly as before. When WPML is active and set up, CAFE adds one thing: each top-level field gets a Translation mode that decides how it behaves in other languages. Your theme code does not change:
cafe_get_field( 'property_title', $post_id ); // the value in the right language
cafe_get_option( 'phone' ); // the Settings Page value for the current language
There are no per-language fields (property_title_nl). A field is defined
once; WPML's translated posts and terms carry the values.
Installation and detection
- Install and set up WPML (a default language must be chosen).
- Keep CAFE active. Nothing else is needed.
On every request CAFE checks whether WPML is loaded (ICL_SITEPRESS_VERSION,
the wpml_current_language hook) and has a default language. If not, CAFE does
nothing multilingual and loads no WPML code: no errors when WPML is
deactivated, upgraded or not configured yet.
Where you see it in CAFE
- CAFE → Settings → Compatibility: WPML with its status (Active, Needs setup or Not activated), its version, WPML String Translation's version, and a link to WPML's languages screen.
- CAFE → Settings → Multilingual (WPML): shown when WPML is installed. It says whether WPML is active or still needs its setup wizard, the WPML version, whether String Translation is active (with a link to CAFE's field labels in WPML → String Translation), the site's languages with the default marked, and what the four translation modes do.
- Field group builder: everything about languages is on each field's Translation tab: the translation mode (radio buttons: Do not translate, Copy, Copy once, Translate; sub-fields: Same as parent, Copy (locked)), then the label per language. Fields not set to Translate show a badge in the field list.
- Edit screens of a translation: Copy fields are locked: read-only, with a lock icon after the label (and after the labels of their sub-fields); hover the field to see why. Translate and Copy once fields are unlocked. Do not translate fields are hidden. Settings Pages say which language you are editing.
Translation modes
Set per field in CAFE → Field Groups → field → Translation (the tab appears when WPML is active). Only top-level fields have a mode; sub-fields of a Field Set, Collection or Section Builder follow their top-level field.
| Mode | Value on a translation | On the translation's edit screen |
|---|---|---|
| Translate (default) | Its own value. Empty until a translator fills it in. | Editable |
| Copy (locked) | Always the original's value. Changing the original updates every language at once. | Read-only, lock icon after the label; hover: "Copied from the original (English)" |
| Copy once | The original's value until the translation saves its own; from then on independent. | Editable, prefilled with the original's value |
| Do not translate | Always the original's value. | Hidden |
Details:
- The original is the post or term the translation was made from (WPML's original element in the translation group).
- Copy / Do not translate values are stored once, on the original. They are
read from there at request time; nothing is duplicated into translations.
Saving such a field through a translation with
cafe_update_field()writes the original's value. The edit screen never does this: read-only and hidden fields are not saved from a translation's form, even if their value is posted. - Copy once needs no WPML event: the translation simply shows the original's value until it has its own. Collections and Section Builders shown this way are saved as the translation's own new rows; the original's rows are never touched.
- Missing translation value (Translate): empty, or the field's default value, like any empty field. There is no automatic fallback to the original; use Copy once for that behaviour.
Example setup for a property:
| Field | Mode |
|---|---|
| Title, Description | Translate |
| Price, SKU | Copy |
| Hero image, Gallery | Translate or Copy (see Media) |
| Internal ID | Do not translate |
| Layout structure (Section Builder) | Copy, or Translate for fully localised pages |
Sub-fields: same as parent, or locked
Inside a Collection, Section Builder or Field Set, each sub-field chooses on its Translation tab:
- Same as parent (default): follows the top-level field. In a translated Section Builder the sub-field is translated too.
- Copy (locked): on a translation the sub-field always shows the value of the same item on the original page (same position, same section kind), read-only with a lock icon. Typical use: images and galleries inside translated sections, where only the texts change per language.
An item that only exists in the translation (e.g. a third section when the original has two) reads a locked sub-field as empty. Locked sub-fields are not required on translations (the value comes from the original). Works for translated posts and terms; on Settings Pages sub-fields follow their parent.
Settings Pages
A Settings Page is one site-wide set of values, so languages are kept as scopes:
| Mode | Default language | Other language (WPML admin bar language) |
|---|---|---|
| Translate | Shared scope | Its own scope (empty until saved) |
| Copy | Shared scope | Shared scope, read-only on the page |
| Copy once | Shared scope | The default language's value until saved in this language |
| Do not translate | Shared scope | Shared scope, hidden on the page |
Switch the language in the admin bar to edit a Settings Page in that
language; a notice on the page says which language is being edited.
cafe_get_option( 'email' ) returns the value for the current request
language.
Collections and Section Builders
The top-level mode applies to the whole structure (rows, sections and all sub-fields):
- Translate: each language has its own items / sections.
- Copy: every language shows the original's items; edit them on the original.
- Copy once: a translation starts with the original's items and becomes independent on its first save.
Inside those items, a sub-field can be locked on its own (Translation tab: Copy (locked)): it then always shows the value of the same item of the original: same position, same section kind, at any depth. That is how a translated text and a shared image can live in one item. See Sub-fields: same as parent, or locked.
Gallery and media
CAFE stores attachment IDs. When a value is shown, CAFE asks WPML
(wpml_object_id) for the attachment's translation in the current language
and uses it when it exists (WPML Media Translation), otherwise the stored
attachment. This applies to Image, File, Video and every Gallery image, in
every mode. The stored ID never changes and no media file is duplicated.
- Translated image: translate the attachment in WPML Media; CAFE shows it automatically.
- Copied gallery: set the Gallery to Copy; the order and choice of images stay the original's, each image shown in the current language when translated.
- Missing translated media: the original attachment is shown.
Field labels
The main labels (and group title) are the default language's texts. The builder shows a box for every other language; every language shows the main labels until you type a translation. There are two ways to translate what editors see; field names and keys never change, so theme code stays the same.
In the CAFE builder (easiest). With WPML active:
- Group title: Settings card → Presentation → Title per language (default Nederlands "Homepagina" → English: "Home Page").
- Field, tab or collapsible label and instructions: open the field → Translation tab → Label per language.
- Section names (Section Builder): open the section → Section name per language.
Empty boxes use the main text. Edit screens show the text of the language you are editing (the WPML language switcher in the admin bar).
The texts are stored in the group's and field's own settings
(title_translations, label_translations), which CAFE already compiles and
caches: no extra table, no extra query, and nothing is loaded on the front
end. They are exported and imported with the field group.
In WPML String Translation. Group titles, labels and instructions are also registered there (context CAFE Field Groups) when a group is saved. A text typed in the CAFE builder wins over String Translation. Groups saved before WPML was activated are registered the next time you save them.
Language names in CAFE notices are WPML's native names ("Nederlands", "English"), so they read the same whichever language you are editing.
REST
CAFE has no public REST route for field values yet. WordPress REST
collections run in WPML's request language, and values read in
register_rest_field() callbacks follow the same rules as cafe_get_field().
Import and export
The translation mode is a normal field setting ("translation": "copy"):
exported and imported with the field group, validated (unknown modes are
rejected), and kept when WPML is not installed. Values are never exported.
Object types
| Object | Supported | Reason |
|---|---|---|
| Posts, pages, custom post types | Yes | WPML translation groups |
| Taxonomy terms | Yes | WPML translation groups (by term_taxonomy_id) |
| Settings Pages | Yes | Language scopes |
| Users | Unsupported | WPML does not give users a language; values are shared |
| Comments | Unsupported | No WPML translation relationship for comments |
Multisite
Every site has its own CAFE tables, field settings, Settings Page language scopes and caches, and its own WPML setup. Nothing is shared between sites.
Hooks
cafe_language_provider lets another plugin supply languages instead of WPML.
The rules use the general CAFE hooks cafe_value_context, cafe_field_view,
cafe_prime_object_ids and cafe_display_object_id. See Hooks.
Limitations
- Tested with WPML 5.0.2 on a single site; see the status note for what was not tested.
- No per-sub-field modes inside Collections / Section Builders (see above).
- Translate mode does not fall back to the original when empty (use Copy once).
- Users and comments are not translated.
- WPML's own translation editor (ATE / translation jobs) is not wired to CAFE fields yet: translators edit CAFE fields on the translation's edit screen.
Manual test
On a site with WPML (English default, plus Dutch and German):
- Create a field group on a custom post type "property" with
title(Translate),description(Translate),price(Copy),sku(Copy),hero_image(Translate),gallery(Copy),internal_id(Do not translate) and a Collectionteam_members(Copy once) withnameandposition. - Create an English property and fill everything. Translate it to Dutch with the WordPress editor ("+" in the Languages box).
- On the Dutch edit screen check: price / sku / gallery read-only with the note, internal_id hidden, team_members prefilled. Fill title and description in Dutch and save.
- On the front end in each language check
cafe_get_field()for every field. Change the English price, and check that it changed in Dutch too. - Settings Page "Contact" with
phone(Copy),email(Translate),address(Copy once): edit it in English, then switch the admin bar to Dutch and edit again. Checkcafe_get_option()on the front end per language. - Translate one image in WPML Media and check that the Dutch page shows it.
- Loop over 100 Dutch properties with
WP_Query(Query Monitor installed): CAFE should run a constant number of queries, not one per property. - Report anything unexpected with the steps to reproduce.
Troubleshooting
- No translation choice in the builder: WPML is not active or has no default language; reload the builder after finishing WPML's setup wizard.
- Translation shows the English value: the field is set to Copy, Copy once (and not translated yet) or Do not translate.
- Dutch Settings Page is empty: Translate fields start empty per language; use Copy once to start from the default language.