Things we learned translating our SAAS product

Alex Ghiculescu
Tanda Product Team
Published in
9 min readJun 5, 2017

Since the start of the year, we’ve been making our core product, Tanda, work in multiple languages and regions. This process is sometimes referred to as internationalisation, or i18n (because there’s 18 letters between the first i and the last n), but it really covers a lot more than that. To be honest I’m not sure exactly what to call it.

We still have a way to go, but we have learned quite a few lessons along the way that I think are worth talking about.

Background

Before all this, we had a B2B SAAS product that was only available in English, and had focused mainly on our home country of Australia. This was because we specialised in payroll automation, which is a highly regulated domain where legislation changes frequently — so we started off with the country we knew best.

About a year ago we made the decision to seriously expand our focus to other countries and markets. This touched a lot of areas of the business — we started flying a lot more, set up 24/5 operations, and had to unlearn a few peculiarities of Australia that don’t exist in most other places. My challenge was to make sure the product kept up and was just as useful and competitive everywhere else in the world as it is here.

How i18n works

When I started building Tanda 5 years ago, I chose to use a framework for web development called Ruby on Rails. When building something using Rails, the easiest way to display content on a web page is to use a templating language called HAML. Here’s an example of what it looks like, from https://www.haml-converter.com/

Text goes in. HTML comes out, browsers display HTML. There’s a few special characters that HAML detects and turns into HTML tags, class names, and so on.

The problem with this is that the text is hard coded in English. If you only speak French, too bad. The solution is to not put the text directly into the HAML. Instead, you should write your HAML like this:

Here, title and content are translation keys. Then, in a separate file, you would store your translation values. What’s cool about that is that you can have multiple different files, each of which contain the same keys but values for a different language.

Then, when you visit a page, we check what language we should show you the page in, grab the corresponding set of values, and inject them into the HAML so that the correct HTML gets output. You can decide what language to use based on a variety of factors. We decided to add a database column for every user where we decide what locale to use for them — more on how that works later.

How translation works

So in short, text in a certain language goes in, and HTML comes out. But in the process of going from the first screenshot to the last, we came across a lot of technical and process challenges that always come up when you go from doing something the easy way, to doing it the right way.

Now that we have this stuff figured out, actually doing i18n doesn’t take much longer than just putting the text directly into the template, and we know we are going to need to do it anyway — so we go i18n-first on all new work that we do. That said, I don’t regret not doing this from day 1 at Tanda. When we were first starting the business, we had much bigger problems to worry about than how it would look to someone in France — problems like getting anyone in Australia to look at it — and i18n didn’t make solving those problems any easier.

There’s one aspect of i18n that I haven’t touched so far, and that’s actually translating stuff to another language. Initially I was worried here because we didn’t have anyone who was fluent in another language. Luckily, our approach to this was driven by what the business needed, and I think it worked out really well as a result. Two of our directors had decided to focus exclusively on the North American market, and had come across a whole bunch of stuff that was lost in translation. Turns out, where Australians say “roster”, Americans say “schedule”. “Leave” becomes “time off”. “Fortnight” is “bi-weekly”. Dates are formatted wrong. It was a different language — but even better, it was one that we could still speak! So the first language we “translated” to was American English — locale code en-us.

We chose a site called Translation.io as our translation backend. This is a fantastic product and I highly recommend it:

The first column is the original text, which our developers write. The second column is the translation. On the right panel, you take what’s in the gray box, and write a translated version in the white box. You can also edit what’s in the gray box if you want to change the original English version. If you’re lazy, you just use the suggestions in the top right, which are based on past translations and Google Translate suggestions. Translation.io also provides a Rails library which you use to keep your codebase in sync with this text.

With Translation.io set up, we were able to start putting English text in, and have the guys in the US localise whatever they needed.

The i18n process for developers

In my opinion, to change any process you need to do 3 things:

  1. Explain the benefits of the new system and why it’s worth the pain of learning new habits and unlearning ingrained ones.
  2. Give someone the mandate to constantly remind people to use the new process, and help them when they get stuck.
  3. Come up with a mascot, tagline, catchphrase, or some other way to make the change memorable and fun.

Step 1 was easy enough. The business benefits of having a product that worked in lots of locales are pretty obvious. The technical challenges make it fun and worthwhile.

