We shipped an embeddable version of our calculator in eleven languages last week. The translation
cost was zero, and not because we used machine translation. It was zero because the strings already
existed and we had never noticed.
This is a short post about a boring realisation: if you localise an app properly, the embeddable
version of that app is usually already localised, and the only thing standing between you and
eleven markets is where you read the locale from.
The setup
The product is a CPU and GPU bottleneck calculator. A while back we translated it into ten
languages beyond English, which meant building the usual things: a dictionary per locale, a route
per locale, and a rule that the URL slug is a keyword in that language rather than a transliteration
of the English one. Somebody searching calculadora cuello de botella should see that phrase in
the path, because on a head term the URL is part of the signal.
Separately, we had an embeddable widget. An iframe, deliberately chrome-less, so other sites could
drop the calculator into an article. It was English only.
The instinct was to treat "localise the widget" as a project. Estimate the strings, get them
translated, wire up a language switcher.
The realisation
Every string the widget renders was already in the dictionaries.
Of course it was. The widget is the same calculator. It shows a CPU label, a GPU label, two search
placeholders, a resolution control, and a verdict line that says balanced, CPU limited or GPU
limited. All of that had been translated months earlier for the full-page version. The widget was
English purely because nothing ever told it otherwise.
The actual work was one decision: where does an iframe read its locale from?
On the main site, locale comes from the path - /es/..., /de/.... An iframe embedded on somebody
else's page has no path of ours to read. It has a query string, and that is it.
/embed?cpu=<id>&gpu=<id>&res=1440p&lang=de
So the locale is read client-side from the query string, and the host page chooses the language by
choosing the URL. That is the entire feature.
The bug that was hiding underneath
One line spoiled it, and it is the kind of thing that only shows up when you localise:
// renders "CPU LIMITED" in every language
verdict = result.type.toUpperCase()
The verdict was being built by upper-casing an internal enum value. In English that reads as a
sentence and nobody questions it. In German it reads as an English word sitting in the middle of a
German widget.
The fix is obvious once seen - map the enum to a dictionary key rather than rendering the enum -
but the lesson generalises: any string you construct from a variable rather than look up is a
string you have not translated. Grep for toUpperCase, capitalize, and template literals that
interpolate an internal value into user-visible text. That is where untranslated strings hide, not
in the JSX where they are easy to see.
The part that actually mattered commercially
An embeddable widget is a distribution mechanism. Somebody puts it on their page, and the
attribution link under it points back at you.
That link has to sit outside the iframe. Search engines do not pass equity through an iframe,
so a widget with the credit inside its own frame gives you traffic and nothing else. Ours is a
paragraph in the host page's own HTML, generated alongside the embed code:
<iframe src="https://bottleneckpc.com/embed?lang=de" ...></iframe>
<p><a href="https://bottleneckpc.com/de/flaschenhals-rechner">Flaschenhals-Rechner</a>
by <a href="https://bottleneckpc.com">BottleneckPC</a></p>
Note the anchor text and target. For a German host we link the German page with its German name,
not the English one. A reader clicking through lands somewhere that makes sense to them, which is
the difference between a link that gets kept and a link that gets removed six months later.
One more thing, and it cost nothing
While testing on other people's pages it became obvious that the widget was loading our analytics
and our ad partner's script onto their site. That is a bad thing to ask of anyone, and an
impossible thing to ask of a European publisher with a privacy policy.
The root layout now path-guards every third-party script, so the embed route loads none of them.
The verification is simple enough to be worth doing rather than assuming: open the widget in a real
browser and count the requests that leave your own domain. Ours is zero.
If you ship an embeddable anything, do that check. The answer is rarely what you assume.
We build BottleneckPC, a free CPU and GPU bottleneck calculator. The
widget is free to embed in any of the
eleven languages, and the scoring engine is open source.
Top comments (0)