← Back to Blog

Flutter DraggableScrollableSheet Localization: Modal Sheets, Drag Handles, and Expandable Content

flutterdraggablescrollablesheetmodalbottomsheetlocalizationaccessibility

Flutter DraggableScrollableSheet Localization: Modal Sheets, Drag Handles, and Expandable Content

DraggableScrollableSheet provides expandable bottom sheets that users can drag to reveal more content. Localizing these sheets requires handling drag handle instructions, content headers, action buttons, and accessibility announcements across different languages. This guide covers everything you need to know about localizing DraggableScrollableSheet in Flutter.

Understanding DraggableScrollableSheet Localization

DraggableScrollableSheet requires localization for:

  • Drag handle labels: Instructions for expanding/collapsing
  • Sheet headers: Titles and subtitles
  • Content sections: Lists, forms, and details
  • Action buttons: Primary and secondary actions
  • Accessibility: Screen reader announcements for drag states
  • RTL support: Proper layout for Arabic, Hebrew, etc.

Basic DraggableScrollableSheet with Localized Content

Start with a simple localized expandable sheet:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class LocalizedDraggableSheet extends StatelessWidget {
  const LocalizedDraggableSheet({super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.mapViewTitle),
      ),
      body: Stack(
        children: [
          // Main content (e.g., map)
          Container(
            color: Colors.grey[200],
            child: Center(
              child: Text(l10n.mapPlaceholder),
            ),
          ),
          // Draggable sheet
          DraggableScrollableSheet(
            initialChildSize: 0.3,
            minChildSize: 0.1,
            maxChildSize: 0.9,
            builder: (context, scrollController) {
              return Container(
                decoration: BoxDecoration(
                  color: Theme.of(context).scaffoldBackgroundColor,
                  borderRadius: const BorderRadius.vertical(
                    top: Radius.circular(16),
                  ),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.1),
                      blurRadius: 10,
                      offset: const Offset(0, -2),
                    ),
                  ],
                ),
                child: ListView(
                  controller: scrollController,
                  padding: EdgeInsets.zero,
                  children: [
                    // Drag handle
                    Center(
                      child: Padding(
                        padding: const EdgeInsets.symmetric(vertical: 12),
                        child: Container(
                          width: 40,
                          height: 4,
                          decoration: BoxDecoration(
                            color: Colors.grey[400],
                            borderRadius: BorderRadius.circular(2),
                          ),
                        ),
                      ),
                    ),
                    // Header
                    Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 16),
                      child: Text(
                        l10n.nearbyPlacesTitle,
                        style: Theme.of(context).textTheme.titleLarge,
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 16,
                        vertical: 4,
                      ),
                      child: Text(
                        l10n.nearbyPlacesSubtitle,
                        style: TextStyle(color: Colors.grey[600]),
                      ),
                    ),
                    const Divider(),
                    // Location items
                    ...List.generate(
                      10,
                      (index) => ListTile(
                        leading: CircleAvatar(
                          child: Icon(_getPlaceIcon(index)),
                        ),
                        title: Text(l10n.placeName(index + 1)),
                        subtitle: Text(l10n.placeDistance(0.5 + index * 0.3)),
                        trailing: IconButton(
                          icon: const Icon(Icons.directions),
                          tooltip: l10n.getDirectionsTooltip,
                          onPressed: () {},
                        ),
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );
  }

  IconData _getPlaceIcon(int index) {
    final icons = [
      Icons.restaurant,
      Icons.local_cafe,
      Icons.shopping_bag,
      Icons.local_gas_station,
      Icons.local_pharmacy,
    ];
    return icons[index % icons.length];
  }
}

Product Details Sheet with Localization

Create a product details sheet with localized content:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class ProductDetailsSheet extends StatelessWidget {
  final String productId;
  final VoidCallback onAddToCart;

  const ProductDetailsSheet({
    super.key,
    required this.productId,
    required this.onAddToCart,
  });

  static Future<void> show(BuildContext context, String productId) {
    return showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.transparent,
      builder: (context) => ProductDetailsSheet(
        productId: productId,
        onAddToCart: () {
          Navigator.pop(context);
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text(AppLocalizations.of(context)!.addedToCartMessage),
            ),
          );
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return DraggableScrollableSheet(
      initialChildSize: 0.6,
      minChildSize: 0.4,
      maxChildSize: 0.95,
      builder: (context, scrollController) {
        return Container(
          decoration: BoxDecoration(
            color: Theme.of(context).scaffoldBackgroundColor,
            borderRadius: const BorderRadius.vertical(
              top: Radius.circular(20),
            ),
          ),
          child: Column(
            children: [
              // Drag indicator with semantic label
              Semantics(
                label: l10n.dragToExpandLabel,
                child: Container(
                  margin: const EdgeInsets.symmetric(vertical: 12),
                  width: 40,
                  height: 4,
                  decoration: BoxDecoration(
                    color: Colors.grey[400],
                    borderRadius: BorderRadius.circular(2),
                  ),
                ),
              ),
              // Scrollable content
              Expanded(
                child: ListView(
                  controller: scrollController,
                  padding: const EdgeInsets.symmetric(horizontal: 16),
                  children: [
                    // Product image placeholder
                    Container(
                      height: 200,
                      decoration: BoxDecoration(
                        color: Colors.grey[300],
                        borderRadius: BorderRadius.circular(12),
                      ),
                      child: Center(
                        child: Icon(
                          Icons.image,
                          size: 64,
                          color: Colors.grey[500],
                        ),
                      ),
                    ),
                    const SizedBox(height: 16),
                    // Product name
                    Text(
                      l10n.sampleProductName,
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                    const SizedBox(height: 8),
                    // Price and rating row
                    Row(
                      children: [
                        Text(
                          l10n.productPrice(99.99),
                          style: Theme.of(context)
                              .textTheme
                              .titleLarge
                              ?.copyWith(
                                color: Theme.of(context).primaryColor,
                                fontWeight: FontWeight.bold,
                              ),
                        ),
                        const Spacer(),
                        Row(
                          children: [
                            const Icon(Icons.star, color: Colors.amber, size: 20),
                            const SizedBox(width: 4),
                            Text(l10n.ratingValue(4.5)),
                            Text(
                              l10n.reviewCount(128),
                              style: TextStyle(color: Colors.grey[600]),
                            ),
                          ],
                        ),
                      ],
                    ),
                    const SizedBox(height: 16),
                    // Description section
                    Text(
                      l10n.descriptionTitle,
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 8),
                    Text(
                      l10n.sampleProductDescription,
                      style: TextStyle(color: Colors.grey[700]),
                    ),
                    const SizedBox(height: 16),
                    // Specifications
                    Text(
                      l10n.specificationsTitle,
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 8),
                    _buildSpecRow(l10n.specBrand, 'Premium Brand'),
                    _buildSpecRow(l10n.specMaterial, l10n.materialCotton),
                    _buildSpecRow(l10n.specColor, l10n.colorBlue),
                    _buildSpecRow(l10n.specSize, 'M, L, XL'),
                    const SizedBox(height: 24),
                    // Quantity selector
                    Row(
                      children: [
                        Text(
                          l10n.quantityLabel,
                          style: Theme.of(context).textTheme.titleMedium,
                        ),
                        const Spacer(),
                        _QuantitySelector(l10n: l10n),
                      ],
                    ),
                    const SizedBox(height: 100), // Space for button
                  ],
                ),
              ),
              // Fixed bottom button
              Container(
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Theme.of(context).scaffoldBackgroundColor,
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.05),
                      blurRadius: 10,
                      offset: const Offset(0, -2),
                    ),
                  ],
                ),
                child: SafeArea(
                  child: Row(
                    children: [
                      Expanded(
                        child: OutlinedButton.icon(
                          onPressed: () {},
                          icon: const Icon(Icons.favorite_border),
                          label: Text(l10n.addToWishlistButton),
                        ),
                      ),
                      const SizedBox(width: 12),
                      Expanded(
                        flex: 2,
                        child: ElevatedButton.icon(
                          onPressed: onAddToCart,
                          icon: const Icon(Icons.shopping_cart),
                          label: Text(l10n.addToCartButton),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  Widget _buildSpecRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        children: [
          Text(
            label,
            style: const TextStyle(color: Colors.grey),
          ),
          const Spacer(),
          Text(value),
        ],
      ),
    );
  }
}

class _QuantitySelector extends StatefulWidget {
  final AppLocalizations l10n;

  const _QuantitySelector({required this.l10n});

  @override
  State<_QuantitySelector> createState() => _QuantitySelectorState();
}

class _QuantitySelectorState extends State<_QuantitySelector> {
  int _quantity = 1;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        IconButton(
          icon: const Icon(Icons.remove_circle_outline),
          onPressed: _quantity > 1
              ? () => setState(() => _quantity--)
              : null,
          tooltip: widget.l10n.decreaseQuantityTooltip,
        ),
        Semantics(
          label: widget.l10n.quantityAccessibilityLabel(_quantity),
          child: Text(
            '$_quantity',
            style: const TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        IconButton(
          icon: const Icon(Icons.add_circle_outline),
          onPressed: () => setState(() => _quantity++),
          tooltip: widget.l10n.increaseQuantityTooltip,
        ),
      ],
    );
  }
}

Filter Sheet with Multiple Sections

Create a localized filter sheet:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class FilterSheet extends StatefulWidget {
  final Function(FilterOptions) onApply;

  const FilterSheet({super.key, required this.onApply});

  static Future<void> show(
    BuildContext context,
    Function(FilterOptions) onApply,
  ) {
    return showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.transparent,
      builder: (context) => FilterSheet(onApply: onApply),
    );
  }

  @override
  State<FilterSheet> createState() => _FilterSheetState();
}

class _FilterSheetState extends State<FilterSheet> {
  RangeValues _priceRange = const RangeValues(0, 500);
  String _selectedCategory = 'all';
  double _minRating = 0;
  bool _inStockOnly = false;
  bool _onSaleOnly = false;

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return DraggableScrollableSheet(
      initialChildSize: 0.7,
      minChildSize: 0.5,
      maxChildSize: 0.95,
      builder: (context, scrollController) {
        return Container(
          decoration: BoxDecoration(
            color: Theme.of(context).scaffoldBackgroundColor,
            borderRadius: const BorderRadius.vertical(
              top: Radius.circular(20),
            ),
          ),
          child: Column(
            children: [
              // Header
              Container(
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  border: Border(
                    bottom: BorderSide(color: Colors.grey[300]!),
                  ),
                ),
                child: Row(
                  children: [
                    Text(
                      l10n.filterTitle,
                      style: Theme.of(context).textTheme.titleLarge,
                    ),
                    const Spacer(),
                    TextButton(
                      onPressed: _resetFilters,
                      child: Text(l10n.resetFiltersButton),
                    ),
                    IconButton(
                      icon: const Icon(Icons.close),
                      tooltip: l10n.closeSheetTooltip,
                      onPressed: () => Navigator.pop(context),
                    ),
                  ],
                ),
              ),
              // Filter content
              Expanded(
                child: ListView(
                  controller: scrollController,
                  padding: const EdgeInsets.all(16),
                  children: [
                    // Price range
                    Text(
                      l10n.priceRangeLabel,
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 8),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Text(l10n.priceValue(_priceRange.start.round())),
                        Text(l10n.priceValue(_priceRange.end.round())),
                      ],
                    ),
                    RangeSlider(
                      values: _priceRange,
                      min: 0,
                      max: 1000,
                      divisions: 20,
                      labels: RangeLabels(
                        l10n.priceValue(_priceRange.start.round()),
                        l10n.priceValue(_priceRange.end.round()),
                      ),
                      onChanged: (values) {
                        setState(() => _priceRange = values);
                      },
                    ),
                    const SizedBox(height: 24),
                    // Category
                    Text(
                      l10n.categoryLabel,
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 8),
                    Wrap(
                      spacing: 8,
                      runSpacing: 8,
                      children: [
                        _buildCategoryChip('all', l10n.categoryAll),
                        _buildCategoryChip('electronics', l10n.categoryElectronics),
                        _buildCategoryChip('clothing', l10n.categoryClothing),
                        _buildCategoryChip('home', l10n.categoryHome),
                        _buildCategoryChip('sports', l10n.categorySports),
                      ],
                    ),
                    const SizedBox(height: 24),
                    // Rating
                    Text(
                      l10n.minimumRatingLabel,
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 8),
                    Row(
                      children: List.generate(5, (index) {
                        final rating = index + 1;
                        return Expanded(
                          child: GestureDetector(
                            onTap: () {
                              setState(() => _minRating = rating.toDouble());
                            },
                            child: Column(
                              children: [
                                Icon(
                                  rating <= _minRating
                                      ? Icons.star
                                      : Icons.star_border,
                                  color: Colors.amber,
                                  size: 32,
                                ),
                                Text(
                                  l10n.starRating(rating),
                                  style: TextStyle(
                                    fontSize: 12,
                                    color: rating <= _minRating
                                        ? Colors.black
                                        : Colors.grey,
                                  ),
                                ),
                              ],
                            ),
                          ),
                        );
                      }),
                    ),
                    const SizedBox(height: 24),
                    // Toggle options
                    Text(
                      l10n.additionalOptionsLabel,
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    SwitchListTile(
                      title: Text(l10n.inStockOnlyLabel),
                      subtitle: Text(l10n.inStockOnlyDescription),
                      value: _inStockOnly,
                      onChanged: (value) {
                        setState(() => _inStockOnly = value);
                      },
                    ),
                    SwitchListTile(
                      title: Text(l10n.onSaleOnlyLabel),
                      subtitle: Text(l10n.onSaleOnlyDescription),
                      value: _onSaleOnly,
                      onChanged: (value) {
                        setState(() => _onSaleOnly = value);
                      },
                    ),
                    const SizedBox(height: 80),
                  ],
                ),
              ),
              // Apply button
              Container(
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Theme.of(context).scaffoldBackgroundColor,
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.05),
                      blurRadius: 10,
                      offset: const Offset(0, -2),
                    ),
                  ],
                ),
                child: SafeArea(
                  child: SizedBox(
                    width: double.infinity,
                    child: ElevatedButton(
                      onPressed: () {
                        widget.onApply(FilterOptions(
                          priceRange: _priceRange,
                          category: _selectedCategory,
                          minRating: _minRating,
                          inStockOnly: _inStockOnly,
                          onSaleOnly: _onSaleOnly,
                        ));
                        Navigator.pop(context);
                      },
                      child: Text(l10n.applyFiltersButton),
                    ),
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  Widget _buildCategoryChip(String value, String label) {
    final isSelected = _selectedCategory == value;
    return FilterChip(
      label: Text(label),
      selected: isSelected,
      onSelected: (selected) {
        setState(() => _selectedCategory = value);
      },
    );
  }

  void _resetFilters() {
    setState(() {
      _priceRange = const RangeValues(0, 500);
      _selectedCategory = 'all';
      _minRating = 0;
      _inStockOnly = false;
      _onSaleOnly = false;
    });
  }
}

class FilterOptions {
  final RangeValues priceRange;
  final String category;
  final double minRating;
  final bool inStockOnly;
  final bool onSaleOnly;

  FilterOptions({
    required this.priceRange,
    required this.category,
    required this.minRating,
    required this.inStockOnly,
    required this.onSaleOnly,
  });
}

Accessibility for DraggableScrollableSheet

Ensure sheets work well with screen readers:

import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class AccessibleDraggableSheet extends StatefulWidget {
  const AccessibleDraggableSheet({super.key});

  @override
  State<AccessibleDraggableSheet> createState() =>
      _AccessibleDraggableSheetState();
}

class _AccessibleDraggableSheetState extends State<AccessibleDraggableSheet> {
  final DraggableScrollableController _controller =
      DraggableScrollableController();
  double _currentSize = 0.3;

  @override
  void initState() {
    super.initState();
    _controller.addListener(_onSizeChanged);
  }

  @override
  void dispose() {
    _controller.removeListener(_onSizeChanged);
    _controller.dispose();
    super.dispose();
  }

  void _onSizeChanged() {
    final newSize = _controller.size;
    if ((newSize - _currentSize).abs() > 0.1) {
      _currentSize = newSize;
      _announceState(context);
    }
  }

  void _announceState(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    String announcement;

    if (_currentSize > 0.8) {
      announcement = l10n.sheetFullyExpandedAnnouncement;
    } else if (_currentSize > 0.5) {
      announcement = l10n.sheetPartiallyExpandedAnnouncement;
    } else {
      announcement = l10n.sheetCollapsedAnnouncement;
    }

    SemanticsService.announce(announcement, TextDirection.ltr);
  }

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(title: Text(l10n.detailsTitle)),
      body: Stack(
        children: [
          Container(
            color: Colors.grey[100],
            child: Center(child: Text(l10n.mainContentPlaceholder)),
          ),
          DraggableScrollableSheet(
            controller: _controller,
            initialChildSize: 0.3,
            minChildSize: 0.1,
            maxChildSize: 0.9,
            builder: (context, scrollController) {
              return Semantics(
                label: l10n.draggableSheetLabel,
                hint: l10n.draggableSheetHint,
                child: Container(
                  decoration: BoxDecoration(
                    color: Theme.of(context).scaffoldBackgroundColor,
                    borderRadius: const BorderRadius.vertical(
                      top: Radius.circular(16),
                    ),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.black.withOpacity(0.1),
                        blurRadius: 10,
                      ),
                    ],
                  ),
                  child: Column(
                    children: [
                      // Accessible drag handle
                      Semantics(
                        button: true,
                        label: l10n.dragHandleLabel,
                        hint: l10n.dragHandleHint,
                        onTap: () => _toggleSheet(),
                        child: GestureDetector(
                          onTap: _toggleSheet,
                          child: Container(
                            width: double.infinity,
                            padding: const EdgeInsets.symmetric(vertical: 16),
                            child: Center(
                              child: Container(
                                width: 40,
                                height: 4,
                                decoration: BoxDecoration(
                                  color: Colors.grey[400],
                                  borderRadius: BorderRadius.circular(2),
                                ),
                              ),
                            ),
                          ),
                        ),
                      ),
                      // Expand/collapse button for accessibility
                      Padding(
                        padding: const EdgeInsets.symmetric(horizontal: 16),
                        child: Row(
                          children: [
                            Text(
                              l10n.contentTitle,
                              style: Theme.of(context).textTheme.titleLarge,
                            ),
                            const Spacer(),
                            IconButton(
                              icon: Icon(
                                _currentSize > 0.5
                                    ? Icons.expand_more
                                    : Icons.expand_less,
                              ),
                              tooltip: _currentSize > 0.5
                                  ? l10n.collapseSheetTooltip
                                  : l10n.expandSheetTooltip,
                              onPressed: _toggleSheet,
                            ),
                          ],
                        ),
                      ),
                      const Divider(),
                      // Content
                      Expanded(
                        child: ListView.builder(
                          controller: scrollController,
                          itemCount: 20,
                          itemBuilder: (context, index) => Semantics(
                            label: l10n.listItemAccessibilityLabel(index + 1),
                            child: ListTile(
                              leading: CircleAvatar(
                                child: Text('${index + 1}'),
                              ),
                              title: Text(l10n.listItemTitle(index + 1)),
                              subtitle: Text(l10n.listItemSubtitle),
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }

  void _toggleSheet() {
    final l10n = AppLocalizations.of(context)!;
    if (_currentSize > 0.5) {
      _controller.animateTo(
        0.3,
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeOut,
      );
      SemanticsService.announce(
        l10n.sheetCollapsingAnnouncement,
        TextDirection.ltr,
      );
    } else {
      _controller.animateTo(
        0.9,
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeOut,
      );
      SemanticsService.announce(
        l10n.sheetExpandingAnnouncement,
        TextDirection.ltr,
      );
    }
  }
}

RTL Support for DraggableScrollableSheet

Handle right-to-left layouts:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class RTLDraggableSheet extends StatelessWidget {
  const RTLDraggableSheet({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.ordersTitle)),
      body: Stack(
        children: [
          Container(color: Colors.grey[100]),
          DraggableScrollableSheet(
            initialChildSize: 0.4,
            minChildSize: 0.2,
            maxChildSize: 0.9,
            builder: (context, scrollController) {
              return Container(
                decoration: BoxDecoration(
                  color: Theme.of(context).scaffoldBackgroundColor,
                  borderRadius: const BorderRadius.vertical(
                    top: Radius.circular(20),
                  ),
                ),
                child: ListView(
                  controller: scrollController,
                  children: [
                    // Drag handle
                    Center(
                      child: Container(
                        margin: const EdgeInsets.symmetric(vertical: 12),
                        width: 40,
                        height: 4,
                        decoration: BoxDecoration(
                          color: Colors.grey[400],
                          borderRadius: BorderRadius.circular(2),
                        ),
                      ),
                    ),
                    // Header with directional padding
                    Padding(
                      padding: const EdgeInsetsDirectional.symmetric(
                        horizontal: 16,
                      ),
                      child: Row(
                        children: [
                          Text(
                            l10n.recentOrdersTitle,
                            style: Theme.of(context).textTheme.titleLarge,
                          ),
                          const Spacer(),
                          TextButton(
                            onPressed: () {},
                            child: Text(l10n.viewAllButton),
                          ),
                        ],
                      ),
                    ),
                    const Divider(),
                    // Order items with RTL-aware layout
                    ...List.generate(5, (index) {
                      return Padding(
                        padding: const EdgeInsetsDirectional.symmetric(
                          horizontal: 16,
                          vertical: 8,
                        ),
                        child: Card(
                          child: Padding(
                            padding: const EdgeInsets.all(16),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Row(
                                  children: [
                                    Text(
                                      l10n.orderNumber(1000 + index),
                                      style: const TextStyle(
                                        fontWeight: FontWeight.bold,
                                      ),
                                    ),
                                    const Spacer(),
                                    Container(
                                      padding: const EdgeInsets.symmetric(
                                        horizontal: 8,
                                        vertical: 4,
                                      ),
                                      decoration: BoxDecoration(
                                        color: Colors.green[100],
                                        borderRadius: BorderRadius.circular(4),
                                      ),
                                      child: Text(
                                        l10n.orderStatusDelivered,
                                        style: TextStyle(
                                          color: Colors.green[800],
                                          fontSize: 12,
                                        ),
                                      ),
                                    ),
                                  ],
                                ),
                                const SizedBox(height: 8),
                                Text(
                                  l10n.orderDate('Jan ${10 + index}, 2026'),
                                  style: TextStyle(color: Colors.grey[600]),
                                ),
                                const SizedBox(height: 4),
                                Row(
                                  children: [
                                    Text(
                                      l10n.orderTotal(99.99 + index * 10),
                                      style: TextStyle(
                                        color: Theme.of(context).primaryColor,
                                        fontWeight: FontWeight.bold,
                                      ),
                                    ),
                                    const Spacer(),
                                    // Arrow direction changes with RTL
                                    TextButton.icon(
                                      onPressed: () {},
                                      icon: Icon(
                                        isRTL
                                            ? Icons.arrow_back_ios
                                            : Icons.arrow_forward_ios,
                                        size: 14,
                                      ),
                                      label: Text(l10n.viewDetailsButton),
                                    ),
                                  ],
                                ),
                              ],
                            ),
                          ),
                        ),
                      );
                    }),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

ARB Translations for DraggableScrollableSheet

Add these entries to your ARB files:

{
  "mapViewTitle": "Map",
  "mapPlaceholder": "Map View",
  "nearbyPlacesTitle": "Nearby Places",
  "nearbyPlacesSubtitle": "Discover places around you",
  "placeName": "Place {number}",
  "@placeName": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "placeDistance": "{distance} km away",
  "@placeDistance": {
    "placeholders": {
      "distance": {"type": "double", "format": "decimalPattern"}
    }
  },
  "getDirectionsTooltip": "Get directions",

  "dragToExpandLabel": "Drag to expand sheet",
  "sampleProductName": "Premium Cotton T-Shirt",
  "productPrice": "${price}",
  "@productPrice": {
    "placeholders": {
      "price": {"type": "double", "format": "currency", "optionalParameters": {"symbol": "$"}}
    }
  },
  "ratingValue": "{rating}",
  "@ratingValue": {
    "placeholders": {
      "rating": {"type": "double", "format": "decimalPattern"}
    }
  },
  "reviewCount": " ({count} reviews)",
  "@reviewCount": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },
  "descriptionTitle": "Description",
  "sampleProductDescription": "High-quality premium cotton t-shirt with a comfortable fit. Perfect for everyday wear.",
  "specificationsTitle": "Specifications",
  "specBrand": "Brand",
  "specMaterial": "Material",
  "specColor": "Color",
  "specSize": "Available Sizes",
  "materialCotton": "100% Cotton",
  "colorBlue": "Navy Blue",
  "quantityLabel": "Quantity",
  "decreaseQuantityTooltip": "Decrease quantity",
  "increaseQuantityTooltip": "Increase quantity",
  "quantityAccessibilityLabel": "Quantity: {count}",
  "@quantityAccessibilityLabel": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },
  "addToWishlistButton": "Wishlist",
  "addToCartButton": "Add to Cart",
  "addedToCartMessage": "Added to cart",

  "filterTitle": "Filters",
  "resetFiltersButton": "Reset",
  "closeSheetTooltip": "Close",
  "priceRangeLabel": "Price Range",
  "priceValue": "${value}",
  "@priceValue": {
    "placeholders": {
      "value": {"type": "int"}
    }
  },
  "categoryLabel": "Category",
  "categoryAll": "All",
  "categoryElectronics": "Electronics",
  "categoryClothing": "Clothing",
  "categoryHome": "Home",
  "categorySports": "Sports",
  "minimumRatingLabel": "Minimum Rating",
  "starRating": "{stars}+",
  "@starRating": {
    "placeholders": {
      "stars": {"type": "int"}
    }
  },
  "additionalOptionsLabel": "Additional Options",
  "inStockOnlyLabel": "In Stock Only",
  "inStockOnlyDescription": "Show only items currently in stock",
  "onSaleOnlyLabel": "On Sale Only",
  "onSaleOnlyDescription": "Show only discounted items",
  "applyFiltersButton": "Apply Filters",

  "detailsTitle": "Details",
  "mainContentPlaceholder": "Main Content",
  "draggableSheetLabel": "Expandable details panel",
  "draggableSheetHint": "Swipe up to expand, swipe down to collapse",
  "dragHandleLabel": "Sheet drag handle",
  "dragHandleHint": "Double tap to toggle expansion",
  "contentTitle": "Content",
  "collapseSheetTooltip": "Collapse sheet",
  "expandSheetTooltip": "Expand sheet",
  "listItemAccessibilityLabel": "List item {number}",
  "@listItemAccessibilityLabel": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "listItemTitle": "Item {number}",
  "@listItemTitle": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "listItemSubtitle": "Tap to view details",
  "sheetFullyExpandedAnnouncement": "Sheet fully expanded",
  "sheetPartiallyExpandedAnnouncement": "Sheet partially expanded",
  "sheetCollapsedAnnouncement": "Sheet collapsed",
  "sheetExpandingAnnouncement": "Expanding sheet",
  "sheetCollapsingAnnouncement": "Collapsing sheet",

  "ordersTitle": "Orders",
  "recentOrdersTitle": "Recent Orders",
  "viewAllButton": "View All",
  "orderNumber": "Order #{number}",
  "@orderNumber": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "orderStatusDelivered": "Delivered",
  "orderDate": "{date}",
  "@orderDate": {
    "placeholders": {
      "date": {"type": "String"}
    }
  },
  "orderTotal": "${total}",
  "@orderTotal": {
    "placeholders": {
      "total": {"type": "double", "format": "currency", "optionalParameters": {"symbol": "$"}}
    }
  },
  "viewDetailsButton": "Details"
}

Testing DraggableScrollableSheet Localization

Write tests for your localized sheets:

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

void main() {
  group('LocalizedDraggableSheet', () {
    testWidgets('displays localized content in English', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          locale: const Locale('en'),
          home: const LocalizedDraggableSheet(),
        ),
      );

      expect(find.text('Nearby Places'), findsOneWidget);
      expect(find.text('Discover places around you'), findsOneWidget);
    });

    testWidgets('displays localized content in Spanish', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          locale: const Locale('es'),
          home: const LocalizedDraggableSheet(),
        ),
      );

      expect(find.text('Lugares Cercanos'), findsOneWidget);
      expect(find.text('Descubre lugares a tu alrededor'), findsOneWidget);
    });

    testWidgets('filter sheet displays localized options', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          locale: const Locale('en'),
          home: Scaffold(
            body: Builder(
              builder: (context) => ElevatedButton(
                onPressed: () => FilterSheet.show(context, (_) {}),
                child: const Text('Open Filter'),
              ),
            ),
          ),
        ),
      );

      await tester.tap(find.text('Open Filter'));
      await tester.pumpAndSettle();

      expect(find.text('Filters'), findsOneWidget);
      expect(find.text('Price Range'), findsOneWidget);
      expect(find.text('Category'), findsOneWidget);
      expect(find.text('Apply Filters'), findsOneWidget);
    });

    testWidgets('handles RTL layout correctly', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          locale: const Locale('ar'),
          home: const Directionality(
            textDirection: TextDirection.rtl,
            child: RTLDraggableSheet(),
          ),
        ),
      );

      expect(find.text('الطلبات'), findsOneWidget);
    });
  });
}

Summary

Localizing DraggableScrollableSheet in Flutter requires:

  1. Drag handle labels with accessibility hints
  2. Header translations for sheet titles and subtitles
  3. Content localization for lists and forms
  4. Action button labels for primary and secondary actions
  5. Accessibility announcements for drag state changes
  6. RTL support with directional padding and icons
  7. Filter and form labels in modal sheets
  8. Comprehensive testing across different locales

DraggableScrollableSheet provides flexible bottom sheets, and proper localization ensures your app delivers a polished experience for users in any language.