jedarlington
Blog

Pawdiary in 30 languages, without a translation library

November 2, 2026

Last week Pawdiary went from English only to 30 languages. That sounds grander than it felt. Most of it was a framework I built in a day, a lot of files, and a few awkward corners that only show up when you look at a language you can’t read.

No library, on purpose

The translation code is a small file in the repo, not a package. t('book.choose_time') looks the key up in the current language and falls back to English if it’s missing. English is the source of truth. The other languages load only when someone picks them, so a French-speaking client downloads French and nothing else.

It took a few days of commits to get there. On 23 October the framework went in with the client booking pages in 11 languages. A few commits later the other nine languages for client pages arrived, including Japanese, Chinese and Korean, and dates and money started following the language.

Filling the gaps with DeepL

I can’t write Arabic or Thai, so the first pass came from a small DeepL script that fills in whatever keys are missing. It isn’t a replacement for a native speaker. It does mean a groomer in Turkey or Poland isn’t looking at an English wall on day one, and I can improve the wording later.

Then came the slow part, which was putting every business-facing screen behind t(). Between 27 and 29 October I merged languages one at a time, from German and Portuguese through to Arabic, Hindi and Thai. Each was its own commit, so if one language broke something it was easy to find.

Things that bit me

  • Plurals. English has “one” and “other”. Polish, Russian and Arabic need more forms. The app uses the browser’s plural rules, and my translation check had to stop assuming everything was a one/other pair.
  • Right to left. Arabic and Hebrew set the page direction to right to left. Layouts you thought were fine start to look odd.
  • Clinic wording. Treatdiary shares the code. Where a clinic should see different text, there’s a second key ending _clinic, picked in the component.
  • Legal and clinical text. Terms, privacy and consent forms are deliberately not machine translated. They need a professional translator before anyone relies on them.

Money and countries

Language was only half of it. A groomer in Japan shouldn’t be sent a pounds-only checkout. The last commit of the week made card payments use the business’s own country and currency through Stripe Connect where Stripe supports it. Amounts use the currency’s smallest unit, so yen and other whole-unit currencies are charged correctly. Plan checkout shows the price in the business’s own currency, and falls back to pounds if Stripe refuses.

What I’d do differently

I’d write the plural check before the first translation, not after the fifteenth. I’d also keep the keys in smaller files from the start. Splitting them into groups like account, clients and settings later was tidy but avoidable.

The honest caveat is that I haven’t had most of these checked by native speakers yet. If you spot a clumsy translation in Pawdiary, I’d like to hear about it.