Author & lead developer — BSc thesis, ELTE Faculty of Informatics
A Chrome extension and a web app that turn anything you read into a language lesson.
Select a word or a sentence on any page and Lasu returns natural translations in up to four of 72 languages at once — each with the meaning, the part of speech, a CEFR level, synonyms, an example sentence and what it means, a romanisation where the script needs one, and a note on register. The card renders in place, over the page you were already reading. The web app holds everything the extension does not: the account, a searchable history grouped by day, a Practice Hub built from the words you actually saved, learning stats, and a community feed with streaks and leaderboards. Both halves speak twelve languages themselves, right-to-left included.
The thing worth looking at is the streaming. A translation used to be a request that returned a finished object, which meant the reader watched a spinner for the length of the slowest language. Now `/api/translate` writes Server-Sent Events as the model produces them, and a partial-JSON parser turns the growing, still-invalid payload into whatever fields have closed so far. Each target language gets its row immediately; the row fills in as its translation is written, with a caret on the one being typed and a pulse on the ones still queued. The view paints once per animation frame rather than once per token. Plain JSON is still the default response, because the extension pins that shape.
That parser exists twice on purpose — `src/lib/partialTranslation.ts` in the app and `partial.js` in the extension — against one wire format, so the card and the web result are never reading the model differently. The endpoint takes an image the same way: right-click a photo, a screenshot or a menu and it goes to the same route as a data URI, is read there, and lands in the same card.
An earlier version of this routed every request through a self-hosted Fastify gateway with paid plans, quotas and Stripe behind it. It worked, and it is still on a branch, but it bought latency and an extra thing to keep alive for a product whose whole promise is that the answer is already there when you look up. Calling the official API directly and streaming it was the better trade, so the gateway came out.
The extension holds to three rules. The service worker is the only thing that fetches, so there is one device id, one cache and one error vocabulary across every surface. The in-page UI lives in a shadow root anchored to a live Range, so nothing of the page's CSS reaches the card and the anchoring survives scroll containers, transforms and iframes. And a translation is a port, not a message — `chrome.runtime.sendMessage` cannot carry tokens as they arrive, so each lookup opens a named port to the worker; closing it aborts the fetch behind it.