Translation happens to the stream, not to the screen
A web session is recorded as an rrweb event stream: a full snapshot of the DOM, then a sequence of mutations. The obvious way to translate a replay is to let the player rebuild the page and then rewrite the text nodes it finds. That is also the wrong way. Every mutation, every seek and every restart rebuilds part of the page, so the rewrite has to be re-applied each time, racing the player while it works.
AnyReplay translates the events instead. Before the player sees anything, the strings are extracted from the snapshot and from every mutation, translated, and substituted back. The player then rebuilds an already-translated page, and every one of those problems disappears. Both halves of that — extraction and substitution — are pure functions, which is what makes the most delicate part of the product testable without a browser.
Context travels with every string
Interface strings are not prose. “Save” on a button and “Save” in a sentence want different words in many languages, and a label has to stay short enough to fit the control it sits in. So every extracted string carries a context: heading, button, label, link, input, attribute or text. Attributes a reviewer would want to read — placeholder, title, alt, aria-label — are translated; the contents of script, style, code, pre and svg are never touched.
Each string is sent to the model with an index, and each translation must carry that index back. Matching by position looks simpler and is a trap: a model asked for forty strings occasionally returns thirty-nine, and without an index every translation after the gap lands on the wrong string. With one, a dropped item costs exactly one string, which simply stays in its original language — what the reviewer was looking at anyway.
A glossary for the words that must not change
Product names, feature names, and the handful of terms your company spells its own way survive translation because you tell the model they must. An organisation-wide glossary maps a term to its required translation — or to itself, meaning “leave this exactly as it is” — and takes precedence over the model’s own judgement. The glossary is included on the Team, Business and Enterprise plans; see pricing.
Translated once, cached by content
Strings are hashed and cached per organisation, so a string translated once in one project is reused by every other project in the same organisation. A button that appears in ten thousand sessions is translated once, and the next reviewer who opens any of those sessions in the same language gets it instantly. Translation is requested when someone presses play in a language other than the recording’s own; nothing is translated speculatively, and nothing leaves your organisation’s scope.
What about masked fields?
A masked input is replaced with asterisks on the user’s device or in their browser, before the recording is sent. By the time the stream reaches the translation step there is no value to translate — which is the point. The privacy page sets out what is masked, what is not, and what is never recorded.
Mobile sessions translate the same way
A React Native recording is already a tree of element types and text, not a set of screenshots, so it goes through the same pipeline: extract, translate with context, substitute, replay. This is the reason the mobile recorder refuses to capture pixels. A screenshot of a Turkish screen is a screenshot forever; a recorded string is one translation away from any language.
Five dashboard languages, five replay languages
The dashboard itself ships in English, Turkish, German, Spanish and French, with a test that fails the build if any locale drifts out of parity with the source strings. Replay translation is offered into those same five languages today, with more added on request. For side-by-side comparisons with other tools, see compare; for the engineering notes behind this page, the blog.
