Seatext library

Why SeaText May Translate the Same Element Twice in a SPA

Duplicate translation happens when SeaText’s script runs on the initial page render and then runs again on a client‑side route change without checking whether the node is already translated. Adding a guard that skips...

SeaText injects a JavaScript snippet into your Single Page Application. The snippet runs as soon as the page loads and also on every route update that the SPA framework triggers. If the translation function is called on both events without first checking whether a node already contains translated content, the same element gets processed twice.

This double pass can cause flickering, extra network calls, and wasted CPU time, especially on pages that load many language variants.

Why the script runs on both initial mount and route changes

The SeaText snippet loads asynchronously with the async attribute. On the first page load the script executes and translates all marked elements. SPA frameworks like React, Vue, and Angular then handle navigation without a full page reload. The snippet registers a listener for route‑change events (for example popstate or framework‑specific router hooks). Every navigation fires that listener, so the translation routine runs again.

Because the routine does not remember which nodes it has already translated, it processes the same DOM nodes a second time. The result is duplicate work and visible flicker.

How the data‑attribute guard prevents repeated API calls

A simple guard adds a custom data attribute (e.g., data-seatext-translated="true") to an element after the first successful translation. Before calling the SeaText API the guard checks for that attribute. If it exists, the function returns early and skips the network request.

function translateNode(node) {
  if (node.dataset.seatextTranslated) return; // guard
  // call SeaText translation API
  // ...
  node.dataset.seatextTranslated = "true";
}

This pattern turns an O(n) repeated operation into an O(1) check per element.

Framework‑specific route‑change patterns

React

In React you typically wrap the translation call in a useEffect that depends on the router location. The effect runs on mount and on every location change.

useEffect(() => {
  document.querySelectorAll('[data-seatext]').forEach(translateNode);
}, [location]);

Vue

Vue Router provides navigation guards. You can call the translation routine in afterEach or in a global mixin that runs after each route resolution.

router.afterEach(() => {
  document.querySelectorAll('[data-seatext]').forEach(translateNode);
});

Angular

Angular’s NavigationEnd event from the Router is the standard hook. Subscribe to it and run the translation function.

router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe(() => {
  document.querySelectorAll('[data-seatext]').forEach(translateNode);
});

Step‑by‑step diagnostic checklist

  1. Load the page. Open DevTools Console and Network tabs. You should see the SeaText script load once (async). No translation requests yet.
  2. Observe initial translation. After the script runs, each marked element gets a data-seatext-translated="true" attribute. Network tab shows one batch of translation requests.
  3. Navigate to another route. Click a link that triggers client‑side routing. The route‑change listener fires.
  4. Check for duplicate calls. If the guard works, no new translation requests appear for already‑translated nodes. Console shows the guard skipping those nodes. If the guard is missing, you see a second batch of identical requests.
  5. Verify attribute persistence. Inspect a translated element. The attribute should remain after navigation. If it disappears, a component unmount/remount may have reset the DOM node.

Before guard: Network tab shows two identical POST requests to the translation endpoint for the same element. After guard: Only the first request appears; the second navigation logs a console message “skip translated node”.

Guard pattern to prevent double translation

Insert the guard before any call to the SeaText translation API. The pattern works for any SPA framework because it relies only on the DOM attribute.

function translateNode(node) {
  if (node.dataset.seatextTranslated) return;
  // call SeaText translation API
  fetch('/api/translate', { ... })
    .then(res => res.json())
    .then(data => {
      node.textContent = data.translated;
      node.dataset.seatextTranslated = "true";
    });
}

Hook this function into your framework’s route‑change hook as shown in the framework‑specific section.

Trade‑offs and limitations

  • Dynamic content updates. If new elements are added to the page after the initial translation (e.g., via an AJAX call), they lack the guard attribute and will be translated. That is desired. However, if existing translated elements are replaced by new DOM nodes (e.g., a component re‑renders with new inner HTML), the guard attribute is lost and the new nodes will be translated again.
  • Resetting the attribute. When you intentionally want a re‑translation (for example, the user changes language), you must remove the attribute from the relevant nodes before calling the translation routine again.
  • Component unmount/remount. In React, if a component unmounts and later mounts again, its DOM nodes are destroyed and recreated. The new nodes have no guard attribute, so they will be translated again. This is correct behavior but can cause a brief flash if the translation is slow.
  • Guard is not a substitute for duplicate route listeners. If your code registers the route‑change listener multiple times, the translation routine will run multiple times per navigation. The guard prevents duplicate API calls, but the extra JavaScript execution still costs CPU. Ensure you register the listener once.

Practical implementation steps

  • Identify the entry point of your SPA (usually index.html or the main JS/TS file).
  • Insert the SeaText snippet inside the <body> tag as described in the documentation.
  • Wrap the translation call with the guard check shown above.
  • Integrate the guard‑wrapped function into your framework’s route‑change hook (React useEffect, Vue afterEach, Angular NavigationEnd).
  • Test by navigating between routes and watching the console – you should see the translation function fire only once per element.

Common pitfalls

  • Forgetting to set the data attribute after the first translation.
  • Placing the guard inside a component that unmounts and remounts, which resets the attribute.
  • Running the translation script before the SPA’s router is fully initialized, causing premature duplicate calls.
  • Registering the route‑change listener more than once.

Key facts

FactDetail
Async loadingThe snippet includes the async attribute for the script tag, ensuring the SeaText AI script loads asynchronously.
Local storage usageThe script stores an ID in local storage, which the SPA must be allowed to access.
Integration pointIdentify the entry point (e.g., index.html) and insert the SeaText AI snippet within the <body> tag.
SPA compatibilityIntegrating the SeaText AI JavaScript snippet into your SPA involves embedding the provided code into your project.

FAQ

  • Why does the duplicate happen only on some pages? Pages that trigger a client‑side navigation without a guard will re‑run the translation routine, while static pages that never change routes won’t.
  • How can I verify the guard is working? Open the browser console and look for the custom data attribute on translated elements; you should see it after the first navigation and no further translation calls for the same node.
  • Does disabling async loading help? No. Async loading only affects script download timing; the duplicate issue is about when the translation function is invoked.
  • Will the guard affect SEO? No. The guard only prevents redundant client‑side work; the final translated content is still rendered for crawlers that execute JavaScript.
  • What if I change the language at runtime? Remove the data-seatext-translated attribute from the elements you want to re‑translate, then trigger the translation routine again.

Further reading and comparison sources

These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.

Further reading and comparison sources

These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.

Learn more

Visit the website for more information.