Implementing Modular Localizations in Flutter
In Flutter, when dealing with a codebase consisting of multiple modules (packages), it is common to have a single, large localization file that is used by both the modules and the main project. However, this approach increases the modules’ dependency on the main project and reduces their reusability. For example, extracting a module to use in another project requires also transferring parts of the localization file. A more effective strategy is to develop individual localization classes for each module, along with a shared localization class for commonly used terms in the app.
In this article, I’ll guide you through the steps to achieve this, ensuring that each module remains self-contained and reusable across different projects.
Initial Setup 🛠
Let’s start by setting up a new Flutter project using flutter create
command. For this article, I’ll name the project as flutter_modular_localizations
.
flutter create flutter_modular_localizations
Module Creation 📦
Our application will consist of two modules: module_a
and module_b
. For demonstration purposes, each module will contain just one page and two localization keys. Start by creating a 'modules' directory at the root of your project. Then, inside the 'modules' directory, run the following commands to generate the modules:
flutter create --template=package module_a
flutter create --template=package module_b
Configuring Modules 🔧
Each module needs a l10n.yaml
file at its root to handle localizations. Here's the l10n.yaml
file for module_a
:
arb-dir: lib/l10n/arb
template-arb-file: app_en.arb
output-dir: lib/l10n/gen
output-class: ModuleALocalizations
output-localization-file: module_a_localizations.dart
synthetic-package: false # set this as false to import the delegate in the main project
nullable-getter: false
Make sure to add the flutter_localizations
dependency in the pubspec.yaml
file of each module:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
Then, in the lib/l10n
directory of each module, create a folder named arb
and inside that, add your .arb
files for each locale. For module_a
, the app_en.arb
file looks like this:
{
"welcome": "Welcome to Module A!",
"description": "This is a sample module demonstrating how to implement modular localizations in Flutter."
}
Configuring the Main Project 🔧
In the main project, update the pubspec.yaml
file to include the modules you previously created (module_a
and module_b
), as well as the flutter_localizations
package for localization support. I've also added flutter_bloc
to update selected locale, but feel free to use any package you're comfortable with.
dependencies:
flutter:
sdk: flutter
module_a:
path: ./modules/module_a
module_b:
path: ./modules/module_b
flutter_localizations:
sdk: flutter
flutter_bloc: ^8.1.5
Then, set up the l10n.yaml
file at the root of your main project like this:
arb-dir: lib/app/l10n/arb
template-arb-file: app_en.arb
output-dir: lib/app/l10n/gen
output-class: AppLocalizations
output-localization-file: app_localizations.dart
synthetic-package: false
nullable-getter: false
Once you have configured the l10n.yaml
file in the main directory of your project, create a folder named arb
inside the lib/l10n
directory. Inside this folder, add your .arb
files for each locale, similar to what we did for each module previously. For this example, the app_en.arb
file contains only the titles for the bottom navigation bar items. However, in a real project, this file could include all the common terms used in the app. Here is the app_en.arb
file for this example:
{
"navbarDestination1": "Module A",
"navbarDestination2": "Module B"
}
Generating Localization Classes ⚙️
Once you’ve added all the necessary localization keys, execute the following script in your terminal from the root directory of your project to generate localization classes across all modules and the main project.
# Execute the command at the root of the project
echo "Generating localizations at the root of the project"
flutter gen-l10n
cd modules
for d in */; do
echo "Generating localizations in $d"
cd "$d"
flutter gen-l10n
cd ..
done
Using the Generated Localization Classes 💻
Once the localization classes have been successfully generated, it’s time to use them in your application. Each module, as well as the main application, now has its own set of localization classes ModuleALocalizations
, ModuleBLocalizations
, and AppLocalizations
. These classes allow you to access the localized strings defined in their respective .arb
files. For example, to display the 'Welcome to Module A!' message from module_a
, you would use:
Text(ModuleALocalizations.of(context).welcome)
Managing Localization Delegates 🔗
In the lib/l10n
directory of your main project, create a file named app_l10n.dart
. Inside this file, define a class called AppL10n
that will store the supported locales and their corresponding localization delegates. It's important to remember that whenever you add a new module to your app, you must also add its localization delegates to the localizationsDelegates
array. Here's the AppL10n
class with the delegates from module_a
and module_b
:
class AppL10n {
AppL10n._();
static const enUS = Locale('en', 'US');
static const trTR = Locale('tr', 'TR');
static const List<Locale> supportedLocales = [enUS, trTR];
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = [
...AppLocalizations.localizationsDelegates,
// Add localization delegates of the each module
...ModuleALocalizations.localizationsDelegates,
...ModuleBLocalizations.localizationsDelegates,
];
}
Configuring the main.dar
t File 🔧
We are close to the end. Open the main.dart
file in your project, and then provide the localizationsDelegates
and supportedLocales
parameters to the MaterialApp
widget, which we previously defined in the AppL10n
class. I've also wrapped the MaterialApp
with BlocProvider
and BlocBuilder
to change the selected locale at runtime.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => AppL10nCubit(),
child: BlocBuilder<AppL10nCubit, AppL10nState>(
builder: (context, state) {
return MaterialApp(
debugShowCheckedModeBanner: false,
locale: state.currentLocale,
localizationsDelegates: AppL10n.localizationsDelegates,
supportedLocales: AppL10n.supportedLocales,
home: const NavbarView(),
);
},
),
);
}
}
And we are done. Run the project, and take a look at how everything comes together. Here is a GIF showing the completed project in action.
If you notice any errors or have suggestions for how to make this guide better, open an issue on the GitHub repository.