Handling CSS font stacks for multi-language websites

Liam Tarpey
Busuu Tech
Published in
9 min readApr 26, 2018

--

An example of a Busuu exercise teaching Spanish with a Japanese interface.

I’m Liam, a Front-end engineer at Busuu, based in London, UK. We specialise in language learning and our goal is to break down barriers and empower everyone to learn a new language. In the front end team, our primary focus is our web app (our main learning platform, Angular — soon to be React) but we also build and maintain a number of other projects — a marketing platform powered by a Grav CMS, an enterprise app and a few internal tools for our education and customer services teams.

I’d like to talk through how we deal with fonts for multi language sites, a challenge that is both close to our hearts and a big challenge for us!

1. Why we addressed this

Naturally, being a language learning company, we need to support a varied number of interface languages, so we required a stable and extendable font architecture that would allow us to add new languages safely, reliably and quickly.

Our brand font is Museo and our initial font stack was very simple — if not a little too simple:

font-family: "Museo", Arial, sans-serif;

Museo does not render properly in a number of non-latin languages as it does not support certain alphabets, and we were ending up with varying results between certain interface languages such as:

  • Spanish: renders every character in Museo
  • Japanese: renders (sometimes a mix of) whatever fallback system font(s) it feels like, except for numbers which render in Museo
  • Polish: renders Museo for the most part but renders a fallback system font which gets applied to each individual letter that Museo doesn’t support.

Obviously we had more issues with different languages, but for the sake of this example, let’s take our Polish case.
Here’s an example of what was being rendered in our app:

The yellow boxes highlight the letters that are being rendered in Helvetica (on Mac OSX)

As you can see above, most of this sentence is rendered using our Museo font but the three characters with the yellow highlight are actually being rendered in Helvetica.

Looking at our dev tools (I’m using Chrome), you can see in our computed properties that the rendered fonts for this particular HTML element are both Museo & Helvetica.

Deleting these 3 letters would result in a sole rendering of Museo.

We can see here that Museo is loaded from the network but Helvetica is loaded locally.

It’s the locally part which is scary. Without defining a fallback font that we know supports all Polish characters, we can end up with either any local font being rendered or a mix of fonts that we have no control over.

The issue was more dramatic with languages like Russian or Turkish and when using a different machine, for example different Windows machines that have different fallback fonts varying by different Windows versions.

Another problem we had was that we needed to render different fonts in different parts of the page, e.g., your interface language could be Italian whilst you’re taking a Chinese course, but I’ll touch on this point later.

As developers, we want to give our users the best possible experience at all times, regardless of the language they choose to experience the site in so we started researching the best way to fix this problem.

2. First impressions & ideas

Our initial thought was probably the most obvious one — let’s rebrand (at least in terms of font) and choose a font that supports all 15 interface languages that we support.

Problems with this approach:

  • Extendibility. How do we know what languages we need to support? Right now we have our list of 15 languages but forward thinking leaves us with the question of what happens if we have to support 30, 60 or even 90 languages in a year or two?
  • Expense. Our designers took a lot of time researching new font possibilities that could cater at least for what we needed right now. Nothing was particularly suitable and the best solution that we could find (that supported nearly every language we had) cost in excess of $4,000.00 for the all the individual licenses.
  • Weight. Yes, the fonts were split by alphabets and we could inject them as needed based on the user’s interface language, but some research led us to the conclusion that some fonts are just too heavy if we need to support all character sets (which we do).

A Japanese font that supports Kanji & Kana character sets can be in excess of 35mb due to the sheer amount of characters it needs to include.

After some more investigation and research on how other multi-language sites approach this, such as Facebook and Airbnb, we decided to go with the choice that ensured that every user would have the best possible experience on the site regardless of their interface language; and which seemed to be the common approach; define an individual font stack for each language.

3. Choosing the font stacks

We first wrote out a list of our current 15 languages, and narrowed it down to the languages that couldn’t support Museo. All of the ones that could will be classed as font-face-lt (latin) rather than having a separate class for each one.

There’s really no right or wrong here. It was all based on research, a mix of looking at what other sites have chosen and reading a lot of amazing articles from around the web. Eventually we built up a stack for each language that we thought matched our brand the closest on all devices and browsers. All of the fonts in the stacks (except Museo) are system fonts — meaning they’re pre-installed on your machine.

Obviously, using a Mac you might have one font but your friend who’s using a Windows laptop or a random phone might have a different one. We had to play around with the order these lived in the stack to ensure that we didn’t get any font mixing.

Here’s our stacks at the time of writing:

Latin:
font-family: 'Museo', 'Helvetica Neue', 'Helvetica', Arial, sans-serif';

Polish / Turkish / Vietnamese:
font-family: 'Helvetica Neue', 'Helvetica', Arial, sans-serif’;

Arabic:
font-family: 'Geeza Pro', 'Helvetica Neue', 'Helvetica', Arial, sans-serif';

Japanese:
font-family: 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', 'Osaka', 'メイリオ', 'Meiryo', 'MS Pゴシック', 'MS PGothic', 'Helvetica Neue', 'Helvetica', Arial, sans-serif';

