Internationalization and Localization in Flutter

Ataxan Rahimli
9 min readMar 7, 2024

--

There are some type of concepts play roles in shaping user experiences across diverse geographies and cultures. Translation (t9n), localization (l10n), internationalization (i18n), and globalization (g11n). We will focus on localization (l10n), internationalization (i18n) concepts which using on Flutter ecosystem

Internationalization (i18n): Internationalization involves designing your app in a way that allows it to be easily adapted to different languages and regions without engineering changes. In Flutter, you typically internationalize your app by externalizing all of the translatable strings and assets.

Here are the steps to internationalize your Flutter app:

  • Externalize all translatable strings and assets using Flutter's intl package or Dart's built-in Intl class.
  • Organize your strings and assets into resource files (usually .arb files).
  • Define different translations for each language your app supports.
  • Load the appropriate translations based on the user's locale.

Localization (l10n): Localization involves adapting your app to a specific locale, including translations, date and time formats, currency formats, and other regional preferences. In Flutter, you achieve localization by loading locale-specific resources based on the user's language and region preferences.

Here's how to implement localization in Flutter:

  • Define locale-specific resources for each supported language and region.
  • Use the appropriate date, time, and number formatting for the user's locale.

1. Implementing Internationalization in Flutter

Implementing internationalization (i18n) and localization (l10n) in Flutter is straightforward in an official way. You’ll require the flutter_localizations package from the Flutter SDK and the intl package.

// pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_localizations: sdk: flutter
intl: any

We create a l10n.yaml in our root directory:

// l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb output-localization-file: app_localizations.dart

Populate your lib/l10n directory with .arb files, starting with the app_en.arb template file. For instance:

// app_en.arb
{
"helloWorld": "Hello World!",
"@helloWorld": {
"description": "Give as much context as possible here"
}
}

Next we create corresponding .arb file for German language

// app_de.arb
{
"helloWorld": "Hallo Welt!",
}

After that we will add build_runner package to dev dependency and write some commands to generate translations files in .dart_tool folder

Execute the command: flutter pub add --dev build_runner

or add build_runner to dev_dependencies in pubspec.yaml

dev_dependencies:
flutter_test:
sdk: flutter
build_runner: any

And execute the command to complete generation : flutter pub run build_runner build --delete-conflicting-outputs

Next step is utilizing below:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
main() {
runApp(const MaterialApp(
title: 'Localizations Sample App',
locale: Locale('en'),
localizationsDelegates:
AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: MyHomeIntl(),
));
}

class MyHomeIntl extends StatelessWidget {
const MyHomeIntl({super.key});
@override
Widget build(BuildContext context) { return Scaffold(
body: Column(
children: [
Text(AppLocalizations.of(context).helloWorld),
],
), );
} }

Application Resource Bundle (ARB)

ARB is a localization resource format based on JSON, making it simple, extensible, and easy to use. Understanding it will help you implement advanced concepts. There is a structure of .arb file

{
"languageKey": "Anything....",
"@languageKey": { // explain and expand the language key
"type": "text", // is possible to use: text, int, String, DateTime
"context": "HomePage", // declare where it will be used
"description": "Give as much context as possible here"
"placeholders": { // dynamic paramters is possible to pass
"dyanmicValue1": {
"type": "String",
"description": "String example description"
},
"dyanmicValue2": {
"type": "DateTime",
"format": "yMd",
"description": "DateTime example description"
}
"dyanmicValue3": {
"type": "int",
"description": "Int example description"
},
"dyanmicValue4": {
"type": "double",
"description": "Double example description"
}
}
}
}

Lets use the structure in a basic example below

{
"welcomeText": "Welcome to our system",
"@welcomeText": {
"description": "Short description for welcome text"
}
}

Let’s explore more in a advanced example

{
"myInfoDetails": "My name is {fullName}, my birthday is {birthday}, I am {age} years old",
"@myInfoDetails": {
"type": "text",
"context": "HomePage",
"description": "Give as much context as possible here"
"placeholders": {
"fullName": {
"type": "String",
"description": "User full name"
},
"birthday": {
"type": "DateTime",
"format": "yyyyMd",
"description": "User birthday"
}
"age": {
"type": "int",
"description": "User age"
}
}
}
}

Let’s use both below to understand better:

@override
Widget build(BuildContext context) {
return Column(
children: [

// Welcome to our system
Text(AppLocalizations.of(context)!.welcomeText,),

// My name is John Doe, my birthday is 2000 January 1, I am 24 years old
Text(
AppLocalizations.of(context)!.myInfoDetails(
'John Doe',
DateTime.utc(2000, 1, 1),
24,
),
),

],
);
}

