Flutter Community
Published in

Flutter Community

Flutter Localization on the Fly

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 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 thats a different question 😂?
If you develop something with all the efforts and if people don’t understand it, its 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, 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 but more of “naive” workaround,If you know how to work with Http Requests and maintain the state of the app with provider, This would be a piece of cake for you.
And 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 which uses this approach.To Give you a test of the sample application this is how it looks.

Lets Approach the solution

The things that we localize in a app are basically strings,we Identify each string uniquely with a key,So basically the data which 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, Sometimes 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 translations 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 which 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 understood 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 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

Lets take a look at description for the User which has three parameters in it 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]),

And 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 thats 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. 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
- checkout the demo app here.

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

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store