Korean:
font-family: 'Apple SD Gothic Neo', 'NanumBarunGothic', '맑은 고딕', 'Malgun Gothic', '굴림', 'Gulim', '돋움', 'Dotum', 'Helvetica Neue', 'Helvetica', Arial, sans-serif';

Russian:
font-family: 'Charcoal', 'Geneva', 'Helvetica Neue', 'Helvetica', Arial, sans-serif';

Chinese:
font-family: '华文细黑', 'STXihei', 'PingFang TC', '微软雅黑体', 'Microsoft YaHei New', '微软雅黑', 'Microsoft Yahei', '宋体', 'SimSun', 'Helvetica Neue', 'Helvetica', Arial, sans-serif';

4. Implementing multiple font stacks

We had the following requirements:

  • One reusable component to support all of our customer facing apps.
  • Ability to apply the stack as a global property on our html or body tag
  • Ability to overwrite the global stack with a new stack using a class if needed (to support multiple languages per page)

As we have 3 customer facing apps which all follow the same style guide, we have a core repository that we import into each project as a git submodule.
This allows us to import reusable styles (buttons, forms etc…) and scripts (helpers, classes, reusable components etc…) into each project and ensure that if any of this code is changed in the core that it will get reflected across all of our projects.

We decided to use this approach for the font stacks and create a file in our core repository called _fonts.scss

We’re using Sass as our CSS pre-processor.

First off, we defined our default font stack — think of this as a ‘last resort’ stack when nothing else is applicable.

$default-typo: 'Helvetica Neue, Helvetica, Arial, sans-serif';

Here’s a couple of examples of our font stacks in Latin, Arabic and Russian using SASS placeholder selectors:

%font-face-lt {
font-family: 'Museo', unquote($default-typo);
}
%font-face-ar {
font-family: 'Geeza Pro', unquote($default-typo);
}
%font-face-ru {
font-family: 'Charcoal', 'Geneva', unquote($default-typo);
}

We’d then extend these placeholders to ensure that we render these stacks on the document body, but also generate a class should we need it that has the exact same properties:

.font-face-lt,
.font-face-lt body {
@extend %font-face-lt;
font-weight: 500;
}
.font-face-ar,
.font-face-ar body {
@extend %font-face-ar;
font-weight: 500;
}
.font-face-ru,
.font-face-ru body {
@extend %font-face-ru;
font-weight: 500;
}

The next step was to apply the correct CSS class on the <html> element so that the CSS can be applied on the <body>. For this, we created a small Javascript service called font-loader.service.js to which we pass our user language. What this script does is basically take a language and injects a class into our <html> tag; in this instance font-face-ja as our user wants a Japanese interface.

You would now see:

Our class is appended to our html tag and the stack is being applied as a property on the body

5. Overwriting the global font stack

In our case (and this was one of our considerations before beginning the project) we had to support multiple languages across one page.

We teach a course in the course language but the app interface should be displayed in the language of your preference.

Consider this screenshot from an exercise which includes Spanish and Arabic texts:

‘Hola’ is rendered in Museo but ‘مَرْحَبَاً’ is rendered in Geeza Pro

In this example, our <html> tag has a class of font-face-ar which sets a global Arabic font stack across the entire app.

Now, this case is very specific to Busuu (as a product), but we needed to ensure that the <p> element wrapping ‘Hola’ always gets the font-face-lt class it needs to be rendered in Museo rather than font-face-ar that is applied globally.

For this, we created a directive (our app is in Angular 1.x) that we could attach to any HTML element and override the global stack by applying whichever class we need to this particular element.

Example of HTML element using a directive to apply a different class:
<p font-switcher="course" class="font-face-lt">SOME_STRING</p>

6. Thoughts

In all honesty when I first got assigned this ticket, I thought it’d be a bit mundane and boring; and it definitely was frustrating at first when working through our initial idea of trying to find a font that suited all languages; but it ended up turning into one of the more rewarding projects I’ve done this year.

Knowing that the resources, solutions and discussions for this kind of problem are limited across the web and knowing that users deserve the best possible experience (especially for a paid product!) pushed me to write this article in the hope that it will help others.

I’m sure the font stacks that we chose aren’t perfect and any suggestions on how to improve these are more than welcome!

A few screenshots of the same component rendered in different languages:

Thanks for reading! If you have questions, please leave a comment.
You can also find us on Twitter: @busuuTech and Liam Tarpey

If you’d like to progress in your tech career and you’ve got a love for learning languages (tech or otherwise!), we’ve got lots of open roles at Busuu!

References

Japanese typography on the web — tips and tricks

Chinese Standard Web Fonts: A Guide to CSS Font Family Declarations for Web Design in Simplified Chinese

Fonts supplied with Windows and Mac OS X, by script

The Most Comprehensive Guide to Web Typography in Japanese

http://hayataki-masaharu.jp/web-typography-in-japanese/#.Wm8kNJOFifQ

--

--