← Back to Blog

Flutter Scaffold Localization: App Structure for Multilingual Interfaces

flutterscaffoldlayoutmateriallocalizationrtl

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

  1. Let Scaffold handle RTL drawer positioning automatically
  2. Use NavigationBar/NavigationRail for consistent navigation
  3. Implement responsive layouts for different screen sizes
  4. Use ScaffoldMessenger for snackbars
  5. Test drawer and FAB positions in RTL mode

Don'ts

  1. Don't hardcode drawer positions - let Flutter handle RTL
  2. Don't forget to localize tooltips for icon buttons
  3. Don't nest Scaffolds unnecessarily
  4. 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.

Further Reading