Chrome's built-in translator, measured
Chrome 138 and newer ship a translation model you can call from a web page. It runs on the device, so the text never goes to a server. We built an English to Hindi translator on it, and most of what we learned came from running it rather than from reading about it.
Everything below was measured on 16 September 2026 in Chrome 152 on macOS. The API is new and changes, so treat the numbers as a dated reading.
Which languages are there
You ask per pair, and the answer is one of four states.
await Translator.availability({ sourceLanguage: "en", targetLanguage: "hi" })
// "downloadable"English to Hindi, Tamil, Kannada, Marathi, Telugu and Bengali, and Hindi to English, all said downloadable. English to Gujarati and to Malayalam said unavailable, so we offer neither. The other two states are downloading and available, and a page has to show something different for each.
It will not download without a click
Calling Translator.create from code, before the language pack is on the machine, throws:
NotAllowedError: Requires a user gesture when availability is "downloading" or "downloadable".
So the Translate button is the gesture, and the create has to run in that click's handler, not in an effect that fires afterwards. Once the pack is present, the same call succeeds with no click at all. On a fresh browser profile the Hindi pack went from 14% to 74% to ready in about nine seconds. After that, create took 127 ms, a sentence took 6 to 28 ms, and forty translations took 246 ms.
The bug 1,987 tests missed
Our first version kept the translator's method and handed it to the code that walks a document:
const translator = await Translator.create({ ... });
return translator.translate; // detached from its object
// later, on the first call:
// TypeError: Failed to execute 'translate' on 'Translator': Illegal invocationtranslate is a Web IDL method, and it refuses to run without the object it belongs to. So the tool failed on every translation it attempted, while 1,987 tests passed. The tests replaced the translator with a plain function, and a plain function has no object to lose. The fix is one line, (text) => translator.translate(text). The test that now guards it uses a fake that refuses a detached call the way Chrome does, and it fails when the binding is taken out.
It does work offline
We checked rather than trusting the description. On a fresh profile we downloaded the Hindi pack, then cut the network through the DevTools protocol. navigator.onLine read false and a fetch of our own site failed. Translation still returned the same Hindi, and so did a brand new create made after the network was gone. The page itself made zero network requests while translating. The one thing that crosses the network is Chrome fetching the language pack from Google the first time a pair is used, and the text is not part of that request.
How good it is
Twenty ordinary English sentences, translated to Hindi and read by a person. Sixteen were clean. One was ambiguous and one had a grammar slip. Two changed the meaning:
- "It keeps working with the wifi off" came back as "it keeps working with the wifi". The one word that mattered was gone.
- "We are out of milk" was translated word for word as हम दूध से बाहर हैं, "we are outside of milk".
Good enough to understand a document, then. Not good enough to publish one without somebody who reads Hindi checking it, and the page says so next to the result.
Names, numbers and codes held up better than we expected. Ten sentences carrying an email address, a URL, an order code (AB-4471/22), a phone number, a discount code, a price, a time and a version string all came back with every token intact. The only damage was case: onnxruntime-web came back as OnNXRuntime-Web. We had planned code to protect those tokens and did not write it.
Where you break a line changes the translation
The easy way to translate a file is one line at a time. It is also wrong, because text is wrapped in places that mean nothing. Five hard-wrapped paragraphs were translated both ways and then back into English. Four came out the same. The fifth did not:
Most of the staff work from home two days a week. line by line -> "Over-employees work from home two days a week." whole -> "Most of the employees work from home two days a week."
Given "Most" on its own, the model has to guess, and it guessed wrong. The same thing happened in markdown, where a lone "Most" became the Hindi for "extremely", and in subtitles. A two-line cue is one sentence wrapped to fit the screen, and "I told him that most" on its own line came back with the Hindi superlative in place of "most of". So plain text is now sent a paragraph at a time, markdown a block at a time, and subtitles a cue at a time. The cost is that the original line breaks do not survive. A Hindi sentence has its own length, and breaking it where the English broke would put the break in the wrong place anyway.
It breaks subtitle markup three times in eight
We first tested one tagged sentence, saw the tags come back, and wrote down that markup needed no handling. That was wrong. Running a real .srt file through the tool showed it straight away, so we measured eight tagged cues properly:
| went in | came back |
|---|---|
| <i>She never said anything about</i> | opening <i> kept, closing tag dropped |
| <v Speaker> | <v वक्ता>, the speaker's name translated |
| <c.yellow> | <c.येलो>, a CSS class that no longer exists |
| the other five | identical |
The textbook fix is to swap each tag for a placeholder, translate, and swap the tags back. The model does not keep placeholders either. {0} and [[0]] each lost one of two. #0# survived but moved. Private-use characters, U+E000, were deleted in all five cases we tried them.
So we check the result instead. A cue is sent with its tags, which works five times in eight. If the tags that come back are not the tags that went in, the cue is translated again with no tags. A tag that wrapped the whole cue goes back on, because we know where it belongs. A tag around part of a cue is dropped, because Hindi puts the words in a different order and nothing tells us which of them it wrapped. The second call costs about 6 ms, on the cues that need it.
The tools this came out of
Translate text, or a .txt, .md, .srt, .vtt or .csv file, on your own device.
filetity is built by Adarsh Mishra. These readings are from Chrome 152 on 16 September 2026. If the API answers differently on your machine, or a language we list as unavailable turns up, we would like to know: support@filetity.com.