Internationalizing and Localizing Your Flutter App

Learn how to use the flutter_localization and Intl packages to easily localize and internationalize your app, making it accessible to users in different locales. By Edson Bueno.

4.9 (22) · 1 Review

Download materials
Save for later

In this tutorial, you’ll learn how to use the Flutter Intl plugin to internationalize and localize your app. Why should anyone care about internationalization and localization, though? Well, let the numbers do the talking:

  • Eight of the top ten countries for app downloads are not English-speaking, according to Sensor Tower.
  • Localized apps are downloaded 128% as often and earn 26% more revenue per supported country, according to Distimo.

But more people learn English every day, so maybe these numbers will lose relevance over time, right? Nope. The next billion internet users will come from emerging economies. Here’s what Google already knows about them:

  • They have a mobile-only mindset. Most have never used a PC and never will — which is lucky for mobile developers.
  • They need localized content. Even the small percentage that knows English prefers content in their language.

Nelson Mandela captured that sentiment when he said: “If you talk to a man in a language he understands, that goes to his head. If you talk to him in his language, that goes to his heart.” You want users to have that kind of passion for your app.

Given all this, can you still think of a single reason not to localize your app? If your answer is that you don’t know how to do it, this tutorial will help.

By the end of this tutorial, you’ll know how to:

  • Tell the difference between internationalization and localization.
  • Internationalize a Flutter app.
  • Use the Intl package.
  • Use the Flutter Intl IDE plugin.
  • Localize materials and Cupertino widgets.
  • Make your app feel like you developed it in the user’s native language.
Note: This article assumes you’re comfortable with Flutter basics. If that’s not the case, check out Getting Started with Flutter.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

Unzip the file and open Android Studio 4.0 or later. You can use Visual Studio Code instead, but you’ll need to adapt some instructions.

Click Open an existing Android Studio project and choose the starter folder from your unzipped download.

The IDE will prompt you with the message: ‘Pub get’ has not been run at the top of your screen. Click Get dependencies and wait for the download to finish.

Today, you’ll work on a very important app indeed — a death by caffeine calculator, the Buzz Kill.

Woman stirring a cup of coffee and staring

Start off by taking a brief look at the code. Open the pages/form_page.dart file and take a look around. There’s a lot of code, but don’t be intimidated! Most of it is simply setting up a list of coffee drinks for a user to select.

Next up open the pages/results_page.dart file. results_page is a simple widget that displays some safe coffee consumption information to the user.

Take a brief look at the code, focusing on pages/form_page.dart and pages/results_page.dart. Then build and run the project to get familiar with it.

Initial version of the sample app

For now, Buzz Kill only works in English. Your job is to make it welcoming for Brazilians.

Why Brazilian Portuguese (pt-BR)? Because Brazil has the third-most app downloads, but only 5% of the population speaks English. That makes it an excellent target for localization.

Talking about languages, do you know how to say internationalization in dt-FL (Flutterian Dart)? It’s i18n. Why? It encompasses the first and last letters of “internationalization” and then shortens the 18 letters in between.

Waving flag containing a Flutter logo

Before you start improving Buzz Kill, there’s one important thing to clear up: what internationalization and localization actually mean.

Differentiating Between Internationalization and Localization

It’s not uncommon to see the words internationalization (i18n) and localization (l10n) used — mistakenly — interchangeably. Where do you draw the line?

Territory shape divided between internationalization and localization

Localization is the process of making your app available to other locales. It includes the parameters defining the user’s language, region and any particular variant — for example, en-US (American English).

That sounds easy, but how do you do it? Do you create a copy of your code for each language? Or add if statements all over the place choosing which text to display? No, that’s where internationalization comes to the rescue.

Internationalization is how you architect and engineer your code to make it easy to localize. So, yes, you can have an internationalized app that supports just one locale, because it has the capacity to ultimately support more than one locale without dramatic engineering efforts. In fact, that’s encouraged. Internationalize even if becoming a polyglot isn’t in your app’s plans. It’ll make your code cleaner and more future-proof.

