Blog /
DesignSetGo and WPML: how multilingual sites read through the bridge
A real-world agency support ticket from last month:
“We installed your plugin on a client site running WPML with English, Spanish, and German. When a visitor is on the Spanish version, your app’s listings show English posts. Is this a bug?”
It is not a bug, but the explanation deserves a post of its own because multilingual is the messiest corner of WordPress and the integration question keeps coming up.
This post is the model. How the bridge interacts with WPML, Polylang, and TranslatePress. The three patterns most apps want (show current language, show all translations, let the user switch). And the caveats per plugin.
The bridge is language-neutral by default
dsgo.posts.list() is a pass-through to WordPress’s WP_Query (specifically the REST API’s posts endpoint). It returns whatever WordPress returns for a query with no language scope set.
On a single-language site, that is just “the posts.” On a multilingual site, the behavior depends on the multilingual plugin:
- WPML: WPML hooks into
WP_Queryto filter by the current language context. The current context is determined by the URL the request came in on. If the request came in on/es/, the context is Spanish; queries return Spanish posts. If the request came in on/, the context is English (the default); queries return English posts. - Polylang: similar to WPML in mechanism. Hooks
WP_Queryto filter by current language. - TranslatePress: TranslatePress does not duplicate posts per language. It translates one canonical post on the fly. So a
dsgo.posts.list()call always returns the canonical (default-language) posts; the translation happens at render time in the page output.
The agency ticket above turned out to be a CDN caching issue: the /es/ page was being served from cache with / context. WPML and the bridge were behaving correctly; the cache was lying. Once they bypassed cache for the app’s iframe URL, the listings switched to Spanish.
The default rule: the bridge respects whatever language context the page came in on. If your app is rendering at /es/apps/listings/, the bridge sees the Spanish context. If it is rendering at /apps/listings/, English (or whatever the site default is).
Pattern 1: show the current language
The most common pattern. The app shows posts in whatever language the visitor is reading.
For WPML and Polylang sites: you get this for free if the URL has the language prefix:
const posts = await dsgo.posts.list({ per_page: 10 });
// On /es/apps/listings/, returns Spanish posts.
// On /apps/listings/, returns English posts.
For TranslatePress sites: the call returns canonical posts; the translation happens in the rendered HTML. If your app is rendering text into the page from the post content, TranslatePress sees that text on its way to the visitor and translates it. You do not need to do anything special; the integration is transparent.
If you want to be explicit about the current language (for example, to set a lang attribute or to render a “currently viewing in Spanish” indicator), call:
const site = await dsgo.site.info();
const currentLang = site.current_language; // 'es', 'en', 'de', etc.
The current_language field is populated by the bridge from WPML/Polylang context, or from the WordPress site language as a fallback.
Pattern 2: show all translations of a post
A “this article is also available in:” widget. List the post’s other-language translations and link to each.
For WPML:
const ctx = await dsgo.context.post();
const translations = ctx?.translations ?? [];
// [{ language: 'es', url: '/es/post-slug', title: 'Título en español' }, ...]
The bridge surfaces WPML’s translation pairs as translations on the post context. The list includes every published translation of the current post (not the canonical one in the current language, which is the post itself).
For Polylang: same shape, also populated.
For TranslatePress: no translations are returned (TranslatePress does not maintain separate posts per language). The widget should hide if translations is empty, which it would be on a TranslatePress site.
Pattern 3: let the user switch languages
A language picker the visitor uses to switch between WPML/Polylang language variants. The bridge does not handle the routing itself; you render the picker and link to the language URL.
const site = await dsgo.site.info();
const langs = site.languages;
// [{ code: 'en', name: 'English', url: '/' }, { code: 'es', name: 'Spanish', url: '/es/' }, ...]
renderPicker(langs);
On click, the user navigates to the language URL. The bridge picks up the new context on the next page load. Your app does not need to manage language state across navigations; WordPress and WPML do that.
Caveats per plugin
WPML. Most-installed multilingual plugin. The bridge’s integration is well-tested against the common cases. Edge case: WPML’s “use language from URL” is the assumed configuration. If the site uses cookies or browser-detection for language switching (the less common “language switcher in header” mode), the bridge sees the cookie context. The behavior is correct but the apparent “current language” can change between two visitors hitting the same URL, which surprises some app authors.
Polylang. Very similar to WPML structurally. The translations surface and language switcher work the same way. Polylang’s free tier is more featureful than WPML’s, so it shows up disproportionately on smaller sites. The bridge does not distinguish between them.
TranslatePress. A different model entirely. TP does not duplicate posts; it translates on the fly. The bridge returns canonical posts; TP intercepts the rendered HTML and translates it. This means: (a) translations is always empty, (b) current_language reflects what TP thinks the visitor wants, (c) the actual translated text shows up in the rendered page, not in the bridge response. Your app’s logic should treat TP as a single-language site from the data layer’s perspective; the translation is a presentation concern.
Other plugins (Loco, Weglot, etc.). Loco is a string-translation tool for plugin/theme strings, not a content-translation tool; it does not affect the bridge. Weglot is a CDN-based translation overlay; it sits in front of the site and translates everything in the rendered HTML, similar to TP. The bridge does not see Weglot; the integration is transparent and lives entirely in the network layer.
The shorter version
The bridge respects whatever language context the page came in on. WPML and Polylang give you per-language post lists for free. TranslatePress is transparent at the data layer and translates at render time. Three patterns cover most apps: show the current language, list translations, render a switcher.
If your app’s multilingual behavior surprises you, the first place to check is the URL the iframe is loading from. The bridge’s context is determined by the iframe URL, not by the parent page URL. Usually they match; on aggressive caching setups they sometimes do not.
Further reading
- BRIDGE-API.md §site.info: the
languagesandcurrent_languagefields. - WPML and the REST API: the canonical WPML docs on the layer the bridge sits on top of.
- The bridge cookbook: includes the “language switcher” pattern.