Android Strings To Flutter

Łukasz Wiśniewski
Flutter Community
Published in
5 min readAug 7, 2019
Photo by Mink Mingle on Unsplash

TL:DR; Turn your Android Strings into i18n dart class. A story of carrots.

Backstory

A lot of articles have been written already about internationalization of Flutter apps. Here’s yet another one!

Coming from Android development to Flutter one thing that struck me was how many ways of doing internationalisation there were. My journey started with the official documentation and oh my… why is there so much boilerplate code there 🙄

Next up I followed the excellent three part article by Vo Thanh Dat. It lead me to use arb files and that famous i18n Flutter plugin for IntelliJ. It looked solid at first glance. Using my custom ugly script I converted a bunch of Android Strings into arb files and thought I would be done with it — well… it turns out localization is not a simple thing 🥕🇵🇱

How do I eat a carrot and keep VSCode?

To make matters “worse” I discovered how great VSCode is and just wanted to start using it… and the aforementioned plugin was dedicated to IntelliJ so what was I supposed to do with that? 🤷‍♀️

My struggle wasn’t an isolated case. I stumbled upon Wu Chun Wa’s article who had similar experiences to mine. He iterated over the plugin idea and wrote a standalone solution in dart that involved AirTable and json files. Be sure to check it out. AirTable, however, wasn’t the setup I was looking for.

Solution

Android Strings is probably the most used mobile i18n format with loads of tooling available for it. It’s the go-to solution for most Android developers, battle-tested, well documented, used by thousands if not millions. It’s likely you already have these XML translations somewhere and might want to migrate them somehow to your next Flutter project.

I must say XML as a format generally sucks however in case of Android Strings it’s relatively easy to understand and follow.

English 🇬🇧
Polish 🇵🇱

I decided I would just endure the pain of parsing those XML files and generate i18n Dart code myself:

intl package is used for the plural rules 🎯

What initially started as a hacky unit test in a sample Kotlin project, several cycles later evolved into an open-source project — as2f (Android String To Flutter):

If you want to use this tool in your app, the up-to-date setup instructions are in the README and the rest is pretty much similar to what the IntelliJ plugin is doing. Also, be sure to follow command line output messages!

Under The Hood

If you have followed the setup instructions from the repo page, you might be wondering how on earth

flutter packages pub run as2f:codegen

…is actually executing Kotlin code in your project. Well, the dart part of the library is nothing more than one pubspec.yaml file and one codegen.dart file located in the /bin directory.

Obviously, you can’t bundle Kotlin code, neither .jar files with your Dart library. You can, however, have a Dart script download a .jar from somewhere and then execute it… and this is what I am doing here.

Wait… downloading a random jar?!

You might ask — “Is it safe to execute code from any place in the internets?”. No, it is not! This is why I chose jitpack as a jar delivery method. I don’t expect anyone to trust me, but trusting jitpack should be no issue.

Each project is built in its own Docker container that only has access to the project’s source code. It doesn’t have access to other projects or build artifacts. Containers run only with normal user privileges (non root).

All communication between our servers is VPN secured and files produced by the build are served over HTTPS (only).

Jitpack + an open-source repo should be a transparent enough setup. It works flawlessly across forks, and probably is the safest solution I can think of at this time.

Now let’s take a closer look at the Kotlin part. It consists of two modules, cli, and as2f. The cli part takes care of all the arguments parsing, setting up file system watchers and calling the code generation logic from the as2f module. I wanted to have the cli part separated from the rest in case I want to reuse the code generation logic in some other context (e.g. IntelliJ plugin). Finally, the cli part is using shadow to produce a self-contained executable jar that can be delivered to you via jitpack.

As for as2f the code generation workflow goes something like this:

  • look for all the strings.xml files, get the language code from the directory name
  • parse XML files, collect all the keys (shameful/painful code involving XML parsing goes here)
  • figure out if there are no discrepancies between translated keys and the original ones, calculate the output
  • generate the dart code using MVEL templates
  • format the dart code using dartfmt
  • only save the file if the generated output is different from the already existing one (you don’t want to trigger unnecessary hot reload)

The hardest part is probably structuring how translations are represented in your code before passing them to the templating engine.

It’s all carrots!

This is where I figured adding a test for those carrots will keep my code organization in check and generally better document what is happening one year from now.

Why Kotlin you might ask? Well, simply put, coming from Android it’s a language I knew better than Dart and which I could get to desired results faster. No doubt you can write it all in Dart — it would take me more time though.

There Will Be Dragons 🐉

This concludes the very initial release of the as2f package. It supports basic strings, strings with arguments and plural strings (integers only, intl limitation, sorry). Gender rules are not supported in Android Strings. It is, however, as2f’s issue #1 since gender rules are supported in Flutter.

It works for me™ but most likely something will not work for you — watch the console output, hopefully, I made error messages descriptive enough to help— a lot of times it’s just missing or mistyped %s or %d in one of the translations files.

You have been warned. That said, feel free to improve the package by submitting a pull request or reporting an issue.

Thanks for reading ❤️ If you think it was good, clap the 👏 button or leave a star 🌟 on the github repo! If you want to stay connected, follow me on twitter/github or just reach out directly on fluttercommunity.slack

--

--