2. Advanced tips and tricks

sdüeüe

# l10n.yaml
synthetic-package: false
output-dir: lib/l10n/generated
nullable-getter: false
output-class: S
untranslated-messages-file: l10n_errors.txt

The output-dir parameter specifies the directory where the generated localiza- tion classes will be placed. You can set this option to generate the localization code in a location different from the default. If output-dir is not specified, the generated files will be placed in the same directory as your ARB files, as defined by arb-dir.

The synthetic-package flag is set to true by default, meaning the generated output files are part of a synthetic package and are not placed in the project’s directory structure. However, if you set synthetic-package to false, the localization files will be generated directly in the directory specified by arb-dir or output-dir if it’s specified.

Avoid null checking: You can set nullable-getter to false to ensure that getters in the generated localizations class are non-nullable. This change removes the need for repeated null checks when accessing localization messages.

# l10n.yaml
nullable-getter: false
// in Flutter
AppLocalizations.of(context).helloWorld

Override the locale

Sometimes, you may need to override the current locale for a specific application part, which is entirely possible. You can use the Localizations.override factory constructor. Here’s an example where the helloWorld message is displayed in both the default locale and an overridden Spanish locale:

@override
Widget build(BuildContext context) {
return Column(
children: [
// Display message in the default locale Text(
AppLocalizations.of(context)!.helloWorld,
),
// Override locale to Spanish for the next Text widget
Localizations.override(
context: context,
locale: const Locale('es'),
child: Builder(
builder: (BuildContext context) {
// Display message in the overridden locale return Text(
AppLocalizations.of(context)!.helloWorld,
);
}, ),
),
]
);
}

In this code, the first Text widget uses the default locale, while the second Text widget, wrapped in Localizations.override, displays the same message but in Spanish (es locale).

Requires resource attribute

You have seen throughout this chapter how important it is to pass along context and metadata via attributes to clarify the context. You can make it a requirement.

use required-resource-attributes: true in your l10n.yaml file, and you get an error while running the flutter gen-l10n command if the attributes are missing.

Output class name

If you want to change the output class name, use the output-class parameter with your class name in the l10n.yaml file.

output-class: S

For example, S here is shorter.

Extracting Localization Data

When localizing an app with the Dart intl package, one of the steps is to extract and generate localization files. The process of doing this may vary depending on the version being used. However, it’s worth noting that it’s possible to extract data using this tool. This clarifies things for you.

Missing Translations

It’s always nice to know what has been missed in translation so you can force the gen-l10n command to give you a report.

# l10n.yaml

untranslated-messages-file: l10n_errors.txt
You can use this file to ensure no missing or translation errors. This is particularly

useful for CD/CI or pre-push git hooks.

Naming Conventions for .arb Files in Flutter Localization

In Flutter localization, .arb files are named following a convention that includes ISO language and regional codes. The file names should contain underscores and adhere to the ISO 639–1 language codes, with the option to add ISO 3166 regional codes for different language regions.

English Locales:

US English: app_en.arb
Great Britain: app_en_GB.arb Australia: app_en_AU.arb

Other Languages:

– France (French): app_fr.arb

– French Canada: app_fr_CA.arb

You’d only need the base file app_en.arb if only supporting English across all regions.

Always make sure your template file exists as the default file to fallback. Other- wise, your project will not compile.

In Flutter’s .arb files, the @@locale key, although optional, serves as a validation tool to ensure the file is named correctly according to its locale. For instance, in app_en.arb, the presence of @@locale: “en” confirms that the file is appro- priately named for the English locale, with _en being the crucial part of the file name. An incorrectly named file not matching its @@locale value will result in an exception.


// /lib/l10n/app_nb_NO.arb
{
"@@locate": "nb_NO", "helloWorld": "Hallo Verden!",
}

Additionally, in Flutter, the order of locales in supportedLocales is critical for accurate locale resolution, using the basicLocaleListResolution algorithm. This prioritizes matching locales by language code (languageCode), script code (scriptCode), and country code (countryCode). For languages with multiple scripts, specifying scriptCode is crucial.

