Supply Translations to your Flutter app.
When it comes to “Internation-alizing” your Flutter app, there’s a few things you have to set up first before you can proceed as it’s states in the documentation, Setting up an internationalized app:
“To add support for other languages, an application must…include a separate package called flutter_localizations.… If you want your app to work smoothly on iOS then you have to add the package ‘flutter_cupertino_localizations’”
With that, you can then import the flutter_localizations library. Next, you must specify the ‘localizationsDelegates’ and ‘supportedLocales’ to the widget, MaterialApp. Each delegate encapsulates a collection of localized values or, with regards to this article, localized text translations.
Flutter has a process to provide such translations (localization strings), but I preferred Thomas Ecalle’s own library, FlappyTranslator, as it too provided a means to display native text strings and their translated text strings in a Flutter app. His work inspired me to create the library package, I10n_Translator. One particular characteristic that I liked and blatantly adapted from his library to use in my own, as he writes in his article, “our strings need to be stored in a human-readable and easy-to-use-or-update format for non-developers.”
His library simply provided a text file that contains comma-separated values. This is commonly called a CSV file. It’s a text file that uses a comma to separate its text values, and is readily recognized by many editors and other popular programs. Thus allowing ‘non-developers’, for example, to easily enter the text translations for a Flutter app. And so, the library package, I10n_Translator, also uses a CSV file to store text translations.
As an example, below is a screenshot of the CSV file named, i10n.csv, containing some English words and their corresponding French and Spanish translations in the simple Windows text editor called, Notepad.
As you see below, this text file is easily read in by Microsoft Excel presenting its contents in a nice easy-to-read tabular format. You can see the English column, the French column and the Spanish column. Further, you can recognize it’s the first line of text displayed is the line that contains the ‘language codes’ (ISO 639–1) represented in this CSV file. Lastly, it also has blank lines here and there, again, for readability.
In this article, I’m going to quickly introduce this library package and how to use it. As always, I prefer using screenshots over gists to show code in my articles. I find them easier to work with, and easier to read. However, you can click/tap on these screenshots to see the code as a gist or in Github if you like. Ironically, it’s better to read this article about mobile development on your computer than on your phone. Besides, we program mostly on our computers; not on our phones. For now.
Further, there‘s a few gif files in this article demonstrating functions and features in ‘moving pictures.’ However, I’m told viewing such files is not possible when reading this article on platforms like Instagram, Facebook, etc. You’ll see empty square spaces where the gif files should be. Please, be aware of this. I would recommend reading this in a browser on medium.com.
The CSV file named, i10n.csv, you’ve see above is actually used by the sample app, Shrine MVC. This sample app, of course, is available to you on Github which you can download so to understand how this library package can be used in your own Flutter app. Note, the library package itself has its own example app as well to further demonstrate its implementation. The source code of the example app is also found on Github.
Below is a screenshot of the resulting code generated from the CSV file. Of course, if you’re familiar with my past library packages, you know I like options. In this case, you’ve the option to instead ‘read in’ the CSV file directly to provide the translations! You don’t have to generate this code from the CSV file if you don’t want to. I’ll explain that later on. For now, let’s see how you’d use this code anyway as there may be times when you’ll want to generate it.
In the screenshot above, there’s now a Map object called, i10nWords, that contains all the translations currently available to the Flutter app. Below, is a screenshot of the sample app, Shrine MVC, taking in that object with an import statement and then supplying that object to the init () function in the i10n_translator library package. Easy peasy.
As it happens, when you run the sample app, Shrine MVC, you’ve the means to manually ‘switch’ between the default text and its two translations. There’s a menu option allowing you to do just that. The screenshots below depict the change, for example, from English to French. Notice the labels of the displayed articles then change accordingly.
Below is the screenshot of the code that displays those items and subsequently they’re corresponding labels. You can see the default strings, in this case, are in English. However, note when looking at the list of Product objects below, the named parameters called, name, are not being supplied the strings directly but are instead being supplied the static function, s (), from the library package, i10n_translator. It’s in this static function where the translation is performed and returned to the named parameter, name.
Below is a gif file demonstrating how the app’s text dynamically changes when you select a ‘language code’ in the menu popup. Note, instead of ‘language code’, I could have just as easily used flags or what have you. It didn’t have to be a list of language codes. Regardless, it’s the library package that takes in the language code and change the Locale of the app accordingly.
The screenshot below conveys the PopupMenuButton widget with its three vertical dots displayed in the gif file above. Notice how the I10n_translator library package is called upon to load the selected Locale. Consequently, in the code, that value is then saved in the app’s system preferences so to load that selected locale if and when the app starts up again. Finally, the app’s interface is refreshed to present the new text translations.
Note, further along in the code, I could have read the generated object, i10nWords, and listed the ‘language codes’ it contained instead of just explicitly listing PopupMenuItems in the code, but I was lazy.
Again, the idea behind all this is to provide the app’s translations in a simple text file delimited by commas. It’s assumes the ‘default’ language is found in the first column. You would then insert those text strings throughout the app itself as you normally would. However, instead of using the widget, Text, you would insert those text strings into the static functions, I10n. s () or I10n. t (). The first column then serves as the ‘key field’ when reading in this CSV file. Note, as a key field, the first column of text is therefore case-sensitive.
Once the translations are ready, you’ve the option now to generate the code and supply those translations to your Flutter app as a parameter. Note, to use this option, you have to set up one thing in your app’s pubspec.yaml file. Under the header, dev_dependencies, you have to reference the library package so you can call it within your chosen IDE.
And so now using your IDE’s terminal window for example, you’re free to type in the following command line to generate the code in a Dart file:
flutter pub run i10n_translator test.csv path/destination/results.dart
The command line above, as an example, would read in a CSV file called, test.csv, and produce a Dart file called, results.dart, in the specified location. It will contain the Map object, i10nWords. Note, however, if you simply type out the command line without the two arguments, it assumes the following CSV file and resulting Dart file:
flutter pub run i10n_translator [i10n.csv] [i10nwords.dart]
Further still, however, if you’re innately lazy and don’t like typing like me, you could use Android Studio and create a ‘Dart command line app’ that instead points directly to the library package file, i10n_translator.dart. You then just ‘run’ that configuration to produce the resulting Dart file. See below.
So, how does that command line work? Looking at a screenshot of that i10n_translator.dart file, you can see there’s a main () function in there that calls the library package class, I10nTranslator. Further, it supplies any arguments to the function, generate () that creates the code.
Again, I’m lazy. I don’t want to do that ‘extra step’ of generating the code to display the translations. I’ve got this CSV file now, why don’t I just use that file?? To do that, I’ll just call the init () function with no parameters.
You see, I’ve simply included the text file as an asset in the Flutter app and have the library package read it in at start up to supply the translations. By default, this library package will look for the CSV file in the assets directory under the location, assets/i10n/i10n.csv. And so, instead of adding the library package under the header, dev_dependences, I’ve specified that location in the pubspec.yaml file. When the app starts up, that CSV file will be read in and the translations made available to the Flutter app. Easy peasy.
And so, over time for example, while your developing your app, you may have more translations entered into that CSV file. There’s no need to generate any code from time to time. Merely place that file in the ‘assets’ location and you’re good to go!
Now what happens if there’s no translation available for a particular word in your Flutter app? Nothing happens. It simply displays the original key field — which happens to be the original text. See what I mean?
This is a utility class, it’s not to hinder your own Flutter app. After all, it’s your Flutter app! An app, no doubt, designed for a very important task! It’s that task that is of primary concern. If there happens to be a ‘missing translation’, you certainly don’t want your app to blow up because of it! So it won’t. It’ll just display the string supplied to the static function I10n. s () or I10n. t ().
If you’ve implemented those static functions throughout your app and yet there’s no CSV file for example, there’s no problem. Again, they’ll simply display the passed text string. Further, even if you mistakenly pass nothing to those static functions, an empty string is then displayed…unseen of course.
Like documentation, translations tend not to get done as the development progresses in projects. Frankly, like documentation, it tends only to get done at the last minute! Again, the priority is getting the app working; not its translations. This library package allows for that. With their unique signature, you search your code and quickly find all the static functions and collect all the text that needs translating. Better yet, have the library package do that for you — but, I’ll explain that a litter later.
Like the sample app, Shrine MVC, the example included with the library package has to first pass the ‘supported locales’ and ‘localization delegates’ to the MaterialApp widget whenever a Flutter app requires translations. Again, this has to be done if you want to introduce Internationalization into your Flutter app — even without using the library package, i10n_translator. Both the entries, I10nDelegate()and I10n.supportedLocales are supplied by the library package, i10n_translator.dart, and so it’ll take care of that part for you.
As it happens, its example code also has a menu option that allows you to switch between languages. In this case, between eight different locales. Once selected, like the first sample app, Shrine MVC, the code calls the library package to load the selected locale, it then saves that locale into system preferences, and then refreshes the UI. Note, the saving of the selected locale uses another library package of mine called, Prefs.
The example also looks up the file, assets/i10n/i10n.csv , for its translations. Since reading a file in Flutter involves an asynchronous operation, that means it involves Future objects. Hence, the example, in this case, needs time to ‘read in’ the CSV file before it can proceed. As you see below, that’s why a FutureBuilder widget is used right away in the example.
Now, as I suggested earlier, there will be instances where you don’t want to wait for a file to be read in. If you recall, it’s in those instances where you’ll want to generate that Map object called, i10nWords. For example, if a FutureBuilder widget is not to be used, the example code could look like this:
After using the i10n_translator file to generate the code, the library package’s init () function is called like before, but now the generated code in the file, i10n_words.dart, is instead read in as a parameter. This, of course, is a much faster operation and yet produces the very same results at the first version. Click on the example code’s menu option, and you can change the text to eight different languages in the good ol’ counter demo app. Easy peasy.
I had hinted earlier that, ideally, while you’re developing your app, this library package should just collect all the text that needs translating for you and put it in that CSV file for your translators to work on later. Well, the library package does just that — almost. As you see in the screenshot below, the s () function does indeed ‘add’ the text string (the key) passed to it into a file when the app is running, but there’s a problem.
Like you, I still have a lot to learn from this wonderful platform that is Flutter. And so, as of writing, there’s an CSV file filling up with all this text, but I can’t figure out how to then readily gain access to that file. I would have liked to have resolved this before publishing this article, but...you know…I’m lazy.
Please, if you’re up for it, let me know what would be the best approach to provide that file to the developer? Better yet, to a non-developer making the process…you know…Easy peasy.