Flutter Localization on the Fly

Mahesh Jamdade
Flutter Community
Published in
8 min readMar 13, 2021
flutter logo taken from flutter.dev

Lots of apps require support for multiple languages inorder to have engagements with different kinds of users and we don’t want to miss our customers just because of a language barrier.

I just googled and found that (As per a 2017 report) out of 7.8 billion people only 1.35 billion people speak English so that’s approx 1/7 th of the population that means if you developed a software that targets humans only 1 out of 8 people will be able to understand it. Whether they would like it or would be interested in your app that's a different question 😂?
If you develop something with all the effort and if people don’t understand it, it's of no use. So you have to make sure your app supports the language of your targeted regions, so for this, we have to make use of L10N (aka LOCALIZATION).

So coming to the point since this post is specific to Flutter our goal is to implement localization in our Flutter app and The flutter team has well documented it here about it and also there are tonnes of other blog posts and videos which explains it very nicely. But there's a problem, All those approaches suggest you to hardcode the static strings in the app.

But what if you have to make some changes to those Strings after you have deployed the app to stores?

Or

what if you want to add support for a new language or remove an existing language support?

In such cases we will need to make changes in the code and even with a single character change in the code, you will have to rebuild and redeploy your app as flutter doesn't support hot update as of today, so updating an app seems daunting.So In this post, I am gonna show you How I solved this problem using the approach shown below in one of the production apps and wanted to share it with you.

The approach I will show you is less of a solution and more of “naive” workaround, If you know how to work with HTTP Requests and maintain the state of the app with a provider, This would be a piece of cake for you.
This will get the work done without making a single line of change in code once it is implemented(unless you want to add new localized Strings to the app). Unfortunately, I cannot show you that mobile app, but don’t worry I have a sample application built for this blog post that uses this approach. To Give you a test of the sample application this is how it looks.

Let's Approach the solution

The things that we localize in an app are basically strings, we Identify each string uniquely with a key, So basically the data that has to be dynamically changed is a JSON object. This JSON data would lie somewhere in your backend which could fetch using network request. You could also store it locally on your phone but remember we want a dynamic solution to update the languages during runtime.

If we were to localize the app in Spanish the json object would look something like this.

{
"Hi":"Hola",
"grateful": "Gracias"
}

if we had multiple languages we could just change the values and keep the keys the same. And our consolidated JSON object would look like this.

{
"es":{ /// json object for spanish
"Hi":"Hola",
"grateful": "Gracias"
},
"en":{ // json object for english
"Hi":"Hi",
"grateful": "grateful"
},
"
/// few other languages if any
...
}

Alternatively, we could just have one JSON object in a single response, and to get a correct translation we could just pass a query param in a request to let the backend know that we are requesting a JSON object of a particular language and it would return you the correct translation object.

so once we have this JSON object ready we could just make an HTTP request decode the response and display it on the UI, problem solved right?
No. Also, don’t forget the variables, we also have variables within our static strings which could be numbers, dates, and other types.

for instance, consider this flutter code

String toolKit = 'flutter';String platform =. 'MacOs'
@override
Widget build(BuildContext context){
return Center(
child:Text(“Hey, This app is built using $toolKit for $platform”)
);
}

so for such strings, we need to make sure these variables go within those translation strings coming from the JSON object in the right place. so for this, I wrote a simple function that does this given an input it produces the respective output.

input: Hey, This app is built using ${toolKit} for ${platform}.

Here's the function that does this for us.

so for the above input, we would invoke this method like this

localize(input,[toolKit,platform]); 

Then it would return the output

output:Hey, This app is built using flutter for MacOs.

Also, remember the order of the arguments in the Argument list (List<String>)should be the same as they appear in the translation sentence.
And the last thing to remember is we should fetch these translations before the app starts in order to prevent any issues And show a loader or the app splash screen whatever you prefer. You can also make use of the shared preferences to store the user's language preference which can be used to fetch the correct language on the next app launch.

Let's build the sample app