final supportedLocales = <Locale>[
Locale.fromSubtags(languageCode: 'zh'), // Generic Chinese Locale.fromSubtags(
languageCode: 'zh',
scriptCode: 'Hans',
), // Simplified Chinese Locale.fromSubtags(
languageCode: 'zh',
scriptCode: 'Hant',
), // Traditional Chinese Locale.fromSubtags(
languageCode: 'zh',
scriptCode: 'Hans',
countryCode: 'CN',
), // Mainland China Locale.fromSubtags(
languageCode: 'zh',
scriptCode: 'Hant',
countryCode: 'TW',
), // Taiwan Locale.fromSubtags(
languageCode: 'zh',
scriptCode: 'Hant',
countryCode: 'HK',
), // Hong Kong ]

This approach ensures precise locale matching for diverse user settings, preventing issues like a Simplified Chinese user defaulting to Traditional Chinese in Taiwan.

Tools

It can be helpful to use certain tools when working with ARB files. This is not because you need help to handle them alone, but because as a project grows, it can quickly become overwhelming. Some useful tools include arb_utils, poEditor, or similar tools designed to assist with .arb files.

Real-time uptime mechanism

It is always advisable to have a tool or a process to help you add translations on the fly. This is because there may be instances where someone may want to change the translation quickly, and you would want to immediately start the next app release or delay the app release due to a small translation error. Therefore, it’s best to have a solution that can help you handle such situations efficiently.

Defining a class for the app’s localized resources

If you need to make multiple customizations, it’s best to begin creating your Local- ization class and utilize the built-in text extraction feature from the intl package. This doesn’t prevent you from using ARB files and the underlying mechanism, but you’ll have to include the localization class that meets your specific needs.

Right-to-Left (RTL) languages in Flutter

Integrating support for right-to-left (RTL) languages is essential for ensuring a seamless user experience in Flutter apps across diverse linguistic environments. Flutter provides built-in mechanisms to handle RTL layouts, text directionality, and overall UI adjustments to accommodate languages such as Arabic, Hebrew, and Persian, which are written from right to left.

To enable RTL support in your Flutter app, follow these steps:

  1. Set Text Direction: Use the textDirection property to specify the text directionality of widgets. Set it to TextDirection.rtl for RTL languages and TextDirection.ltr for left-to-right (LTR) languages. For example:
Text(
'مرحبا بالعالم',
textDirection: TextDirection.rtl,
)

2. Handle Layout Direction: Ensure proper layout directionality by using Directionality widgets. Wrap your app's root widget with Directionality and specify the text directionality.

Directionality(
textDirection: TextDirection.rtl,
child: MyApp(),
)

3. Support RTL Icons: Some icons may need to be mirrored for RTL layouts. Flutter’s Icon widget provides the matchTextDirection property, which automatically flips icons based on the text directionality.

Icon(
Icons.arrow_back,
matchTextDirection: true,
)

4. Handle Alignment: Adjust widget alignments to reflect RTL layouts properly. Use properties like mainAxisAlignment and crossAxisAlignment in Row, Column, and Flex widgets accordingly.

5. Test RTL Layouts: Test your app thoroughly with RTL languages enabled to ensure that UI elements, text alignment, and overall layout behave as expected.

By incorporating these techniques, Flutter apps can seamlessly adapt to RTL languages, providing an intuitive and localized experience for users across different language preferences. It’s crucial to consider RTL support early in the development process to ensure a smooth user experience for international audiences.

Conclusion

In conclusion, Flutter offers powerful tools and approaches for internationalization (i18n) and localization (l10n), enabling developers to create apps that cater to diverse audiences worldwide. By leveraging Flutter’s built-in support for i18n and l10n, developers can seamlessly translate their apps into multiple languages, adapt layouts and content based on locale preferences, and provide a consistent user experience across different regions.

Throughout this article, we’ve explored various techniques and best practices for internationalizing and localizing Flutter apps, including using the Intl package for message formatting and pluralization, utilizing MaterialApp’s localizationsDelegates and supportedLocales properties for managing app translations, and incorporating flutter_localizations for handling locale-specific widgets and resources.

Additionally, we’ve discussed how to handle right-to-left (RTL) languages and dynamically switch between different locales at runtime, ensuring a fluid and seamless experience for users worldwide. By following these guidelines and incorporating i18n and l10n principles into your Flutter app development workflow, you can create engaging, accessible, and culturally relevant experiences for a global audience.

As Flutter continues to evolve, we anticipate further enhancements and refinements to its internationalization and localization capabilities, empowering developers to reach even broader audiences and markets. Whether you’re targeting users in a specific region or aiming for global expansion, embracing i18n and l10n practices in your Flutter projects is essential for fostering inclusivity, accessibility, and user satisfaction on a global scale.

--

--

No responses yet