Flutter Navigator Localization: Route Management for Multilingual Apps
Navigator is Flutter's route management widget that maintains a stack of Route objects and provides methods for pushing, popping, and replacing screens. In multilingual applications, Navigator ensures that localization context flows correctly across route transitions, translated page titles appear in the system back button, route-based locale switching works smoothly, and modal routes display localized barrier labels.
Understanding Navigator in Localization Context
Navigator manages a stack of pages, handling transitions between screens using named routes, generated routes, or imperative push/pop calls. For multilingual apps, this enables:
- Localization context propagation to all pushed routes automatically
- Translated route titles for system-level back navigation
- Locale-aware route generation for deep linking
- Modal barrier semantics with localized dismissal labels
Why Navigator Matters for Multilingual Apps
Navigator provides:
- Context propagation: Every pushed route inherits the localization context from MaterialApp
- Localized transitions: Page transition labels and semantics adapt to the active language
- Deep link routing: URL-based navigation with locale-prefixed paths
- Modal accessibility: Barrier dismiss labels for dialogs and bottom sheets in the active language
Basic Navigator Implementation
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedNavigatorExample extends StatelessWidget {
const LocalizedNavigatorExample({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.homeTitle)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FilledButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DetailPage(),
),
);
},
child: Text(l10n.viewDetailsButton),
),
const SizedBox(height: 16),
OutlinedButton(
onPressed: () {
Navigator.pushNamed(context, '/settings');
},
child: Text(l10n.settingsButton),
),
],
),
),
);
}
}
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.detailsTitle)),
body: Padding(
padding: const EdgeInsets.all(16),
child: Text(l10n.detailsContent),
),
);
}
}
Advanced Navigator Patterns for Localization
Named Routes with Localized Page Titles
Define named routes where each page receives localized content through the inherited localization context.
class LocalizedRoutedApp extends StatelessWidget {
const LocalizedRoutedApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
initialRoute: '/',
routes: {
'/': (context) => const HomePage(),
'/profile': (context) => const ProfilePage(),
'/settings': (context) => const SettingsPage(),
},
onUnknownRoute: (settings) {
return MaterialPageRoute(
builder: (context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.notFoundTitle)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 64),
const SizedBox(height: 16),
Text(
l10n.notFoundMessage,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 16),
FilledButton(
onPressed: () => Navigator.pushNamedAndRemoveUntil(
context, '/', (_) => false,
),
child: Text(l10n.goHomeButton),
),
],
),
),
);
},
);
},
);
}
}
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.profileTitle)),
body: Padding(
padding: const EdgeInsets.all(16),
child: Text(l10n.profileContent),
),
);
}
}
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.settingsTitle)),
body: Padding(
padding: const EdgeInsets.all(16),
child: Text(l10n.settingsContent),
),
);
}
}
Passing Localized Arguments Between Routes
Routes often receive data that should be displayed with localized formatting -- dates, currencies, and parameterized messages.
class OrderConfirmationRoute extends StatelessWidget {
const OrderConfirmationRoute({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
final orderId = args['orderId'] as String;
final total = args['total'] as double;
return Scaffold(
appBar: AppBar(title: Text(l10n.orderConfirmationTitle)),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.check_circle,
size: 64,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 16),
Text(
l10n.orderSuccessMessage,
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 8),
Text(
l10n.orderIdLabel(orderId),
style: Theme.of(context).textTheme.bodyLarge,
),
Text(
l10n.orderTotalLabel(total.toStringAsFixed(2)),
style: Theme.of(context).textTheme.bodyLarge,
),
const Spacer(),
SizedBox(
width: double.infinity,
child: FilledButton(
onPressed: () => Navigator.pushNamedAndRemoveUntil(
context, '/', (_) => false,
),
child: Text(l10n.backToHomeButton),
),
),
],
),
),
);
}
}
// Navigating to this route:
// Navigator.pushNamed(context, '/order-confirmation', arguments: {
// 'orderId': 'ORD-12345',
// 'total': 49.99,
// });
Modal Routes with Localized Barriers
Dialog and bottom sheet routes have barrier labels that screen readers announce. These must be localized.
class LocalizedModalNavigation extends StatelessWidget {
const LocalizedModalNavigation({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FilledButton(
onPressed: () {
showDialog(
context: context,
barrierLabel: l10n.dismissDialogLabel,
builder: (context) {
final dialogL10n = AppLocalizations.of(context)!;
return AlertDialog(
title: Text(dialogL10n.confirmActionTitle),
content: Text(dialogL10n.confirmActionMessage),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(dialogL10n.cancelButton),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
child: Text(dialogL10n.confirmButton),
),
],
);
},
);
},
child: Text(l10n.showDialogButton),
),
const SizedBox(height: 16),
OutlinedButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
final sheetL10n = AppLocalizations.of(context)!;
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
sheetL10n.chooseOptionTitle,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
ListTile(
leading: const Icon(Icons.camera),
title: Text(sheetL10n.takePhotoOption),
onTap: () => Navigator.pop(context),
),
ListTile(
leading: const Icon(Icons.photo_library),
title: Text(sheetL10n.chooseFromGalleryOption),
onTap: () => Navigator.pop(context),
),
],
),
);
},
);
},
child: Text(l10n.showBottomSheetButton),
),
],
),
);
}
}
RTL Support and Bidirectional Layouts
Navigator's page transitions automatically adapt to RTL -- slide transitions reverse direction, and back navigation uses the correct directional semantics.
class BidirectionalNavigation extends StatelessWidget {
const BidirectionalNavigation({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final isRtl = Directionality.of(context) == TextDirection.rtl;
return Scaffold(
appBar: AppBar(
title: Text(l10n.navigationTitle),
leading: IconButton(
icon: Icon(isRtl ? Icons.arrow_forward : Icons.arrow_back),
tooltip: l10n.goBackTooltip,
onPressed: () => Navigator.maybePop(context),
),
),
body: Padding(
padding: const EdgeInsetsDirectional.all(16),
child: Text(l10n.pageContent),
),
);
}
}
Testing Navigator Localization
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
void main() {
Widget buildTestApp({Locale locale = const Locale('en')}) {
return MaterialApp(
locale: locale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const LocalizedNavigatorExample(),
routes: {
'/settings': (_) => const SettingsPage(),
},
);
}
testWidgets('Navigation preserves localization context', (tester) async {
await tester.pumpWidget(buildTestApp());
await tester.pumpAndSettle();
await tester.tap(find.byType(FilledButton).first);
await tester.pumpAndSettle();
expect(find.byType(DetailPage), findsOneWidget);
});
testWidgets('Navigation works in RTL', (tester) async {
await tester.pumpWidget(buildTestApp(locale: const Locale('ar')));
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
});
}
Best Practices
Localization context propagates automatically through Navigator -- pushed routes inherit delegates from MaterialApp without extra setup.
Use
onUnknownRouteto provide a localized 404 page for unmatched routes.Pass structured data as route arguments and format them with locale-aware methods in the destination page.
Provide localized
barrierLabelforshowDialogand modal routes to support screen readers.Use directional back icons (arrow_back/arrow_forward) based on
Directionality.of(context)for custom back buttons.Test navigation flows in RTL to verify that page transitions slide in the correct direction.
Conclusion
Navigator is the routing backbone of Flutter apps, and localization context flows through it seamlessly. Every pushed route, dialog, and bottom sheet inherits the localization delegates registered in MaterialApp. By providing localized route titles, barrier labels, error pages, and directional navigation icons, you ensure that the entire navigation experience is fully localized across all supported languages.