When building multilingual web applications, ensuring that every piece of content is translated accurately is more than just helpful—it’s essential. One of the go-to services for managing translations dynamically is Weglot, a powerful tool that integrates easily into most web frameworks and handles translations automatically. However, in complex real-world applications, especially those that rely heavily on client-side JavaScript to render content dynamically, Weglot alone may fall short.
TL;DR: Weglot is excellent for translating static and server-rendered content but struggles with dynamic client-side widgets. In this article, I share how this issue affected our multilingual app and why client-side rendering poses a challenge for Weglot. I also explain how I solved it using a server-side rendering (SSR) fallback strategy. If you rely on client-rendered content, you’ll need a custom strategy like this to maintain consistent translations.
The Problem: Dynamic Content Lost in Translation
Our web application relies heavily on third-party widgets and custom components that render on the client side after the page is loaded. This includes things like:
- Chat support modules
- Product recommendation sliders
- Interactive forms and pop-ups
Weglot integrates by scanning the DOM at load and using mutation observers to detect changes. This works for modest updates but breaks down when an entire section is rendered after the page has already loaded. In our case, the widgets were invisible to Weglot because the translation system didn’t pick up on client-created DOM elements quickly enough—or at all.
The result? Users switching to French or Spanish were still seeing English-only text inside widgets and forms, ruining the multilingual experience we were trying to build.
Understanding Weglot’s Limitations
There are several reasons Weglot struggles with client-side content:
- Delayed Rendering: Content that appears after the initial page load requires Weglot to re-scan the DOM or rely on manual triggers.
- Shadow DOM and Isolation: Some widgets render in shadow DOMs, out of reach from Weglot’s mutation observer.
- Dynamic Text Generation: Content built via JavaScript functions or on-the-fly APIs often does not exist in the DOM long enough for Weglot to catch it.
Multiple calls to Weglot.refresh() as content loads became unsustainable. Widgets would sometimes update before Weglot re-rendered—or worse, update again afterward, undoing the translated strings.
Why Client-Side Fixes Fell Short
I tried implementing a few client-side workarounds before committing to an SSR strategy. These included:
- Adding explicit
Weglot.refresh()calls after each dynamic load - Injecting translated text manually using the Weglot API
- Polling DOM elements for changes and feeding them into Weglot
Unfortunately, each solution was fragile. Monitoring hidden elements or non-standard widget structures led to race conditions and performance issues. It also created excessive coupling between the translation logic and business logic, which complicated debugging and testing.
The SSR Fallback Strategy
After banging my head against this problem for several sprints, I took a step back and reimagined the architecture. If Weglot struggled with content rendered dynamically in the browser, what if I could ensure that all content—widget data included—was available at the server-rendering stage?
Enter: Server-Side Rendering Fallback.
In this approach, I made the following changes:
- Refactored Widgets to Render on the Server: Where possible, I fetched widget data server-side and rendered HTML in advance using Next.js’s SSR capabilities.
- Serialized Translated Text: I used Weglot’s API to fetch translations server-side for widget content and shoveled it into JSON objects.
- Injected Localized HTML: During SSR, the page delivered a fully-translated version of widgets as static strings, ensuring Weglot could process it immediately.
An Example Walkthrough
Let’s say I had a product widget that displayed the names of top items in a store. Previously, this would load via client-side JavaScript, calling an API and rendering in a Vue or React component. I updated it to instead fetch data in the Next.js getServerSideProps() function like so:
export async function getServerSideProps(context) {
const lang = context.locale || 'en';
const data = await fetchProducts(lang);
return {
props: { data }
}
}
This allowed me to pull content in the user’s preferred language and deliver HTML-ready components. Weglot could now parse the page at initial load and serve up a fully translated experience—even for dynamic elements.
Maintaining Flexibility
Of course, not all content can be rendered server-side. Some widgets—like chat boxes or video players—have real-time interactivity. For these, I created hybrid solutions:
- Fallback Static Message: For each widget, I provided a server-rendered fallback message that explained or summarized the widget’s purpose.
- Invisible Tags: I added
aria-labeland hidden spans with translated strings to help Weglot understand context. - Weglot-Optimized Config: Custom data attributes prefixed with
data-wegletto feed untranslated DOM with tagged translation cues.
It’s not as seamless as true SSR, but it ensured users at least understood what the widget was doing in their native language.
Unexpected Wins
Pushing content to the server had unexpected benefits beyond just translation:
- Better SEO: Google indexed more localized content, improving organic traffic in multiple languages.
- Performance Gains: Server-rendering allowed for leaner client bundles and fewer HTTP calls.
- Improved Accessibility: With structured, stable content, screen readers became more accurate in interpreting widget UI.
Things to Watch Out For
SSR is a powerful tool, but it’s not without challenges. Here are a few pitfalls I encountered during implementation:
- Hydration Mismatches: Be cautious when rehydrating client-side frameworks like React or Vue on server-rendered HTML, as mismatches may occur.
- Caching Conflicts: If you cache at the CDN level, make sure translations don’t get mixed by language. Set proper
Vary: Accept-Languageheaders. - API Throttling: Weglot has rate limits. If you fetch translations programmatically via API, watch those limits carefully.
Conclusion
Weglot is incredibly effective at translating well-structured web apps, but even it can’t keep up with JavaScript-heavy, client-side rendering strategies on its own. If you’re facing untranslated content in widgets or dynamic modules, consider an architectural adjustment through partial or full SSR. By moving content compilation to the server where possible, I brought Weglot back into the loop and delivered a consistent, multilingual UX end-to-end.
This approach might not suit every project, but for ours, it meant the difference between patchy translations and a truly global-ready product.
If you’re struggling with similar issues in your multilingual app, don’t give up. Step back, consider how and where your content is rendered, and remember: even dynamic widgets can be made static long enough to get the job done right.