Now that you understand the approach (I assume) let's build the sample app.
Since you are looking for a solution for localization I assume you know how to build layouts in Flutter so I won't be covering that part.

so let's create the flutter project and run this command in your terminal

flutter create .

Now add these dependencies in your pubspec.yaml, I have used the json_annotation and dev_dependencies just to generate the toMap and FromJson methods but these dependencies are not mandatory.

dependencies:
flutter:
sdk: flutter
http: ^0.12.0+2
json_annotation: ^3.1.1
provider: ^4.3.3
shared_preferences: ^2.0.4
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^1.10.13
json_serializable: ^3.5.1

Now in the main. dart we need to import the provider and wrap the MaterialApp in a consumer so as to rebuild the whole app when we change the language.

The LocaleModel class contains everything about localization which we are interested in and the Settings Class contains code related to changing the theme of the app, So let's focus on the LocaleModel class.

class LocaleModel with ChangeNotifier {LocaleModel() {
fetchTranslations();
}
Map<String, dynamic> _translations = {};Map<String, dynamic> _selectedTranslation = {};Locale locale;Locale get getlocale => locale;Map<String, dynamic> get translations => _selectedTranslation;set translations(Map<String, dynamic> map) {
_selectedTranslation = map;
notifyListeners();
Future<void> fetchTranslations({String language}) async {
try {
language = await getLanguagePreferences();
final response = await http.get(Uri.parse(translationsApi));
if (response != null) {
if (response.statusCode == 200) {
final Map decoded = json.decode(response.body);
_selectedTranslation = decoded[language ?? 'en'];
_translations = decoded;
notifyListeners();
}
}
} catch (_) {
print(_.toString());
}
}
}
}

the constructor at the top invokes this function as soon as the app starts. Remember we have a multiprovider wrapped around in main.dart that's where this call comes from.

LocaleModel() {
fetchTranslations();
}

This function basically makes a network request and stores the translations in the state of the app in these two Map Objects _translation and _selectedTranslation.
The _translation holds the consolidated Map object (with All languages) that we discussed above and _selectedTranslation holds the strings of the current translation being shown in the app. we need _translation in order to fetch the translation on changing the language this prevents us from making an extra API call, we directly fetch it from the state and refresh the app without making the user to wait for the changes to take effect.

Now let's see how we are making use of these translations

Let's take a look at the description for the User which has three parameters in city, state, country

red underlined variables used in the description of the user.

The description that we receive in the API response looks like this

And this is how we access this map object from _selectedTranslation. considering we have already instantiated localeModel like this in initstate to access the objects of LocaleModel class.

/// initialized in initstate
_localeModel = Provider.of<LocaleModel>(context, listen: false);

access the description in the widget using _localeModel.

localize(_localeModel.translations['description'],[user.location.city, user.location.state, user.location.country]),

This is how the description widget looks.

And thats how we can localize the entire app,The syntax might look bit ugly but we can always simplfy it.

And we can change the language of the app like this

_localeModel.changelocale(Locale('es')); // changing to spanish

And you can see few more additional things done in the source code like storing and fetching the current locale for the next launch using sharedPreference.

And that's it we are at the end.

Summing up

This approach allows you to
- Dynamically edit the translation files.
- Add/ Remove support for a new language.
both without making a single line of change in your flutter app.
- No hassle with generating arb files and maintaining static translations

Things it lacks: Numbers, Dates, and Currencies translation but we can always have custom functions for these or this could also be backend-driven within the same translation object.
That said we are at the end of this blog post hope you like this approach, And if you have any questions or critiques feel free to drop them below in the comments I would love to discuss them. And don’t forget to smash the 👏🏼 button until it warns. The more you clap the more it inspires me to bring some great blog posts.
And you can always
- play with the source code here
Check out the demo app here.

Thank you for taking the time to read this long post and keep fluttering 💙.

--

--

Mahesh Jamdade
Flutter Community

I occasionally write about my experiences with different technologies. Personal blog: https://blog.maheshjamdade.com