Implementing Modular Localizations in Flutter

cem256 - Cem Avcı
4 min readApr 19, 2024

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.dart 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.

--

--