In today’s globalized world, creating applications that cater to diverse audiences is crucial. Flutter, Google’s UI toolkit, provides excellent support for internationalization (i18n) and localization (l10n). This means adapting your app’s UI to suit different cultures, languages, and regional preferences. This post dives deep into how to adapt your UI for different cultures in Flutter.
What is Internationalization (i18n) and Localization (l10n)?
- Internationalization (i18n): The process of designing an application so that it can be adapted to various languages and regions without engineering changes.
- Localization (l10n): The process of adapting an internationalized application for a specific region or language by translating text and adding locale-specific components.
Why Adapt Your UI for Different Cultures?
- Improved User Experience: Users prefer applications in their native language and cultural context.
- Increased Market Reach: Adapting your UI can unlock new markets and increase your app’s user base.
- Compliance: Some regions require applications to be available in local languages.
Key Concepts in Flutter i18n and l10n
Before diving into implementation, it’s essential to understand these core concepts:
- Locale: A combination of language and country codes that identifies a specific regional variant. Example:
en_US(English, United States),fr_FR(French, France). - Messages/Translations: Localized text that appears in your application’s UI. These messages are typically stored in separate files.
- Intl Package: The Flutter
intlpackage provides essential functions for date/time formatting, number formatting, pluralization, and message translation. - LocalizationDelegate: A class that loads the localized resources for a given locale.
- Localizations Widget: A widget that manages the app’s locale and provides access to localized resources.
Steps to Adapt Your UI for Different Cultures in Flutter
Step 1: Add the intl and flutter_localizations Dependencies
Add the required dependencies to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
intl: ^0.18.0
flutter_localizations:
sdk: flutter
Run flutter pub get in your terminal to download the dependencies.
Step 2: Configure flutter_localizations
In your MaterialApp widget, configure the localizationsDelegates and supportedLocales:
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Localization Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
Locale('en', 'US'), // English, United States
Locale('fr', 'FR'), // French, France
],
home: MyHomePage(title: 'Flutter Localization Demo'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Hello, World!', // This should be localized
),
],
),
),
);
}
}
The localizationsDelegates array includes standard delegates for Flutter widgets, ensuring that the built-in components also support localization.
The supportedLocales array specifies the locales that your application supports.
Step 3: Create Localization Files
Create a directory named lib/l10n (or a name of your choosing) to store your localization files.
Create a file named app_localizations.dart within this directory. This file will contain the code to load and access localized messages.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class AppLocalizations {
AppLocalizations(this.locale);
final Locale locale;
static AppLocalizations? of(BuildContext context) {
return Localizations.of(context, AppLocalizations);
}
static const LocalizationsDelegate delegate =
_AppLocalizationsDelegate();
static Future load(Locale locale) async {
final String name =
Intl.canonicalizedLocale(locale.toString());
Intl.defaultLocale = name;
return AppLocalizations(locale);
}
String get helloWorld {
return Intl.message(
'Hello, World!',
name: 'helloWorld',
desc: 'The conventional newborn greeting',
);
}
String get welcomeMessage {
return Intl.message(
"Welcome to our App!",
name: "welcomeMessage",
desc: "The initial welcome message to users."
);
}
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate {
const _AppLocalizationsDelegate();
@override
bool isSupported(Locale locale) {
return ['en', 'fr'].contains(locale.languageCode);
}
@override
Future load(Locale locale) async {
return AppLocalizations.load(locale);
}
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
Now, you need to generate the necessary .arb files for the messages. You can either create them manually or use a package like flutter_intl. Let’s create them manually for this example:
Create lib/l10n/app_en.arb:
{
"@@locale": "en",
"helloWorld": "Hello, World!",
"welcomeMessage": "Welcome to our App!"
}
Create lib/l10n/app_fr.arb:
{
"@@locale": "fr",
"helloWorld": "Bonjour, le monde !",
"welcomeMessage": "Bienvenue dans notre application !"
}
Step 4: Update MaterialApp with Custom Delegate
Update your MaterialApp widget to include the custom AppLocalizations.delegate:
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_localization_demo/l10n/app_localizations.dart'; // Import your AppLocalizations
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Localization Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
localizationsDelegates: [
AppLocalizations.delegate, // Add your custom delegate
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
Locale('en', 'US'), // English, United States
Locale('fr', 'FR'), // French, France
],
home: MyHomePage(title: 'Flutter Localization Demo'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
AppLocalizations.of(context)!.helloWorld, // Access localized text
),
Text(
AppLocalizations.of(context)!.welcomeMessage, // Access localized text
),
],
),
),
);
}
}
In your UI, access the localized text using AppLocalizations.of(context)!.helloWorld.
Step 5: Dynamically Change the Locale
To allow users to change the locale, you can use the setState method of a StatefulWidget to update the MaterialApp‘s locale.
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_localization_demo/l10n/app_localizations.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
Locale _locale = Locale('en', 'US'); // Default locale
void _changeLocale(Locale newLocale) {
setState(() {
_locale = newLocale;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Localization Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
locale: _locale, // Set the current locale
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
Locale('en', 'US'), // English, United States
Locale('fr', 'FR'), // French, France
],
home: MyHomePage(
title: 'Flutter Localization Demo',
changeLocale: _changeLocale,
),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key, required this.title, required this.changeLocale})
: super(key: key);
final String title;
final Function(Locale) changeLocale;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
AppLocalizations.of(context)!.helloWorld,
),
Text(
AppLocalizations.of(context)!.welcomeMessage,
),
DropdownButton(
value: Localizations.localeOf(context),
items: [
DropdownMenuItem(
child: Text('English'),
value: Locale('en', 'US'),
),
DropdownMenuItem(
child: Text('Français'),
value: Locale('fr', 'FR'),
),
],
onChanged: (Locale? newLocale) {
if (newLocale != null) {
changeLocale(newLocale);
}
},
),
],
),
),
);
}
}
In this example, a DropdownButton allows users to select their preferred locale, and the UI updates dynamically.
Formatting Dates, Numbers, and Currencies
The intl package provides functions for formatting dates, numbers, and currencies according to the user’s locale.
import 'package:intl/intl.dart';
String formatDate(DateTime date, Locale locale) {
final formatter = DateFormat.yMd(locale.toString());
return formatter.format(date);
}
String formatCurrency(double amount, Locale locale, String currencyCode) {
final format = NumberFormat.currency(locale: locale.toString(), symbol: currencyCode);
return format.format(amount);
}
Usage example:
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class FormattingExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
final now = DateTime.now();
final amount = 1234.56;
final currentLocale = Localizations.localeOf(context);
return Scaffold(
appBar: AppBar(
title: Text('Formatting Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Formatted Date: ${DateFormat.yMd(currentLocale.toString()).format(now)}'),
Text('Formatted Currency: ${NumberFormat.currency(locale: currentLocale.toString(), symbol: '$').format(amount)}'),
],
),
),
);
}
}
Handling Pluralization
Different languages have different pluralization rules. The intl package provides the plural method for handling this.
import 'package:intl/intl.dart';
String pluralize(int count, Locale locale) {
return Intl.plural(
count,
zero: 'No items',
one: '1 item',
other: '$count items',
locale: locale.toString(),
);
}
Example of usage:
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class PluralizationExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
final itemCount = 5;
final currentLocale = Localizations.localeOf(context);
return Scaffold(
appBar: AppBar(
title: Text('Pluralization Example'),
),
body: Center(
child: Text(
Intl.plural(
itemCount,
zero: 'No items',
one: '1 item',
other: '$itemCount items',
locale: currentLocale.toString(),
),
),
),
);
}
}
Handling Right-to-Left (RTL) Languages
Flutter provides automatic support for RTL layouts. Use the Directionality widget to ensure your UI correctly handles RTL languages like Arabic and Hebrew.
Directionality(
textDirection: TextDirection.rtl,
child: // Your RTL UI components
)
Best Practices
- Use Clear and Concise Keys: Ensure your translation keys are meaningful and easy to understand.
- Separate Logic from UI: Keep localization logic separate from your UI components to improve maintainability.
- Test Thoroughly: Test your application in multiple locales to ensure correctness.
- Use a Localization Management Tool: Consider using tools like Phrase or Lokalise to manage your translations.
Conclusion
Adapting your UI for different cultures in Flutter is essential for creating inclusive and globally accessible applications. By using the intl package, localization delegates, and best practices, you can ensure that your app provides a great experience for users around the world. Embracing i18n and l10n is not just about translating text; it’s about respecting and understanding the diverse needs of your users.