In the previous chapters, we've learned how we can create a simple Flutter application that can already run on multiple platforms, displaying hardcoded texts in a preferred language. We have one problem with the latter: With any luck, hundreds, thousands (or millions!) could use our apps after publishing them to the app stores. Apps supporting multiple languages and accessibility features can appeal to a broader audience worldwide.
a11y
: accessibilityi18n
: internationalizationl10n
: localization
In a nutshell, accessibility means that as many people can use our mobile app as possible, including those with disabilities, like visual or motor impairment.
If we want to deploy apps to users who speak a different language than our default one, having apps support the target audience's native language and culture can be appealing in those markets. Internationalization means we should develop an app in a way that makes it possible to localize values like text and layouts for each language the app supports.
By default, Flutter only supports US English localization. To add support for other languages, the Flutter team recommends using a package called flutter_localizations.
To use flutter_localizations, we have to add it to our pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
After a successfully completed pub get packages
command, we have to specify additional list type properties for MaterialApp
:
localizationsDelegates[]
,supportedLocales[]
.
Next, we have to add predefined global values (GlobalMaterialLocalizations.delegate
, const Locale(’hu’)
) to make the application's widgets adapt to the current language.
Besides the flutter_localizations
package, we can add the intl package too, which provides internationalization and localization facilities, including message translation, plurals and genders, date/number formatting and parsing, and bidirectional text support. Let's include this package too in the project's pubspec.yaml
file:
dependencies:
intl: ^0.17.0
Additionally, we should enable the generate flag in the pubspec.yaml
file:
flutter:
generate: true
This flag is essential to generate localizations. To configure where these files are generated, we can add a new l10n.yaml
file to the root directory of the project with the following content:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: l10n.dart
output-class: L10n
With this file we can specify the following settings:
- The input files (
app_en.arb
andapp_hu.arb
) will be located in${FLUTTER_PROJECT}/lib/l10n
, - The
app_en.arb
input file will provide the template, - The generated localizations will be placed in the
l10n.dart
file
In the generated l10n.dart
file, the class that we'll need is named L10n
.
We can add the default English localization to the app_en.arb
(ARB format = Application Resource Bundle) template file. For example, to store the localization of our home screen's title we can add the following key-value pairs:
{
"homeTitle": "Know your Santa",
"@homeTitle": {
"description": "Page title.",
"context": "HomePage"
}
}
The homeTitle
is the resource ID and its value is the localization string (“Know your Santa”).
Because the app_en.arb
file is the template file, we have to add an attribute item keyed by the original resource ID plus a prefix ‘@
’ character. In the attribute item optional we can use predefined resource attributes like:
description
: A short paragraph describing the resource and how it is being used by the app, and messages that need to be passed to the localization process and translators. Every attribute item must have adescription
as a requirement.context
: It describes the context in which this resource applies. When this piece of information is missing, it defaults to globalcontext
.
In the same directory, let's create the app_hu.arb
file for Hungarian translations and add a translation for the title string:
{
"homeTitle": "Ismerd meg a Mikulásod"
}
Here's the complete app_en.arb
file for the example project:
{
"@@locale": "en",
"homeTitle": "Know your Santa",
"@homeTitle": {
"description": "Page title.",
"context": "HomePage"
},
"homeAppbarTitle": "HomePage",
"@homeAppbarTitle": {
"description": "Page title of appbar.",
"context": "HomePage"
},
"homeCurrentLanguage": "Current language: {language}",
"@homeCurrentLanguage": {
"description": "Show the current language on the Home page",
"context": "HomePage",
"placeholders": {
"language": {}
}
},
"homeYourSanta": "Your santa",
"@homeYourSanta": {
"description": ""
},
"santaName": "Santa Claus",
"@santaName": {
"description": ""
},
"santaHat": "Hat",
"@santaOutfit1": {
"description": ""
},
"santaTie": "Tie",
"@santaOutfit2": {
"description": ""
},
"santaDescription": "Jingle bells jingle bells\nJingle all the way\nOh what fun it is to ride\nIn a one horse open sleigh\nJingle bells jingle bells\nJingle all the way\nOh what fun it is to ride\nIn a one horse open sleigh\n\nDashing through the snow\nIn a one horse open sleigh\nOver the fields we go\nLaughing all the way\nBells on bob tail ring\nMaking spirits bright\nOh What fun it is to ride \nA sleighing song tonight\n\nOh, oh jingle bells jingle bells\nJingle all the way\nOh what fun it is to ride\nIn a one horse open sleigh\n\nYou better not pout\nYou better not cry\nYou better not shout\nI'm telling you why\nSanta Claus is coming to town\n\nHe's making a list\nHe's checking it twice\nGonna find out\nWho's naughty or nice\nSanta Claus is coming to town\n\nHe sees you when you're sleeping\nHe knows if you're awake\nHe knows if you've been bad or good\nSo be good for goodness sake\n\nI am dreaming of a white Christmas\nJust like the ones I used to know\nWhere the tree tops glisten\nAnd children listen\nTo hear sleighbells in the snow\nI am dreaming of a white Christmas\nWith every Christmas card I write\nMay your days be merry and bright\nAnd may all your Christmases be\n\nOh, the weather outside is frightful\nBut the fire is so delightful\nAnd since we've got no place to go\nLet it snow, let it snow, let it snow\n\nOh, it doesn't show signs of stopping\nAnd I've brought some corn for popping\nThe lights are turned way down low\nLet it snow, let it snow, let it snow\n\nWhen we finally kiss goodnight\nHow I'll hate going out in the storm\nBut, if you really hold me tight\nAll the way home I'll be warm\n\nThe fire is slowly dying\nAnd my dear, we're still good by ing\nBut, as long as you love me so\nLet it snow, let it snow, let it snow\n\nHere comes Santa Claus\nHere comes Santa Claus\nRight down Santa Claus Lane\nVixen and Blitzen and all his reindeer\nPullin' on the reins\nBells are ringin', children singin'\nAll is merry and bright\nHang your stockings and say your prayers\nSanta Claus comes tonight\n\nHere comes Santa Claus\nHere comes Santa Claus\nRight down Santa Claus Lane\nHe's got a bag that's filled with toys\nFor boys and girls again\nHear those sleigh bells jingle jangle\nOh what a beautiful sight\nJump in bed, and cover your head\nSanta Claus comes tonight\nSanta Claus comes tonight",
"@santaDescription": {
"description": ""
}
}
And this is the complete app_hu.arb
file:
{
"@@locale": "hu",
"homeTitle": "Ismerd meg a Mikulásod",
"homeAppbarTitle": "Kezdőlap",
"homeCurrentLanguage": "Aktuális nyelv: {language}",
"homeYourSanta": "A mikulásod",
"santaName": "Mikulás",
"santaHat": "Kalap",
"santaTie": "Nyaktekerészeti mellfekvenc",
"santaDescription": "Hull a pelyhes fehér hó,\njöjj el kedves télapó.\nMinden gyermek várva vár,\nvidám ének hangja száll.\nVan zsákodban minden jó,\npiros alma, mogyoró.\nJöjj el hozzánk, várunk rád,\nkedves öreg télapó!\n\nNagyszakállú télapó,\njó gyermek barátja.\nCukrot, diót, mogyorót\nrejteget a zsákja.\nAmerre jár reggelig,\nkis cipőcske megtelik.\nMegtölti a télapó,\nha üresen látja."
}
Whenever we run the application the code generation takes place. We can see the generated files under ${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n
:
l10n.dart
,l10n_en.dart
,l10n_hu.dart
.
In the main.dart
file MaterialApp
needs to include L10n.delegate()
in the localisationDelegates
list, and the locales we support in the supportedLocales
list:
import 'package:flutter/material.dart';
import 'gen_l10n/l10n.dart';
import 'presentation/page/home/home_page.dart';
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter l10n demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.red,
),
localizationsDelegates: L10n.localizationsDelegates,
supportedLocales: L10n.supportedLocales,
home: HomePage(),
);
}
}
Callers can look up localized strings using an instance of the L10n
class returned by L10n.of(context)
. For instance, in the home_page.dart
file at the top of the build()
method of the HomePage
Widget
we can get a reference to the L10n
object and pass its homeTitle
value to the title text:
import 'package:flutter_gen/gen_l10n/l10n.dart';
/// (...)
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final L10n l10n = L10n.of(context);
return Scaffold(
/// (...)
body: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
/// (...)
Text(l10n.homeTitle),
/// (...)
],
),
),
);
}
}
As a result, users can see the home page according to the system language of their device:
en | hu |
---|---|
In some languages, for instance, in Arabic writing goes from the right to the left (RTL) instead of from the left to the right like in English or Hungarian. Localization for RTL languages needs the text direction to be changed.
To demonstrate RTL support we can add and .arb
file for Arabic string resources, and we use the start
and end
properties of the EdgeInsetsDirectional
class instead of the basic left
and right
of the EdgeInsets
class.
People with visual disabilities often use the default TalkBack (Android) or VoiceOver (iOS) assistants to read the content of screens for them. If we want to specify exactly what will these assistants say about our apps' screens, we can do that using the Semantics widget which has tons of properties regarding such settings. It can be used for analytics too.
For example, we can wrap one of our Text
widgets into a Semantics
widget, and we can add a label
property.
title: Semantics(
child: Text(l10n.homeAppbarTitle),
label: "This is the HomePage.",
),
Once we need to support multiple platforms with different display sizes and factors, we'll need to learn about responsive layouts and how to deal with them in Flutter.
We've learned that we can use the SizedBox
widget for presenting any space between widgets and it’s simpler than a Container
widget when we don’t need to specify borders and more properties. To achieve better scalability, we can consider using the FittedBox widget. With FittedBox
, we can easily and automatically scale up/down and position its child within itself using the fit
property, which can be familiar from the BoxFit
widget. It’s easy to use while being really powerful.
The Wrap widget is another very useful tool to handle scaling. This Wrap
widget arranges its children in multiple horizontal or vertical runs. When there is not enough space to fit one of its children, Wrap
creates a new run adjacent to the previous one on the cross axis.
MediaQuery always provides the size of the full application, info about the physical screen, etc, while Layoutbuilder provides the current parent's size only.
In this section, we will see how the flutter_screenutil package wraps the MediaQuery into a size config under the hood.
First of all, we need to initialize and set the fit size in the app.dart
file before we show any resizable UI like this:
import 'package:flutter_screenutil/flutter_screenutil.dart';
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// Set the fit size (fill in the screen size of the device in the design,in dp)
return ScreenUtilInit(
designSize: const Size(375, 812),
allowFontScaling: false,
builder: () => MaterialApp(
/// (...)
),
);
}
}
Note that the
designSize
property should be equal to the size of the design draft. Ideally, our UI development should be based on an existing design like the following with 375x812 size:
Under the hood, MediaQuery
provides important properties like:
pixelRatio
(device pixel density),- screen
width
, - screen
height
, viewInsets
andviewPadding
(these properties could be useful when we want to handle some padding issues caused by software keyboard),- size of the
appBar
, and thenotch
- if the device has one.
The
MediaQuery
widget also provides information about the current orientation. With that, we can easily handle the landscape mode too if we don’t want to disable it. We could enable and disable the different orientations in themain.dart
file if we add or remove one of them from thesetPreferredOrientations
list of theSystemChrome
class before we run our application.
After successfully initializing the ScreenUtil
we could use the setHeight()
and setWidth()
functions to adapt to screen height/width.
Besides those, we can also use the .w()
, .h()
, and .r()
extension functions to make our code more readable.
🚨 Don’t forget that we can only achieve responsiveness if we use the above-mentioned extension functions for all size properties (including font sizes) on each widget for each screen. If we follow this rule, we should not be surprised even when our app runs on a small screen, or in a small browser window.
Normal Android Device (1440x2560) dpi | Small Device (240x320) ldpi | Galaxy Tab S7 (1600x2560) hdpi |
---|---|---|
iPhone 11 Pro | WEB |
---|---|
-
Developing for Multiple Screen Sizes and Orientations in Flutter by Deven Joshi
-
Build Responsive UIs in Flutter by Raouf Rahiche
-
How to build a responsive layout in Flutter by Souvik Biswas (codemagic)
-
responsive_builder - Another interesting package
-
Used icon by Icons8