Flutter Scaffold Localization: App Structure for Multilingual Interfaces
Scaffold is a Flutter widget that implements the basic Material Design visual layout structure. In multilingual applications, Scaffold provides the foundational app structure that automatically adapts to different text directions, supporting RTL languages with properly mirrored drawer positions and navigation.
Understanding Scaffold in Localization Context
Scaffold provides a framework for app bars, drawers, bottom navigation, floating action buttons, and body content. For multilingual apps, this enables:
- Automatic RTL drawer positioning
- Direction-aware floating action button placement
- Localized app bar titles and actions
- Consistent navigation patterns across all languages
Why Scaffold Matters for Multilingual Apps
Scaffold provides:
- RTL support: Drawer automatically moves to the right in RTL
- Consistent structure: Same layout patterns across locales
- Theme integration: Respects localized theme settings
- Navigation scaffolding: Standardized navigation for all languages
Basic Scaffold Implementation
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedScaffoldExample extends StatelessWidget {
const LocalizedScaffoldExample({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.appTitle),
actions: [
IconButton(
icon: const Icon(Icons.search),
tooltip: l10n.searchTooltip,
onPressed: () {},
),
IconButton(
icon: const Icon(Icons.more_vert),
tooltip: l10n.moreTooltip,
onPressed: () {},
),
],
),
drawer: Drawer(
child: ListView(
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
child: Text(
l10n.menuHeader,
style: Theme.of(context).textTheme.headlineSmall,
),
),
ListTile(
leading: const Icon(Icons.home),
title: Text(l10n.menuHome),
onTap: () => Navigator.pop(context),
),
ListTile(
leading: const Icon(Icons.settings),
title: Text(l10n.menuSettings),
onTap: () => Navigator.pop(context),
),
],
),
),
body: Center(
child: Text(l10n.bodyContent),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: l10n.addTooltip,
child: const Icon(Icons.add),
),
);
}
}
Scaffold with Bottom Navigation
Localized Bottom Navigation Bar
class LocalizedBottomNavScaffold extends StatefulWidget {
const LocalizedBottomNavScaffold({super.key});
@override
State<LocalizedBottomNavScaffold> createState() =>
_LocalizedBottomNavScaffoldState();
}
class _LocalizedBottomNavScaffoldState
extends State<LocalizedBottomNavScaffold> {
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final pages = [
Center(child: Text(l10n.homePageContent)),
Center(child: Text(l10n.searchPageContent)),
Center(child: Text(l10n.profilePageContent)),
];
return Scaffold(
appBar: AppBar(
title: Text(_getTitle(l10n)),
),
body: pages[_currentIndex],
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
setState(() => _currentIndex = index);
},
destinations: [
NavigationDestination(
icon: const Icon(Icons.home_outlined),
selectedIcon: const Icon(Icons.home),
label: l10n.navHome,
),
NavigationDestination(
icon: const Icon(Icons.search_outlined),
selectedIcon: const Icon(Icons.search),
label: l10n.navSearch,
),
NavigationDestination(
icon: const Icon(Icons.person_outlined),
selectedIcon: const Icon(Icons.person),
label: l10n.navProfile,
),
],
),
);
}
String _getTitle(AppLocalizations l10n) {
switch (_currentIndex) {
case 0:
return l10n.navHome;
case 1:
return l10n.navSearch;
case 2:
return l10n.navProfile;
default:
return l10n.appTitle;
}
}
}
Scaffold with End Drawer
RTL-Aware End Drawer
class LocalizedEndDrawerScaffold extends StatelessWidget {
const LocalizedEndDrawerScaffold({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.appTitle),
actions: [
Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.filter_list),
tooltip: l10n.filterTooltip,
onPressed: () {
Scaffold.of(context).openEndDrawer();
},
),
),
],
),
endDrawer: Drawer(
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
l10n.filterTitle,
style: Theme.of(context).textTheme.titleLarge,
),
),
const Divider(),
CheckboxListTile(
title: Text(l10n.filterOption1),
value: true,
onChanged: (_) {},
),
CheckboxListTile(
title: Text(l10n.filterOption2),
value: false,
onChanged: (_) {},
),
CheckboxListTile(
title: Text(l10n.filterOption3),
value: true,
onChanged: (_) {},
),
const Spacer(),
Padding(
padding: const EdgeInsets.all(16),
child: FilledButton(
onPressed: () => Navigator.pop(context),
child: Text(l10n.applyFilters),
),
),
],
),
),
),
body: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: 20,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text('${l10n.itemLabel} ${index + 1}'),
),
);
},
),
);
}
}
Scaffold with FAB Positions
Direction-Aware FAB Placement
class LocalizedFabScaffold extends StatelessWidget {
const LocalizedFabScaffold({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.appTitle),
),
body: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: 30,
itemBuilder: (context, index) {
return Card(
child: ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text('${l10n.listItem} ${index + 1}'),
subtitle: Text(l10n.listItemSubtitle),
),
);
},
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {},
icon: const Icon(Icons.add),
label: Text(l10n.createNew),
),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
);
}
}
Center Docked FAB
class LocalizedCenterDockedFab extends StatelessWidget {
const LocalizedCenterDockedFab({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.appTitle),
),
body: Center(
child: Text(l10n.mainContent),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: l10n.addTooltip,
child: const Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
shape: const CircularNotchedRectangle(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
icon: const Icon(Icons.home),
tooltip: l10n.navHome,
onPressed: () {},
),
IconButton(
icon: const Icon(Icons.search),
tooltip: l10n.navSearch,
onPressed: () {},
),
const SizedBox(width: 48), // Space for FAB
IconButton(
icon: const Icon(Icons.favorite),
tooltip: l10n.navFavorites,
onPressed: () {},
),
IconButton(
icon: const Icon(Icons.person),
tooltip: l10n.navProfile,
onPressed: () {},
),
],
),
),
);
}
}
Persistent Bottom Sheet
Localized Bottom Sheet
class LocalizedBottomSheetScaffold extends StatefulWidget {
const LocalizedBottomSheetScaffold({super.key});
@override
State<LocalizedBottomSheetScaffold> createState() =>
_LocalizedBottomSheetScaffoldState();
}
class _LocalizedBottomSheetScaffoldState
extends State<LocalizedBottomSheetScaffold> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
void _showBottomSheet() {
final l10n = AppLocalizations.of(context)!;
_scaffoldKey.currentState?.showBottomSheet(
(context) => Container(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
l10n.bottomSheetTitle,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
Text(l10n.bottomSheetContent),
const SizedBox(height: 24),
FilledButton(
onPressed: () => Navigator.pop(context),
child: Text(l10n.closeButton),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(l10n.appTitle),
),
body: Center(
child: FilledButton(
onPressed: _showBottomSheet,
child: Text(l10n.showBottomSheet),
),
),
);
}
}
Scaffold Messenger
Localized Snackbars
class LocalizedSnackbarScaffold extends StatelessWidget {
const LocalizedSnackbarScaffold({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.appTitle),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FilledButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.snackbarMessage),
action: SnackBarAction(
label: l10n.undoAction,
onPressed: () {},
),
),
);
},
child: Text(l10n.showSnackbar),
),
const SizedBox(height: 16),
OutlinedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.errorSnackbar),
backgroundColor: Theme.of(context).colorScheme.error,
behavior: SnackBarBehavior.floating,
),
);
},
child: Text(l10n.showError),
),
],
),
),
);
}
}
Responsive Scaffold
Adaptive Layout Scaffold
class LocalizedResponsiveScaffold extends StatelessWidget {
const LocalizedResponsiveScaffold({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final screenWidth = MediaQuery.of(context).size.width;
final isWide = screenWidth > 800;
if (isWide) {
return Scaffold(
body: Row(
children: [
NavigationRail(
selectedIndex: 0,
labelType: NavigationRailLabelType.all,
destinations: [
NavigationRailDestination(
icon: const Icon(Icons.home_outlined),
selectedIcon: const Icon(Icons.home),
label: Text(l10n.navHome),
),
NavigationRailDestination(
icon: const Icon(Icons.search_outlined),
selectedIcon: const Icon(Icons.search),
label: Text(l10n.navSearch),
),
NavigationRailDestination(
icon: const Icon(Icons.person_outlined),
selectedIcon: const Icon(Icons.person),
label: Text(l10n.navProfile),
),
],
onDestinationSelected: (_) {},
),
const VerticalDivider(width: 1),
Expanded(
child: Column(
children: [
AppBar(
title: Text(l10n.appTitle),
automaticallyImplyLeading: false,
),
Expanded(
child: Center(
child: Text(l10n.wideLayoutContent),
),
),
],
),
),
],
),
);
}
return Scaffold(
appBar: AppBar(
title: Text(l10n.appTitle),
),
body: Center(
child: Text(l10n.narrowLayoutContent),
),
bottomNavigationBar: NavigationBar(
selectedIndex: 0,
destinations: [
NavigationDestination(
icon: const Icon(Icons.home_outlined),
selectedIcon: const Icon(Icons.home),
label: l10n.navHome,
),
NavigationDestination(
icon: const Icon(Icons.search_outlined),
selectedIcon: const Icon(Icons.search),
label: l10n.navSearch,
),
NavigationDestination(
icon: const Icon(Icons.person_outlined),
selectedIcon: const Icon(Icons.person),
label: l10n.navProfile,
),
],
onDestinationSelected: (_) {},
),
);
}
}
ARB File Structure
English (app_en.arb)
{
"@@locale": "en",
"appTitle": "My App",
"searchTooltip": "Search",
"moreTooltip": "More options",
"menuHeader": "Navigation",
"menuHome": "Home",
"menuSettings": "Settings",
"bodyContent": "Main content area",
"addTooltip": "Add new item",
"homePageContent": "Welcome to the home page",
"searchPageContent": "Search for anything",
"profilePageContent": "Your profile",
"navHome": "Home",
"navSearch": "Search",
"navProfile": "Profile",
"navFavorites": "Favorites",
"filterTooltip": "Filter",
"filterTitle": "Filters",
"filterOption1": "Show active only",
"filterOption2": "Include archived",
"filterOption3": "Sort by date",
"applyFilters": "Apply Filters",
"itemLabel": "Item",
"listItem": "List Item",
"listItemSubtitle": "Tap for details",
"createNew": "Create New",
"mainContent": "Main content here",
"bottomSheetTitle": "Bottom Sheet",
"bottomSheetContent": "This is a localized bottom sheet with content.",
"closeButton": "Close",
"showBottomSheet": "Show Bottom Sheet",
"snackbarMessage": "Action completed successfully",
"undoAction": "Undo",
"showSnackbar": "Show Snackbar",
"errorSnackbar": "An error occurred",
"showError": "Show Error",
"wideLayoutContent": "Wide screen layout",
"narrowLayoutContent": "Mobile layout"
}
German (app_de.arb)
{
"@@locale": "de",
"appTitle": "Meine App",
"searchTooltip": "Suchen",
"moreTooltip": "Weitere Optionen",
"menuHeader": "Navigation",
"menuHome": "Startseite",
"menuSettings": "Einstellungen",
"bodyContent": "Hauptinhaltsbereich",
"addTooltip": "Neues Element hinzufügen",
"homePageContent": "Willkommen auf der Startseite",
"searchPageContent": "Suchen Sie nach allem",
"profilePageContent": "Ihr Profil",
"navHome": "Startseite",
"navSearch": "Suchen",
"navProfile": "Profil",
"navFavorites": "Favoriten",
"filterTooltip": "Filter",
"filterTitle": "Filter",
"filterOption1": "Nur aktive anzeigen",
"filterOption2": "Archivierte einbeziehen",
"filterOption3": "Nach Datum sortieren",
"applyFilters": "Filter anwenden",
"itemLabel": "Element",
"listItem": "Listenelement",
"listItemSubtitle": "Tippen für Details",
"createNew": "Neu erstellen",
"mainContent": "Hauptinhalt hier",
"bottomSheetTitle": "Unteres Blatt",
"bottomSheetContent": "Dies ist ein lokalisiertes unteres Blatt mit Inhalt.",
"closeButton": "Schließen",
"showBottomSheet": "Unteres Blatt anzeigen",
"snackbarMessage": "Aktion erfolgreich abgeschlossen",
"undoAction": "Rückgängig",
"showSnackbar": "Snackbar anzeigen",
"errorSnackbar": "Ein Fehler ist aufgetreten",
"showError": "Fehler anzeigen",
"wideLayoutContent": "Breitbild-Layout",
"narrowLayoutContent": "Mobiles Layout"
}
Arabic (app_ar.arb)
{
"@@locale": "ar",
"appTitle": "تطبيقي",
"searchTooltip": "بحث",
"moreTooltip": "المزيد من الخيارات",
"menuHeader": "التنقل",
"menuHome": "الرئيسية",
"menuSettings": "الإعدادات",
"bodyContent": "منطقة المحتوى الرئيسية",
"addTooltip": "إضافة عنصر جديد",
"homePageContent": "مرحباً بك في الصفحة الرئيسية",
"searchPageContent": "ابحث عن أي شيء",
"profilePageContent": "ملفك الشخصي",
"navHome": "الرئيسية",
"navSearch": "بحث",
"navProfile": "الملف الشخصي",
"navFavorites": "المفضلة",
"filterTooltip": "تصفية",
"filterTitle": "الفلاتر",
"filterOption1": "إظهار النشطة فقط",
"filterOption2": "تضمين المؤرشفة",
"filterOption3": "ترتيب حسب التاريخ",
"applyFilters": "تطبيق الفلاتر",
"itemLabel": "عنصر",
"listItem": "عنصر القائمة",
"listItemSubtitle": "انقر للتفاصيل",
"createNew": "إنشاء جديد",
"mainContent": "المحتوى الرئيسي هنا",
"bottomSheetTitle": "الورقة السفلية",
"bottomSheetContent": "هذه ورقة سفلية مترجمة تحتوي على محتوى.",
"closeButton": "إغلاق",
"showBottomSheet": "إظهار الورقة السفلية",
"snackbarMessage": "تم إكمال الإجراء بنجاح",
"undoAction": "تراجع",
"showSnackbar": "إظهار الإشعار",
"errorSnackbar": "حدث خطأ",
"showError": "إظهار الخطأ",
"wideLayoutContent": "تخطيط الشاشة العريضة",
"narrowLayoutContent": "تخطيط الجوال"
}
Best Practices Summary
Do's
- Let Scaffold handle RTL drawer positioning automatically
- Use NavigationBar/NavigationRail for consistent navigation
- Implement responsive layouts for different screen sizes
- Use ScaffoldMessenger for snackbars
- Test drawer and FAB positions in RTL mode
Don'ts
- Don't hardcode drawer positions - let Flutter handle RTL
- Don't forget to localize tooltips for icon buttons
- Don't nest Scaffolds unnecessarily
- Don't override FAB positions without considering RTL
Conclusion
Scaffold is the foundational widget for creating Material Design app structures in multilingual Flutter applications. With automatic RTL support for drawers and proper navigation patterns, Scaffold ensures your app structure works correctly across all languages. Use Scaffold's built-in features for app bars, drawers, bottom navigation, and floating action buttons to create consistent, localized user experiences.