For step 2, I took ownership and self-designated to be the annoying person — but once other people got into the habit of doing i18n properly they tended to help each other out (with new tooling, or just with advice and troubleshooting) which made everything much easier and more friendly. Special thanks go to both of the Daves, two of our developers (or Dave-elopers) who led from the front here.

For step 3, we were inspired by Pitbull.

But two mascots are better than one, so we also gave Josh — who led the US project from the non-tech side — a new nickname.

I wrote a short document that explained what i18n was and how you should structure your code, then presented it to the team. Then I asked everyone to volunteer to convert different areas of the codebase into the proper i18n structure. I thought we’d all put aside fixed time to just go through and convert pages. This didn’t really happen, except for a few dedicated efforts for particular areas (eg. converting all the site navigation, or page titles).

What did happen (after some gentle reminders) is that everyone started to use i18n when writing new code, and in the process of doing so, would also convert stuff that was nearby. So if you were fixing a bug on a particular page, you’d also convert to i18n any code that was near where the bug lived.

As a result, new features from the past few months, as well as code that gets touched a lot (which usually maps to features that get used a lot) have been converted, while older and more stable stuff hasn’t yet. For example, thanks to a combination of Google Translate and what I remember from high school, rostering is almost fully translated to French now — and could thus be translated to any other language without code changes.

This isn’t perfect but it has been a very good starting point — it was definitely enough for US English. Ask me again in 12 months what we did to get to 100% coverage, which we’ll need to do to properly cover non-English languages.

Technical challenges

Ruby on Rails is really good at i18n — not something I knew when I picked it. The entire framework is written i18n-first — all the built in names of days and months, error messages, and so forth are automatically translated — and you can use the same structure in your code. The Ruby on Rails i18n guide is quite good.

We did come across a few areas where we needed to build on it though. The first was around namespacing our translations. Translation.io has some really good tools that let you drill down to a particular translation key by splitting it up with a directory-style UI (it splits the key on “.”, each word becomes a new level). The problem with this is that by default Rails splits translation keys based on the controller name. We have over 100 controllers — I didn’t want over to make our translators filter through so many top level directories. To work around this, I monkey patched the default Rails behaviour to add a prefix (“views.”) to all our keys:

This came in really handy when we wanted to make translation available on the client side. We chose the i18n-js library because of its asset pipeline support. Because all our server side translation keys were under a single prefix, we just made another prefix (“js.”) and put all our client side translations under it. This way we could control which translations were sent to the client — minimising file size, and avoiding unintended side effects! This is our i18n-js config:

As well as exporting only a specific key prefix, we also generate a separate file for each locale which contains only that locale’s text. Then at runtime, we render a <script> tag just for the appropriate locale based on the current user.

Another Rails enhancement we made makes it easier to look up tooltips on form elements from i18n. We use tooltips a lot (more than placeholders, which Rails supports natively) so this made our code much more succinct.

Once we got into the habit of using i18n, we wanted to use it on other codebases that weren’t Rails backed. Unfortunately, the Translation.io gem is quite tied to Rails. We looked into removing the dependency, but given that these other codebases don’t use Ruby either (they’re JS apps — generally React — that interact via our public API) we ended up just writing our own Translation.io integration library in Javascript.

New opportunities created by i18n

Once you think of your text on a page as being keys and values, you realise other things you can do with this flexibility. For example, I’m now thinking about giving customers the ability to customise their own text so they can add domain specific wording where appropriate. I’m also thinking about making different locales for various industries, again so we can use domain specific wording that better suits what they expect to see. For example, Locations & Teams could become “Stores & Teams” for retailers.

Both of these ideas have downsides — eg. our help site would no longer match what’s in the app, and it would probably make support a little bit harder — but they seem like big user experience wins. So we just need to figure out if the trade offs are worth it, and what the process looks like. But it’s cool to think that we can do this sort of stuff now.

Conclusion

Translating our product has been a much bigger — and more interesting — problem than I thought it would be. The biggest thing I learned is that translation is a mindset, and it’s never finished. Unless you plan for your software to never get used, you’re always going to be adding or changing text so it makes sense to always approach it with the “how will I make sure this works everywhere in the world” mindset.

The challenge for the next 6 months is moving to 100% i18n coverage, and then staying there as we add new functionality or make changes. I’ll be sure to write about what we learn once we figure that out!

--

--