Common internationalization mistakes in Flutter

Roman Ismagilov
6 min readMay 21, 2024

--

Achieving flawless internationalization involves more than just translation. It requires careful consideration of linguistic nuances and regional preferences. In this article, we’ll delve into some common mistakes encountered during Flutter app localization and explore effective strategies to overcome them.

1. Using different string entries to make a single sentence by concatenating.

Let’s have this sentence as an example:

I have read and agree with terms and conditions, privacy policy

Highlighted parts should have a different text style and be clickable.

Very often people split it to several entries and then concatenate TextSpans. When this solution works for English and might event work for some other languages in the app, problems start when working with languages that have grammar cases or a different word order.

Let’s break it down and see where do the problems come from.

I have read and agree with + terms and conditions+, +privacy policy

In Estonian the same sentence would sound like this:

Olen lugenud ja nõustun +tingimustega+, +privaatsuspoliitikaga

Word order is the same, nothing bad should happen? But it is so tempting to reuse the terms and conditions entry as a title for the screen with the same name. Would perfectly work in English, but the in Estonian we would get a title meaning “With terms and conditions” instead of “Terms and conditions” because of -ga ending. Another problem is that it is extremely easy to lose context of the whole sentence when translating these entries one by one and using a wrong grammar case. Not to mention lost whitespaces.

Some languages might have a different word order, for example, German:

Ich habe die Bedingungen und Datenschutzrichtlinie gelesen und akzeptiere sie

The initial solution would not support this, because there’s some text after the Datenschutzrichtlinie (privacy policy) word and then we have to add an additional TextSpan after it. Sounds like a headache?

Solution: use tagged localization entries. For instance, styled_text package allows for adding custom tags to string and then handling them in the code:

"agreeWithTerms": "I have read and agree with <a action='OPEN_TC'>terms and conditions</a>, <a action='OPEN_PP'>privacy policy</a>"

I recommend using Maatteogekko’s fork of this library due to this issue. If you know a better solution, please, let me know in the comments.

dependencies:  
styled_text:
git:
ref: bd403bd6c7c7df422b8d13e14b995662818fd9a9
url: https://github.com/Maatteogekko/styled_text_package.git
path: styled_text

And then in the code it would look like that:

      StyledText(
text: t.agreeWithTerms,
style: Theme.of(context).textTheme.bodyMedium,
tags: {
'a': StyledTextActionTag(
(String? text, Map<String?, String?> attrs) {
final action = attrs['action'];
switch (action) {
case 'OPEN_TC':
_openTC();
case 'OPEN_PP':
_openPP();
}
},
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).primaryColor,
decoration: TextDecoration.underline),
)
},
)

As a result we’re getting this:

2. Ignoring plurals or writing some custom logic to handle it

Let’s have this sentence as an example:

5 users online

In case there’s only one user, we should show “1 user online”. Why can’t we just add a condition in the code that if (n==1), then use another string? Or go even simpler with a “N user(s)” string. Would perfectly work in case of English.

But let’s have a look on Russian:

1 пользователь онлайн

2 пользователя онлайн

5 пользователей онлайн

21 пользователь онлайн

And it’s even more complex in Arabic.

Luckily for us, there’s a standard for pluralization. You can read about it more on the Unicode website. And this article shows what plural categories are present in some popular languages.

Long story short, we should provide according plurals categories for every language that we have in the app.

    "usersOnline": {
"one": "$n user online",
"other": "$n users online"
}
    "usersOnline": {
"one": "$n пользователь онлайн",
"few": "$n пользователя онлайн",
"many": "$n пользователей онлайн",
"other": "$n пользователя онлайн"
}

I’m using slang package to implement pluralization:

Text(t.usersOnline(n: 3)),

As a result we are getting grammatically correct plural forms. Online editors, such as POEditor also support this.

3. Manually formatting date and time, hardcoding names of months, days of week

That’s a very common mistake to make. Different regions have different formats of dates. There are different rules for shortening day of weeks, etc.

For instance, in Finland, hours and minutes are separated with a dot. In Estonian, shortened days of week are represented with 1 letter. Not to mention that in the US month comes at the first place in dates. Seems pretty impossible to have all this in mind when working with date and time. Luckily, everything is standardised again. Let’s use intl package.

By default, DateFormat uses the locale that is used in the app, but for the demo purposes we are providing some specific ones to see the output. TimeOfDay takes a locale from the context.

    final date = DateTime.now();
format(String locale) => "$locale: ${DateFormat.yMMMEd(locale).format(date)}";
print(format("en_US"));
print(format("en_GB"));
print(format("et_EE"));
print(format("fi_FI"));
print(format("ar_QA"));
print(TimeOfDay.now().format(context));

The output:

flutter: en_US: Tue, May 21, 2024
flutter: en_GB: Tue, 21 May 2024
flutter: et_EE: T, 21. mai 2024
flutter: fi_FI: ti 21. toukok. 2024
flutter: ar_QA: الثلاثاء، ٢١ مايو ٢٠٢٤
flutter: 13:24

4. Concatenating currency and price strings

This might seems like a minor issue, but imagine scaling your app to the US market while having something like this in your code:

String formatCurrency(String currency, String amount) => amount + currency;

While it could have been an absolutely valid formatting in most of the European countries, users in the US will see prices formatted in a wrong way: 1000$, while it should be $1000. Personally, I would not trust my bank card details to an app, that shows the price in a wrong way. What if there are some bugs and the final price would be different?

Moreover, in some countries it is common to use commas as separators, in other counties they use dots.

Let’s have a look, how to work with currencies using intl package:

print(NumberFormat.simpleCurrency(locale: 'en_US', name: 'USD').format(199.99));
print(NumberFormat.simpleCurrency(locale: 'en_GB', name: 'GBP').format(199.99));
print(NumberFormat.simpleCurrency(locale: 'es_ES', name: 'EUR').format(199.99));

NumberFormat.simpleCurrency uses the app’s locale by default, here we are passing some specific locales for demo purposes. The output is the following:

flutter: $199.99
flutter: £199.99
flutter: 199,99 €

Furthermore, there are short and explicit formats. The most common use case is to show short format in the lists of good being sold or in the shopping cart. And the explicit format should be used during the checkout. You can read about it more in this article by Shopify.

Why is that important? Imagine this situation: a Canadian user opens the app and sees prices with dollar signs. User want to be sure, that he is actually paying with Canadian dollars, not with US dollars. That could be done using a simple function:

  String explicitFormat(String currencyCode, String? locale, num amount) {
final shortFormat = NumberFormat.simpleCurrency(locale: locale, name: currencyCode).format(amount);

return "$shortFormat $currencyCode";
}

5. Using fonts that support only Latin script

It won’t look nice if some parts of the UI would be displayed using unexpected font when the content is written in a different script. That might happen if there’s a limited amount of characters that are supported by the font. That could be either:

  • Cyrillic script (привет)
  • Greek script (γεια)
  • Extended Latin alphabet (č, ü, å, ß, œ…)
  • Other scripts
Even if some Extended Latin letters are available in the font, some might not

Solution here is to check that the font you’re going to use supports characters used in a country you are planning to launch your app in. For example, in Google fonts, you can check that in Glyphs tab.

Hope you’ve found this article useful.

I haven’t covered the RTL/LTR case and handling different measurement systems, it’s worth writing separate articles, make sure to subscribe to get the updates.

--

--

Roman Ismagilov

Covering some non-obvious nuances of Flutter development in my articles