Announcing Rosetta – a rock solid localization tool for Flutter

We are pleased to announce Rosetta, our localization generator library. Just like the namesake – Rosetta Stone – it will offer a guiding hand managing translations between languages in your Flutter application.

announcing rosetta - localization in flutter library

Why?

As the team dove deeper in Flutter development we met some cases when the original code pattern should be improved to reach better code quality. Localization in a Flutter app was one of these cases.

Where did we start? Based on official documentation and a few blog posts (you can check them here and there), we created a basic localization class for our needs.

This class has many advantages:

  • Every text in UI is coming from this class (just imagine Android’s strings.xml)
  • The only parameter for the function is Context object which is accessible in every Widget builder function. So not a big deal to depend on that.
  • With the help of get functions, we have an autocomplete for available texts

So this is our vanilla Translation class with some comments:

Translation

To use our translation class with the rest of the app we have to make a reference to our delegate in MaterialApp just like that:

Main

After that, using localized text in the app takes no effort. For example add “Hello there!” to our Text widget:

HomePage

We had to admit, it’s a pretty cool way to use localization.

But if we watch our code closer, we find some flaws. With every new piece of text, we have to do the following:

  • Add a new constant to the Keys class
  • Add key-text pair to every language in the localization map
  • Write the get function to have a clear access to the value

What’s wrong with all of these?

  • It’s boring to write that boilerplate code.
  • A chance for multiple errors. (For example, not having a key-value pair in a specific language will be only discovered at runtime.)
  • Also the source code becomes terrible after having medium amount of text with multiple languages.

Naturally, we started asking questions to find solutions:

What if we place our texts to JSON files instead of making a huge map in a Translation class?

What if we generate the boiler plate code to save time and avoid nasty errors?

At this point you can ask a simple question:

What is rosetta?

Rosetta is an annotation library which aims to simplify the localization logic of your Flutter apps. It’s built upon a simple pattern and based on the flutter_localizations library. It’s not meant to be used as a standalone library rather, used with a generator.

The generator part is called rosetta_generator. Based on the JSON translation files you provide, it will generate all the boiler-plate code for you.

Library setup

You can set up the library in a few simple steps.

  • You should locate your Flutter projects pubspec.yaml file. Find the dependencies block.

pubspec.yaml – Dependencies

The dependencies block should contain all the above dependencies. The first one is already added for any Flutter project, the second one is the base localization library. And finally the third one is rosetta, which contains the annotations for configuring the generator.

  • Next stop is the “dev_dependencies:” block. If you don’t have one in your pubspec.yaml don’t worry, just declare it after your dependencies block. Here we will declare our generator dependencies. First we declare “build_runner”, which will control our generator instances. After that, we declare rosetta_generator, which will generate code for our annotated classes.

pubspec.yaml – DevDependencies

The last step is to configure the generator task. This should be done in a build.yaml file in the same directory as your pubspec.yaml. If there’s no build.yaml file in the directory, you need to create an empty file. Open the file paste the following:

build.yaml

That’s it. The library is configured, now we need to annotate a little bit to get things working.

Library usage

First, we need to create a class containing two static members. It will be used later for localization, just like how we did earlier.

Next, we need to import the rosetta library, so that we can use the rosetta “@Stone” annotation.

If the IDE says it’s an unknown package, then you should go to pubspec.yaml and call “Packages get” to refresh your dependencies. Once, we imported the library we should annotate our “Translation” class.

You can see a parameter called “path” in the example above. The string constant provided to this parameter should point to a directory where your translation JSON files are stored. They should be named after the languageCode they represent. (like “en.json”) Also keep in mind that these directories should be added as assets in your Flutter project’s pubspec.yaml file.

Before we run the generator the first time, we need to add one more line below the imports of the annotated class’s code.

This is the link between your code and the generated one. We are all set, let’s run the generator!

To start the generator, we need to open a Terminal or use the one provided by Android Studio at the bottom tabs. Please make sure to have the flutter executable on your $PATH, otherwise the next command won’t work. (The Flutter install tutorial has a part where you add the binaries to your PATH.) In the terminal we start build_runner with the below command:

If the command fails and reports some kind of conflict error, you should add the following flag to the command: –delete-conflicting-outputs. This will allow you to override the conflicting generated files. (It won’t harm any project files.) If you want the runner to refresh the generated files every time you edit and save the annotated classes, you should use the watch command:

If everything goes well, you will see a file generated next to the file containing the annotated class. The generator process will generate 3 classes (let’s assume that the annotated class was called Translation as in the example above):

  • _$Keys: Contains all your keys as static fields, this is currently for internal use.
  • _$TranslationDelegate: This is an implementation of LocalizationsDelegate<Translation>, this should be passed to MaterialApp or CupertinoApp as a localization delegate. Also should be passed to the static delegate attribute of your original class.
  • _$TranslationHelper: An abstract class, which is meant to be mixed in to your annotated class. Contains functions to access the localized strings for each key (ex.: String get emptyList => _translate(_$Keys.emptyList);).

If you apply the generated classes to your original, you will end up with something like this:

That’s it! From now on, if you add key-value translation pairs into you JSON, the generator will automatically generate all the functions for you, so you can access them easily.

Final thoughts

The above described library-generator pair can be found on pub:

We hope you will found them just as useful as we did. We have some future plans for extending the functionality of the library like validation and parametrized translations. Stay tuned!

We had a great time exploring the code generation part as well. You have to dig deep to pick up the methodology of generators, but it’s fun when you finally know what you’re doing.

Everyone is welcome to contribute the library and we are also happy to see feature requests.

Who we are: Wanari is a custom software development company founded in 2000. We love exploring new technologies and creating future-proof software for our clients. Follow us on Insta or LinkedIn to see more posts like this.

Tamás Agócs

Tamás Agócs

Mobile Application Developer at Wanari
Agócs is our very own shepherd for the Android team. He's committed to push the team's technological knowledge to the limits and beyond....

Renato Kiss

Renato Kiss


Latest posts by Renato Kiss (see all)
Ádám Hosszú

Ádám Hosszú

Programming is like sex. One mistake and you have to support it for the rest of your life. (Michael Sinz)

Latest posts by Ádám Hosszú (see all)