Next, you’ll see how you use Flutter to internationalize your apps.

Internationalizing in Flutter

These are the stepping stones to i18n in Flutter:

  1. Create a class containing dynamic String properties or functions for every translatable text of your app. For example, a formPageAppBarTitle property may return 'Death by Caffeine Calculator' or 'Calculadora de Morte por Cafeína', depending on the locale.
  2. Create an intermediary class in charge of setting up instances of the class created in the previous step when the app initializes or the locale changes.
  3. Provide an instance of this intermediary class to a Localizations widget living inside MaterialApp.
  4. Replace hard-coded texts with their new, dynamic versions. For example, Text('Death by Caffeine Calculator') becomes Text(Localizations.of<ClassFromTheFirstStepContainingValuesThatVaryPerLocale>(context, ClassFromTheFirstStepContainingValuesThatVaryPerLocale).formPageAppBarTitle).

Although ClassFromTheFirstStepContainingValuesThatVaryPerLocale is a meaningful name, for sure, it’s quite long, and you’ll be typing it a lot. From now on, you’ll call it S.

Maintaining Translations

The question that remains is: How should S store the Strings for all the supported languages?

That’s what you call an implementation detail, and as such, it’s up to you. But here are some ideas:

  1. Use a Map<String, Map<String, String>>, where the first Map‘s key is the language, and the second Map‘s key is the ID of the text, e.g., formPageAppBarTitle.
  2. Maintain — and read from — JSON-like files, one per locale.
  3. Use your creativity.

The second approach looks like an excellent choice, and it’s what you’ll use in this tutorial. You’ll count on the Intl package to help you read the values from the files.

Keeping content separate from logic keeps your code neat and makes it feasible even for non-coders to change it.

Blueprint of the Flutter's internationalization architecture

Even with Intl’s help, there’s plenty of work left for you to do. The process of changing the delegate every time a new language comes in and changing S whenever you need to add a new String — which happens often — is cumbersome and robotic.

As you know, wiping out cumbersome and robotic tasks is a developer’s superpower. That’s why the people at Localizely created the mind-blowing Flutter Intl plugin for Android Studio and Visual Studio Code that handles those tasks for you.

Whenever you add a new language or String, the plugin generates a new LocalizationsDelegate and S for you.

Note: Don’t confuse the Intl package with the Flutter Intl IDE plugin. The latter handles generating the classes that use the former. You’ll use both in this tutorial.

Setting up Flutter Intl

Open Android Studio’s preferences by pressing Command-, (comma) on macOS or Control-Alt-S on Linux or Windows.

Select Plugins on the left-side panel (1) and Marketplace in the upper tab bar (2). Type intl in the search bar (3), then click Install for Localizely‘s Flutter Intl result (4).

Android Studio's preferences dialog

With the plugin installed, restart your IDE.

Generating Classes

Open pubspec.yaml and replace # TODO: Add intl and flutter_localizations here. with:

intl: 0.16.1
  sdk: flutter

You’ve just added two packages to your project:

  1. intl: Facilities for i18n and l10n such as message translation and date/number formatting and parsing. You’ll mostly deal with this package through the generated classes.
  2. flutter_localizations: Hang tight! You’ll learn more about this in just a second.

Select the starter folder in the project directory then, on the menu bar, select ToolsFlutter IntlInitialize for the Project.

Path to Flutter Intl on Android Studio's menu bar

The command above added a flutter_intl section to your pubspec.yaml. Add main_locale: en_US to it, below and in the same indentation level of enabled: true.

  main_locale: en_US
  enabled: true

Click Pub get in the Flutter commands bar at the top of your screen.

Return to ToolsFlutter Intl on the IDE’s menu bar, but this time, select Remove Locale. Choose en and click OK.

Flutter Intl's Remove Existing Locale dialog

Here, you changed your default locale from en to en_US, properly identifying the nuance. That’ll allow you to execute custom logic based on the country code later.

The plugin created two folders, including some files, inside lib:

  1. generated: Holds the generated classes. You won’t need to touch them.
  2. l10n: Home of your .arbs, the JSON syntax files that’ll hold your translations. The one for American English is already there for you.

Now that you’ve set up Flutter Intl, it’s time to configure your app to use it.

Configuring Your App

Go to lib/main.dart, and add these two imports below the others:

import 'package:buzzkill/generated/l10n.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

Now, inside MaterialApp, remove // TODO: Specify localizationsDelegates and supportedLocales. and add this instead:

localizationsDelegates: [
  // 1
  // 2
supportedLocales: S.delegate.supportedLocales,

Here’s what you did:

  1. Remember the intermediate class? The delegate responsible for setting up instances of S? The plugin generated it for you, and you use it here.
  2. Not only does your app’s text need translations, but some Flutter widgets do as well. The delegates taking care of them came from the flutter_localizations package you added above.

Back in the menu bar, select ToolsFlutter IntlAdd Locale. Type in pt_BR and click OK.

Flutter Intl's Add New Locale dialog

The lib/l10n folder now contains a new file — intl_pt_BR.arb — and the Android version of your app is ready to support en-US and pt-BR.

Only the Android version? Not the iOS? Yep! That’s because one specific iOS configuration file needs your special care. The good news is that you don’t need Xcode to do it.

In your project structure, open ios/Runner/Info.plist and substitute <!-- TODO: Specify supported locales. --> with:


Stop the previous execution of the app, then build and run it again to make sure you haven’t introduced any errors. Don’t expect any visual changes.

Initial version of the sample app

Extracting the Strings

Replace what’s in lib/l10n/intl_en_US.arb with:

  "@@locale": "en_US",
  "formPageAppBarTitle": "Death by Caffeine Calculator",
  "firstSuggestedDrinkName": "Drip Coffee (Cup)",
  "secondSuggestedDrinkName": "Espresso (Shot)",
  "thirdSuggestedDrinkName": "Latte (Mug)",
  "formPageWeightInputLabel": "Body Weight",
  "formPageWeightInputSuffix": "pounds",
  "formPageRadioListLabel": "Choose a drink",
  "formPageActionButtonTitle": "CALCULATE",
  "formPageCustomDrinkRadioTitle": "Other",
  "formPageCustomDrinkServingSizeInputLabel": "Serving Size",
  "formPageCustomDrinkServingSizeInputSuffix": "fl. oz",
  "formPageCustomDrinkCaffeineAmountInputLabel": "Caffeine",
  "formPageCustomDrinkCaffeineAmountInputSuffix": "mg",
  "resultsPageAppBarTitle": "Dosages",
  "resultsPageLethalDosageTitle": "Lethal Dosage",
  "resultsPageFirstDisclaimer": "*Based on {servingSize} fl. oz serving.",
  "resultsPageLethalDosageMessage": "{quantity, plural, one{One serving.} other{{formattedNumber} servings in your system at one time.}}",
  "resultsPageSafeDosageTitle": "Daily Safe Maximum",
  "resultsPageSafeDosageMessage": "{quantity, plural, one{One serving per day.} other{{formattedNumber} servings per day.}}",
  "resultsPageSecondDisclaimer": "*Applies to age 18 and over. This calculator does not replace professional medical advice."

These are the en-US entries for every visible line of text within Buzz Kill. Pay special attention to:

  • @@locale: Identifies the locale of the file. If you don’t add it, Intl can infer it from the file name, but it’ll give you a warning.
  • resultsPageFirstDisclaimer: {servingSize} is a placeholder. S dynamically replaces it with the value you specify when using the function it generates.
  • resultsPageLethalDosageMessage and resultsPageSafeDosageMessage: These are plurals. Their values depend on a number specified when calling the function. Besides that, they also use a {formattedNumber} placeholder, like resultsPageFirstDisclaimer.

Don’t worry! These concepts are easier to grasp when you see them reflected on the Dart side.

Note: You shouldn’t reuse entries, which is why you named them after the place they appear in your app. The same string can have different translations depending on its context.

Now that you have your .arb file set up for American English, it’s time to add the Brazilian Portuguese version to Buzz Kill.

Adding Brazilian Portuguese Translations

This time, inside lib/l10n/intl_pt_BR.arb, replace everything with:

  "@@locale": "pt_BR",
  "formPageAppBarTitle": "Calculadora de Morte por Cafeína",
  "firstSuggestedDrinkName": "Café Coado (Xícara)",
  "secondSuggestedDrinkName": "Espresso (Shot)",
  "thirdSuggestedDrinkName": "Latte (Caneca)",
  "formPageWeightInputLabel": "Peso Corporal",
  "formPageWeightInputSuffix": "libras",
  "formPageRadioListLabel": "Escolha uma bebida",
  "formPageActionButtonTitle": "CALCULAR",
  "formPageCustomDrinkRadioTitle": "Outra",
  "formPageCustomDrinkServingSizeInputLabel": "Tamanho",
  "formPageCustomDrinkServingSizeInputSuffix": "fl. oz",
  "formPageCustomDrinkCaffeineAmountInputLabel": "Cafeína",
  "formPageCustomDrinkCaffeineAmountInputSuffix": "mg",
  "resultsPageAppBarTitle": "Dosagens",
  "resultsPageLethalDosageTitle": "Dose Letal",
  "resultsPageLethalDosageMessage": "{quantity, plural, one{Uma porção.} other{{formattedNumber} porções no seu sistema de uma vez.}}",
  "resultsPageSafeDosageTitle": "Limite Seguro Diário",
  "resultsPageSafeDosageMessage": "{quantity, plural, one{Uma porção por dia.} other{{formattedNumber} porções por dia.}}",
  "resultsPageFirstDisclaimer": "*Baseado em uma porção de {servingSize} fl. oz.",
  "resultsPageSecondDisclaimer": "*Se aplica a pessoas com 18 anos ou mais. Essa calculadora não substitui conselhos médicos profissionais."

There’s nothing new here. These are the Portuguese translations for the same lib/l10n/intl_en_US.arb entries.

Trigger the classes to re-generate by saving the file with Command-S on macOS or Control-S on Linux or Windows.

Build and run again. Everything should look the same as before, and now you’re ready to code for real.

Initial version of the sample app

You’ve accomplished a lot, but the app won’t reflect your changes until you remove the hard-coded values from your widgets.

Removing Hard-Coded Values

Your journey begins on lib/pages/form_page.dart, Buzz Kill’s home. Open the file and start by adding this line at the top of the imports block:

import 'package:buzzkill/generated/l10n.dart';

The UI presents the user with three suggested caffeinated drinks. In code, you control them with _drinkSuggestions. Remove the current _drinkSuggestions definition and add this instead:

// 1
List<Drink> _drinkSuggestions;

// 2
Locale _userLocale;

void didChangeDependencies() {
  // 3
  final newLocale = Localizations.localeOf(context);
  if (newLocale != _userLocale) {
    _userLocale = newLocale;
    // 4
    _selectedDrinkSuggestion = null;
    _drinkSuggestions = [
        // 5
        name: S.of(context).firstSuggestedDrinkName,
        caffeineAmount: 145,
        servingSize: 8,
        name: S.of(context).secondSuggestedDrinkName,
        caffeineAmount: 77,
        servingSize: 1.5,
        name: S.of(context).thirdSuggestedDrinkName,
        caffeineAmount: 154,
        servingSize: 16,

There’s a lot going on in there:

  1. The property can’t be final anymore. Since you have strings that need to be localized in your list of Drinks, you now need to initialize it inside didChangeDependencies() before the context will be available.
  2. Locale contains information about (drum roll, please), the current locale! You use this property to track locale changes while the app is open. That way, you can reset your form fields and suggestions if a change occurs.
  3. You’re getting the current Locale.
  4. Here, you reset the input fields.
  5. You defined firstSuggestedDrinkName inside the .arb files and, like magic, it became a property of S. S.of(context) is short for Localizations.of<S>(context, S);

Now that you’ve translated the suggestions, it’s time to move on to build(). From top to bottom, here’s a sequence of substitutions for you to make:

  1. 'Death by Caffeine Calculator' becomes S.of(context).formPageAppBarTitle
  2. 'Body Weight' becomes S.of(context).formPageWeightInputLabel
  3. 'pounds' becomes S.of(context).formPageWeightInputSuffix
  4. 'Choose a drink' becomes S.of(context).formPageRadioListLabel
  5. 'Other' becomes S.of(context).formPageCustomDrinkRadioTitle
  6. 'Serving Size' becomes S.of(context).formPageCustomDrinkServingSizeInputLabel
  7. 'fl. oz' becomes S.of(context).formPageCustomDrinkServingSizeInputSuffix
  8. 'Caffeine' becomes S.of(context).formPageCustomDrinkCaffeineAmountInputLabel
  9. 'mg' becomes S.of(context).formPageCustomDrinkCaffeineAmountInputSuffix
  10. ‌'CALCULATE' becomes S.of(context).formPageActionButtonTitle

Go through the build() method and replace each instance of the above strings with their corresponding localized variants.

Wow, that was heavy-ish! If only the Buzz Kill’s developer had been smart enough to internationalize it from the start. :]

Adjusting the Results Page

You’re halfway there. Now, it’s time to give lib/pages/results_page.dart the same treatment. Open the file and add this import at the top of the imports block:

import 'package:buzzkill/generated/l10n.dart';

Now, move on to build() and make these replacements:

  1. 'Dosages' becomes S.of(context).resultsPageAppBarTitle;
  2. 'Lethal Dosage' becomes S.of(context).resultsPageLethalDosageTitle;
  3. 'Daily Safe Maximum' becomes S.of(context).resultsPageSafeDosageTitle;
  4. '*Based on ${drink.servingSize} fl. oz serving.' becomes S.of(context).resultsPageFirstDisclaimer(drink.servingSize);

You skipped a few strings in the build() method, so double back and make sure to update them:

    • Remove:
      lethalDosage == 1
          ? 'One serving.'
          : '${lethalDosage.toStringAsFixed(1)} '
            'servings in your system at one time.'
    • Add:
    • Remove:
      safeDosage == 1
          ? 'One serving per day.'
          : '${safeDosage.toStringAsFixed(1)} '
            'servings per day.'
    • Add:
    • Remove:
      '*Applies to age 18 and over. This calculator '
      'does not replace professional medical advice.'
    • Add:

Notice that some of these aren’t properties of S, but functions, because they need arguments like a plurals value or filler text for a placeholder.

Finally, build and run the app. Mess with the device’s language configuration and watch the app respond accordingly. The .gif below demonstrates how to do that on Android (on the left) and iOS (on the right), but the steps may vary on your OS version.

Translated version of the sample app with instructions for changing the language settings

Congratulations, you’ve translated your first app!

Going Beyond Translation

Not to be a total buzzkill (ha!), but you’re just getting started with localization. Simply put: The product must feel local, and that goes beyond just the language. Different regions use different formats for everything from the time of day to how they write out phone numbers.

They say a picture is worth a thousand words. This one shows some things that localization covers:

Many different aspects of localization

Can you guess which aspects of localization apply to Buzz Kill?

  1. Measurement Units: Brazilians — and most of the rest of the world — don’t use the imperial system. Translating “pounds” and “fl. oz” into Portuguese doesn’t mean that people will understand them.
  2. Number Formatting: Unlike the United States, Brazil uses a comma as the decimal separator and a dot as the thousand separator.
  3. Culture: Drip coffees and espressos are pretty common in Brazil as well as in the United States, but lattes aren’t!
  4. Text Direction: In some languages, writing goes from the right to the left (RTL). Localization for these languages goes beyond just text direction. For example, if your left and right paddings have different values, you want to switch them for an RTL locale. Buzz Kill doesn’t support any RTL languages at the moment, but you’ll see how easy it is to be ready to support them right out of the box.

Next, you’ll put these localization features in place.

Making the App Feel Local

You’ll start by changing the text. Roll up your sleeves, open lib/l10n/intl_pt_BR.arb and replace:

  1. "thirdSuggestedDrinkName": "Latte (Caneca)" with "thirdSuggestedDrinkName": "Pingado (Copo Americano)";
  2. "formPageWeightInputSuffix": "libras" with "formPageWeightInputSuffix": "quilos";
  3. "formPageCustomDrinkServingSizeInputSuffix": "fl. oz" with "formPageCustomDrinkServingSizeInputSuffix": "ml";
  4. "resultsPageFirstDisclaimer": "*Baseado em uma porção de {servingSize} fl. oz." with "resultsPageFirstDisclaimer": "*Baseado em uma porção de {servingSize} ml.";

Save the file with Command-S on macOS or Control-S on Linux or Windows.

First, you’ve changed the third suggestion name from Latte to a well-known caffeinated friend of Brazilians, the pingado. Then you changed the weight measure name from pounds to kilograms, and the liquid volume measure name from fluid ounces to milliliters.

As you might have guessed, changing measure unit names isn’t enough. You need some math to convert them.

Converting Measures

Create a new file by right-clicking the lib folder and choosing NewDart File. Name it measurement_conversion and enter the following code:

import 'package:flutter/widgets.dart';

bool _shouldUseImperialSystem(Locale locale) {
  final countryCode = locale.countryCode;
  return countryCode == 'US';

// 1
extension IntMeasurementConversion on int {
  int get _roundedPoundFromKg => (this * 2.20462).round();
  double get _flOzFromMl => this * 0.033814;

  // 2
  int toPoundsIfNotAlready(Locale locale) {
    if (_shouldUseImperialSystem(locale)) {
      return this;

    return _roundedPoundFromKg;

  double toFlOzIfNotAlready(Locale locale) {
    if (_shouldUseImperialSystem(locale)) {
      return toDouble();

    return _flOzFromMl;

extension DoubleMeasurementConversion on double {
  int get _roundedMlFromFlOz => (this * 29.5735).round();
  // 3
  double toMillilitersIfShouldUseMetricSystem(Locale locale) {
    if (_shouldUseImperialSystem(locale)) {
      return this;

    return _roundedMlFromFlOz.toDouble();

Going over it step-by-step:

  1. You use Dart extension methods to add utilities to int and double.
  2. The functions in this extension convert a number to its imperial system counterpart if the user entered it using the metric system.
  3. Finally, you’re converting the number to the metric system if the user isn’t using the imperial system.

Now, you need to use the functionalities you’ve just added.

Implementing the Measurement Conversion

Go to lib/pages/form_page.dart and add an import to the previously created file at the top:

import 'package:buzzkill/measurement_conversion.dart';

Inside _pushResultsPage(), replace the weight and drink variable declarations with:

final weight =

final drink = _selectedDrinkSuggestion ??
      caffeineAmount: _caffeineTextController.intValue,
      servingSize: _servingSizeTextController.intValue

If the user entered the weight in kilograms and the serving size in milliliters, this code converts them to their imperial system alternatives because that’s how ResultsPage and Drink expect them.

Now, it’s time to make the numbers look like your Brazilian users expect them to.

Formatting Numbers

Go to lib/pages/results_page.dart and, at the top of the import block, add these two imports:

import 'package:buzzkill/measurement_conversion.dart';
import 'package:intl/intl.dart';

At the beginning of the build() method, add this line below the declaration of lethalDosage:

final numberFormat = NumberFormat('#.#');

NumberFormat comes from Intl. It handles the decimal/thousand separators localization.

Still in build(), make these substitutions:

  1. lethalDosage.toStringAsFixed(1) becomes numberFormat.format(lethalDosage);
  2. safeDosage.toStringAsFixed(1) becomes numberFormat.format(safeDosage);
  3. S.of(context).resultsPageFirstDisclaimer(drink.servingSize) becomes:

In the first two, you started using the numberFormat to format your numbers. The last substitution also uses one of the lib/measurement_conversion.dart functions to display the serving size converted back to milliliters, in case the user is not in the U.S.

Dog trying to reach a ball

Customizing Drink Suggestions

Much like the metric units, you can’t change a suggested caffeinated drink just by replacing its name.

Lattes and pingados have different amounts of caffeine and serving sizes, so you need to make a few additional changes. Open lib/drink.dart, and add the following import:

import 'package:buzzkill/generated/l10n.dart';

Add this function to Drink:

static List<Drink> suggestionListOf(BuildContext context) {
  final suggestionList = [
      name: S.of(context).firstSuggestedDrinkName,
      caffeineAmount: 145,
      servingSize: 8,
      name: S.of(context).secondSuggestedDrinkName,
      caffeineAmount: 77,
      servingSize: 1.5,
  final countryCode = Localizations.localeOf(context).countryCode;
  if (countryCode == 'BR') {
        name: S.of(context).thirdSuggestedDrinkName,
        servingSize: 6.42,
        caffeineAmount: 23,
  } else {
        name: S.of(context).thirdSuggestedDrinkName,
        caffeineAmount: 154,
        servingSize: 16,

  return suggestionList;

That makes the last item of the list adaptable to the device’s country.

Open lib/pages/form_page.dart, and in didChangeDependencies(), change the _drinkSuggestions initialization to:

_drinkSuggestions = Drink.suggestionListOf(context);

Here, you delegate the creation of the suggested drinks to Drink. You can now add a drink specific to the culture of the country you’re targeting. Now, you’ll add support for RTL languages.

Preparing for RTL Languages

For the final touch, open lib/ui_components/informative_card.dart and change the padding of the second Padding to:

const EdgeInsetsDirectional.only(
  top: 16,
  end: 16,
  bottom: 16,

Can you spot the difference?

When using EdgeInsetsDirectional, you don’t specify left and right, but start and end. This is RTL-friendly. Make a habit of using it whenever you need different values for left and right.

Finally, build and run and delight yourself with a fully-localized app. Again, play with the device’s language settings to ensure everything works as intended.

Final localized version of the sample app

Where to Go From Here?

You can download the completed project files by clicking on the Download Materials button at the top or bottom of the tutorial.

Take a look at an updated version of the l10n illustration:

Localization chart with the changes you made colored in

You’ve now colored some of the circles and tried out some of the available localization options. This is the furthest you can go with Buzz Kill.

By now, you’re hopefully seen there’s no better return on investment for your apps than localization.

In less than an hour, you opened Buzz Kill up to a multitude of new potential users without adding a single feature. And it only gets better: You don’t have to go through the internationalization process again when you add support for new locales. Just add a new .arb file and you’re good to go — assuming there aren’t any other localization requirements for you to meet.

For future projects, you can also look to the Intl package to help you with:

  • Date Parsing/Formatting: Not every country uses the U.S.’s month/day/year format for dates. Intl can handle that for you.
  • Genders: Intl lets you display text depending on a given subject gender, just as you can display singular or plural language based on a given number.
  • Bidirectional Text: When right-to-left meets left-to-right language, as when a Hebrew page embeds an English word, Intl can help.

Wanna know what happens if the user selects a language other than American English or Brazilian Portuguese? You can learn more about it in the official Flutter docs.

If you enjoyed this article or have any questions, let us know by using the comments section below.