← Back to Blog

Flutter GridView Localization: Adaptive Grids, Responsive Layouts, and Localized Content

fluttergridviewgridresponsivelocalizationaccessibility

Flutter GridView Localization: Adaptive Grids, Responsive Layouts, and Localized Content

GridView is essential for displaying collections of items in a two-dimensional scrollable grid. Localizing GridView requires handling item labels, responsive layouts for different text lengths, RTL support, and accessibility features. This guide covers everything you need to know about localizing GridView in Flutter.

Understanding GridView Localization

GridView requires localization for:

  • Item titles and labels: Text displayed within grid items
  • Category headers: Section titles above grid groups
  • Empty state messages: What to show when no items exist
  • Loading indicators: Progress text during data fetching
  • Responsive layouts: Adapting to varying text lengths
  • RTL support: Proper mirroring for right-to-left languages

Basic GridView with Localized Items

Start with a simple localized grid:

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

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

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

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.productsTitle),
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(16),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          crossAxisSpacing: 16,
          mainAxisSpacing: 16,
          childAspectRatio: 0.75,
        ),
        itemCount: 12,
        itemBuilder: (context, index) {
          return Card(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Expanded(
                  flex: 3,
                  child: Container(
                    width: double.infinity,
                    color: Colors.grey[200],
                    child: Icon(
                      Icons.image,
                      size: 48,
                      color: Colors.grey[400],
                    ),
                  ),
                ),
                Expanded(
                  flex: 2,
                  child: Padding(
                    padding: const EdgeInsets.all(8),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          l10n.productName(index + 1),
                          style: Theme.of(context).textTheme.titleSmall,
                          maxLines: 2,
                          overflow: TextOverflow.ellipsis,
                        ),
                        const Spacer(),
                        Text(
                          l10n.productPrice(29.99 + index),
                          style: Theme.of(context).textTheme.bodyLarge?.copyWith(
                                fontWeight: FontWeight.bold,
                                color: Theme.of(context).primaryColor,
                              ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

Adaptive GridView Based on Locale

Adjust grid columns based on text length for different languages:

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

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

  int _getColumnCount(BuildContext context) {
    final locale = Localizations.localeOf(context);
    final screenWidth = MediaQuery.of(context).size.width;

    // Languages with longer text need fewer columns
    final languagesWithLongText = ['de', 'ru', 'fi', 'hu'];
    final isLongTextLocale = languagesWithLongText.contains(locale.languageCode);

    if (screenWidth < 400) {
      return isLongTextLocale ? 1 : 2;
    } else if (screenWidth < 600) {
      return isLongTextLocale ? 2 : 3;
    } else {
      return isLongTextLocale ? 3 : 4;
    }
  }

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

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.categoriesTitle),
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(16),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: columnCount,
          crossAxisSpacing: 12,
          mainAxisSpacing: 12,
          childAspectRatio: 1.2,
        ),
        itemCount: 8,
        itemBuilder: (context, index) {
          return _buildCategoryTile(context, l10n, index);
        },
      ),
    );
  }

  Widget _buildCategoryTile(
    BuildContext context,
    AppLocalizations l10n,
    int index,
  ) {
    final categories = [
      (l10n.categoryElectronics, Icons.devices),
      (l10n.categoryClothing, Icons.checkroom),
      (l10n.categoryHome, Icons.home),
      (l10n.categorySports, Icons.sports_soccer),
      (l10n.categoryBooks, Icons.book),
      (l10n.categoryToys, Icons.toys),
      (l10n.categoryBeauty, Icons.spa),
      (l10n.categoryFood, Icons.restaurant),
    ];

    final (title, icon) = categories[index];

    return Card(
      child: InkWell(
        onTap: () {},
        borderRadius: BorderRadius.circular(12),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(icon, size: 36, color: Theme.of(context).primaryColor),
            const SizedBox(height: 8),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 8),
              child: Text(
                title,
                textAlign: TextAlign.center,
                style: Theme.of(context).textTheme.titleSmall,
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

GridView with Localized Categories and Sections

Group grid items with localized headers:

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

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

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

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.shopTitle),
      ),
      body: CustomScrollView(
        slivers: [
          _buildSectionHeader(context, l10n.featuredSection),
          _buildGridSection(context, l10n, 4),
          _buildSectionHeader(context, l10n.newArrivalsSection),
          _buildGridSection(context, l10n, 6),
          _buildSectionHeader(context, l10n.bestSellersSection),
          _buildGridSection(context, l10n, 8),
        ],
      ),
    );
  }

  Widget _buildSectionHeader(BuildContext context, String title) {
    return SliverToBoxAdapter(
      child: Padding(
        padding: const EdgeInsets.fromLTRB(16, 24, 16, 12),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(
              title,
              style: Theme.of(context).textTheme.titleLarge?.copyWith(
                    fontWeight: FontWeight.bold,
                  ),
            ),
            TextButton(
              onPressed: () {},
              child: Text(AppLocalizations.of(context)!.seeAllButton),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildGridSection(
    BuildContext context,
    AppLocalizations l10n,
    int itemCount,
  ) {
    return SliverPadding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      sliver: SliverGrid(
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          crossAxisSpacing: 12,
          mainAxisSpacing: 12,
          childAspectRatio: 0.8,
        ),
        delegate: SliverChildBuilderDelegate(
          (context, index) => _buildProductCard(context, l10n, index),
          childCount: itemCount,
        ),
      ),
    );
  }

  Widget _buildProductCard(
    BuildContext context,
    AppLocalizations l10n,
    int index,
  ) {
    return Card(
      clipBehavior: Clip.antiAlias,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            flex: 3,
            child: Container(
              width: double.infinity,
              color: Colors.grey[200],
              child: Stack(
                children: [
                  Center(
                    child: Icon(
                      Icons.shopping_bag,
                      size: 48,
                      color: Colors.grey[400],
                    ),
                  ),
                  if (index % 3 == 0)
                    Positioned(
                      top: 8,
                      left: 8,
                      child: Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 8,
                          vertical: 4,
                        ),
                        decoration: BoxDecoration(
                          color: Colors.red,
                          borderRadius: BorderRadius.circular(4),
                        ),
                        child: Text(
                          l10n.saleLabel,
                          style: const TextStyle(
                            color: Colors.white,
                            fontSize: 12,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ),
                ],
              ),
            ),
          ),
          Expanded(
            flex: 2,
            child: Padding(
              padding: const EdgeInsets.all(8),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    l10n.productName(index + 1),
                    style: Theme.of(context).textTheme.bodyMedium,
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                  ),
                  const SizedBox(height: 4),
                  Text(
                    l10n.productPrice(19.99 + index * 5),
                    style: Theme.of(context).textTheme.titleSmall?.copyWith(
                          fontWeight: FontWeight.bold,
                        ),
                  ),
                  const Spacer(),
                  Row(
                    children: [
                      Icon(Icons.star, size: 14, color: Colors.amber[700]),
                      const SizedBox(width: 4),
                      Text(
                        l10n.ratingValue(4.5),
                        style: Theme.of(context).textTheme.bodySmall,
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

GridView with Empty and Loading States

Handle localized empty and loading states:

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

enum GridState { loading, empty, loaded, error }

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

  @override
  State<StatefulGridView> createState() => _StatefulGridViewState();
}

class _StatefulGridViewState extends State<StatefulGridView> {
  GridState _state = GridState.loading;
  List<int> _items = [];

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  Future<void> _loadData() async {
    setState(() => _state = GridState.loading);

    await Future.delayed(const Duration(seconds: 2));

    setState(() {
      _items = List.generate(12, (i) => i);
      _state = _items.isEmpty ? GridState.empty : GridState.loaded;
    });
  }

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

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.galleryTitle),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            tooltip: l10n.refreshTooltip,
            onPressed: _loadData,
          ),
        ],
      ),
      body: _buildBody(l10n),
    );
  }

  Widget _buildBody(AppLocalizations l10n) {
    switch (_state) {
      case GridState.loading:
        return _buildLoadingState(l10n);
      case GridState.empty:
        return _buildEmptyState(l10n);
      case GridState.error:
        return _buildErrorState(l10n);
      case GridState.loaded:
        return _buildLoadedState(l10n);
    }
  }

  Widget _buildLoadingState(AppLocalizations l10n) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const CircularProgressIndicator(),
          const SizedBox(height: 16),
          Text(
            l10n.loadingItemsMessage,
            style: TextStyle(color: Colors.grey[600]),
          ),
        ],
      ),
    );
  }

  Widget _buildEmptyState(AppLocalizations l10n) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            Icons.grid_off,
            size: 80,
            color: Colors.grey[400],
          ),
          const SizedBox(height: 16),
          Text(
            l10n.noItemsTitle,
            style: Theme.of(context).textTheme.titleLarge,
          ),
          const SizedBox(height: 8),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 32),
            child: Text(
              l10n.noItemsMessage,
              textAlign: TextAlign.center,
              style: TextStyle(color: Colors.grey[600]),
            ),
          ),
          const SizedBox(height: 24),
          ElevatedButton.icon(
            onPressed: _loadData,
            icon: const Icon(Icons.refresh),
            label: Text(l10n.tryAgainButton),
          ),
        ],
      ),
    );
  }

  Widget _buildErrorState(AppLocalizations l10n) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            Icons.error_outline,
            size: 80,
            color: Colors.red[400],
          ),
          const SizedBox(height: 16),
          Text(
            l10n.errorTitle,
            style: Theme.of(context).textTheme.titleLarge,
          ),
          const SizedBox(height: 8),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 32),
            child: Text(
              l10n.errorLoadingMessage,
              textAlign: TextAlign.center,
              style: TextStyle(color: Colors.grey[600]),
            ),
          ),
          const SizedBox(height: 24),
          ElevatedButton.icon(
            onPressed: _loadData,
            icon: const Icon(Icons.refresh),
            label: Text(l10n.retryButton),
          ),
        ],
      ),
    );
  }

  Widget _buildLoadedState(AppLocalizations l10n) {
    return RefreshIndicator(
      onRefresh: _loadData,
      child: GridView.builder(
        padding: const EdgeInsets.all(16),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          crossAxisSpacing: 8,
          mainAxisSpacing: 8,
        ),
        itemCount: _items.length,
        itemBuilder: (context, index) {
          return Container(
            decoration: BoxDecoration(
              color: Colors.primaries[index % Colors.primaries.length],
              borderRadius: BorderRadius.circular(8),
            ),
            child: Center(
              child: Text(
                l10n.itemNumber(index + 1),
                style: const TextStyle(
                  color: Colors.white,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

GridView with RTL Support

Handle right-to-left layouts properly:

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

class RTLGridView extends StatelessWidget {
  const RTLGridView({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.albumsTitle),
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(16),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          crossAxisSpacing: 12,
          mainAxisSpacing: 12,
          childAspectRatio: 0.85,
        ),
        itemCount: 8,
        itemBuilder: (context, index) {
          return Card(
            clipBehavior: Clip.antiAlias,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                Expanded(
                  flex: 3,
                  child: Container(
                    color: Colors.grey[300],
                    child: Stack(
                      children: [
                        Center(
                          child: Icon(
                            Icons.photo_album,
                            size: 48,
                            color: Colors.grey[500],
                          ),
                        ),
                        // Position badge based on text direction
                        Positioned(
                          top: 8,
                          left: isRTL ? null : 8,
                          right: isRTL ? 8 : null,
                          child: Container(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 8,
                              vertical: 4,
                            ),
                            decoration: BoxDecoration(
                              color: Theme.of(context).primaryColor,
                              borderRadius: BorderRadius.circular(12),
                            ),
                            child: Text(
                              l10n.photoCount(10 + index * 5),
                              style: const TextStyle(
                                color: Colors.white,
                                fontSize: 12,
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
                Expanded(
                  flex: 2,
                  child: Padding(
                    // Use directional padding
                    padding: const EdgeInsetsDirectional.all(12),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          l10n.albumName(index + 1),
                          style: Theme.of(context).textTheme.titleSmall,
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                        ),
                        const SizedBox(height: 4),
                        Text(
                          l10n.albumDate,
                          style: Theme.of(context).textTheme.bodySmall?.copyWith(
                                color: Colors.grey[600],
                              ),
                        ),
                        const Spacer(),
                        Row(
                          children: [
                            Icon(
                              Icons.favorite,
                              size: 16,
                              color: Colors.red[400],
                            ),
                            const SizedBox(width: 4),
                            Text(
                              l10n.likesCount(25 + index * 3),
                              style: Theme.of(context).textTheme.bodySmall,
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

Accessibility for GridView

Ensure proper accessibility labels:

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

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

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

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.menuTitle),
      ),
      body: Semantics(
        label: l10n.menuGridAccessibilityLabel,
        child: GridView.builder(
          padding: const EdgeInsets.all(16),
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            crossAxisSpacing: 12,
            mainAxisSpacing: 12,
          ),
          itemCount: 9,
          itemBuilder: (context, index) {
            return _buildAccessibleMenuItem(context, l10n, index);
          },
        ),
      ),
    );
  }

  Widget _buildAccessibleMenuItem(
    BuildContext context,
    AppLocalizations l10n,
    int index,
  ) {
    final menuItems = [
      (l10n.menuHome, Icons.home, l10n.menuHomeDescription),
      (l10n.menuProfile, Icons.person, l10n.menuProfileDescription),
      (l10n.menuSettings, Icons.settings, l10n.menuSettingsDescription),
      (l10n.menuMessages, Icons.mail, l10n.menuMessagesDescription),
      (l10n.menuNotifications, Icons.notifications, l10n.menuNotificationsDescription),
      (l10n.menuFavorites, Icons.favorite, l10n.menuFavoritesDescription),
      (l10n.menuOrders, Icons.shopping_bag, l10n.menuOrdersDescription),
      (l10n.menuHelp, Icons.help, l10n.menuHelpDescription),
      (l10n.menuAbout, Icons.info, l10n.menuAboutDescription),
    ];

    final (title, icon, description) = menuItems[index];

    return Semantics(
      button: true,
      label: '$title. $description',
      child: Card(
        child: InkWell(
          onTap: () {
            // Announce action
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text(l10n.navigatingTo(title)),
              ),
            );
          },
          borderRadius: BorderRadius.circular(12),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(
                icon,
                size: 32,
                color: Theme.of(context).primaryColor,
              ),
              const SizedBox(height: 8),
              Text(
                title,
                textAlign: TextAlign.center,
                style: Theme.of(context).textTheme.bodySmall,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

GridView with Selection and Localized Actions

Handle selection with localized feedback:

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

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

  @override
  State<SelectableGridView> createState() => _SelectableGridViewState();
}

class _SelectableGridViewState extends State<SelectableGridView> {
  final Set<int> _selectedItems = {};
  bool _isSelectionMode = false;

  void _toggleSelection(int index) {
    setState(() {
      if (_selectedItems.contains(index)) {
        _selectedItems.remove(index);
        if (_selectedItems.isEmpty) {
          _isSelectionMode = false;
        }
      } else {
        _selectedItems.add(index);
      }
    });
  }

  void _startSelectionMode(int index) {
    setState(() {
      _isSelectionMode = true;
      _selectedItems.add(index);
    });
  }

  void _clearSelection() {
    setState(() {
      _selectedItems.clear();
      _isSelectionMode = false;
    });
  }

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

    return Scaffold(
      appBar: AppBar(
        title: _isSelectionMode
            ? Text(l10n.selectedCount(_selectedItems.length))
            : Text(l10n.photosTitle),
        leading: _isSelectionMode
            ? IconButton(
                icon: const Icon(Icons.close),
                tooltip: l10n.cancelSelectionTooltip,
                onPressed: _clearSelection,
              )
            : null,
        actions: _isSelectionMode
            ? [
                IconButton(
                  icon: const Icon(Icons.share),
                  tooltip: l10n.shareTooltip,
                  onPressed: () {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text(
                          l10n.sharingItems(_selectedItems.length),
                        ),
                      ),
                    );
                  },
                ),
                IconButton(
                  icon: const Icon(Icons.delete),
                  tooltip: l10n.deleteTooltip,
                  onPressed: () => _showDeleteDialog(context, l10n),
                ),
              ]
            : null,
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(8),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          crossAxisSpacing: 4,
          mainAxisSpacing: 4,
        ),
        itemCount: 24,
        itemBuilder: (context, index) {
          final isSelected = _selectedItems.contains(index);

          return GestureDetector(
            onTap: () {
              if (_isSelectionMode) {
                _toggleSelection(index);
              }
            },
            onLongPress: () {
              if (!_isSelectionMode) {
                _startSelectionMode(index);
              }
            },
            child: Semantics(
              label: _isSelectionMode
                  ? isSelected
                      ? l10n.photoSelectedLabel(index + 1)
                      : l10n.photoNotSelectedLabel(index + 1)
                  : l10n.photoLabel(index + 1),
              child: Container(
                decoration: BoxDecoration(
                  color: Colors.grey[300],
                  border: isSelected
                      ? Border.all(
                          color: Theme.of(context).primaryColor,
                          width: 3,
                        )
                      : null,
                ),
                child: Stack(
                  fit: StackFit.expand,
                  children: [
                    Icon(
                      Icons.photo,
                      size: 40,
                      color: Colors.grey[500],
                    ),
                    if (_isSelectionMode)
                      Positioned(
                        top: 4,
                        right: 4,
                        child: Container(
                          width: 24,
                          height: 24,
                          decoration: BoxDecoration(
                            color: isSelected
                                ? Theme.of(context).primaryColor
                                : Colors.white.withOpacity(0.7),
                            shape: BoxShape.circle,
                            border: Border.all(
                              color: Theme.of(context).primaryColor,
                              width: 2,
                            ),
                          ),
                          child: isSelected
                              ? const Icon(
                                  Icons.check,
                                  size: 16,
                                  color: Colors.white,
                                )
                              : null,
                        ),
                      ),
                  ],
                ),
              ),
            ),
          );
        },
      ),
      floatingActionButton: _isSelectionMode
          ? null
          : FloatingActionButton(
              onPressed: () {},
              tooltip: l10n.addPhotoTooltip,
              child: const Icon(Icons.add_photo_alternate),
            ),
    );
  }

  void _showDeleteDialog(BuildContext context, AppLocalizations l10n) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(l10n.deletePhotosTitle),
        content: Text(
          l10n.deletePhotosConfirmation(_selectedItems.length),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text(l10n.cancelButton),
          ),
          TextButton(
            onPressed: () {
              Navigator.pop(context);
              setState(() {
                _selectedItems.clear();
                _isSelectionMode = false;
              });
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text(l10n.photosDeletedMessage),
                ),
              );
            },
            style: TextButton.styleFrom(foregroundColor: Colors.red),
            child: Text(l10n.deleteButton),
          ),
        ],
      ),
    );
  }
}

ARB Translations for GridView

Add these entries to your ARB files:

{
  "productsTitle": "Products",
  "@productsTitle": {
    "description": "Title for products grid"
  },
  "productName": "Product {number}",
  "@productName": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "productPrice": "${price}",
  "@productPrice": {
    "placeholders": {
      "price": {"type": "double", "format": "currency", "optionalParameters": {"symbol": "$"}}
    }
  },

  "categoriesTitle": "Categories",
  "categoryElectronics": "Electronics",
  "categoryClothing": "Clothing",
  "categoryHome": "Home & Garden",
  "categorySports": "Sports",
  "categoryBooks": "Books",
  "categoryToys": "Toys",
  "categoryBeauty": "Beauty",
  "categoryFood": "Food & Drinks",

  "shopTitle": "Shop",
  "featuredSection": "Featured",
  "newArrivalsSection": "New Arrivals",
  "bestSellersSection": "Best Sellers",
  "seeAllButton": "See All",
  "saleLabel": "SALE",
  "ratingValue": "{rating} stars",
  "@ratingValue": {
    "placeholders": {
      "rating": {"type": "double"}
    }
  },

  "galleryTitle": "Gallery",
  "refreshTooltip": "Refresh",
  "loadingItemsMessage": "Loading items...",
  "noItemsTitle": "No Items Found",
  "noItemsMessage": "There are no items to display. Try refreshing or adding new items.",
  "tryAgainButton": "Try Again",
  "errorTitle": "Something Went Wrong",
  "errorLoadingMessage": "We couldn't load the items. Please check your connection and try again.",
  "retryButton": "Retry",
  "itemNumber": "Item {number}",
  "@itemNumber": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },

  "albumsTitle": "Albums",
  "photoCount": "{count} photos",
  "@photoCount": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },
  "albumName": "Album {number}",
  "@albumName": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "albumDate": "January 2024",
  "likesCount": "{count} likes",
  "@likesCount": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },

  "menuTitle": "Menu",
  "menuGridAccessibilityLabel": "Application menu grid with 9 options",
  "menuHome": "Home",
  "menuHomeDescription": "Go to the home screen",
  "menuProfile": "Profile",
  "menuProfileDescription": "View and edit your profile",
  "menuSettings": "Settings",
  "menuSettingsDescription": "App settings and preferences",
  "menuMessages": "Messages",
  "menuMessagesDescription": "View your messages",
  "menuNotifications": "Notifications",
  "menuNotificationsDescription": "View notification settings",
  "menuFavorites": "Favorites",
  "menuFavoritesDescription": "View saved favorites",
  "menuOrders": "Orders",
  "menuOrdersDescription": "View order history",
  "menuHelp": "Help",
  "menuHelpDescription": "Get help and support",
  "menuAbout": "About",
  "menuAboutDescription": "About this app",
  "navigatingTo": "Navigating to {destination}",
  "@navigatingTo": {
    "placeholders": {
      "destination": {"type": "String"}
    }
  },

  "photosTitle": "Photos",
  "selectedCount": "{count} selected",
  "@selectedCount": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },
  "cancelSelectionTooltip": "Cancel selection",
  "shareTooltip": "Share",
  "deleteTooltip": "Delete",
  "sharingItems": "Sharing {count} items",
  "@sharingItems": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },
  "photoLabel": "Photo {number}",
  "@photoLabel": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "photoSelectedLabel": "Photo {number}, selected",
  "@photoSelectedLabel": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "photoNotSelectedLabel": "Photo {number}, not selected",
  "@photoNotSelectedLabel": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "addPhotoTooltip": "Add photo",
  "deletePhotosTitle": "Delete Photos",
  "deletePhotosConfirmation": "Are you sure you want to delete {count} photos? This action cannot be undone.",
  "@deletePhotosConfirmation": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },
  "cancelButton": "Cancel",
  "deleteButton": "Delete",
  "photosDeletedMessage": "Photos deleted successfully"
}

Testing GridView Localization

Write tests for your localized GridView:

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

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

      expect(find.text('Products'), findsOneWidget);
    });

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

      expect(find.text('Productos'), findsOneWidget);
    });

    testWidgets('shows loading state with localized message', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          locale: const Locale('en'),
          home: const StatefulGridView(),
        ),
      );

      expect(find.text('Loading items...'), findsOneWidget);
      expect(find.byType(CircularProgressIndicator), 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: RTLGridView(),
          ),
        ),
      );

      // Verify RTL text is displayed
      expect(find.text('الألبومات'), findsOneWidget);
    });

    testWidgets('selection mode shows localized count', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          locale: const Locale('en'),
          home: const SelectableGridView(),
        ),
      );

      // Long press to enter selection mode
      await tester.longPress(find.byType(Container).first);
      await tester.pumpAndSettle();

      expect(find.text('1 selected'), findsOneWidget);
    });
  });
}

Summary

Localizing GridView in Flutter requires:

  1. Localized item content with proper text handling
  2. Adaptive layouts that adjust columns for different text lengths
  3. Sectioned grids with translated headers
  4. Empty and loading states with appropriate messages
  5. RTL support with directional positioning
  6. Accessibility labels for screen reader users
  7. Selection feedback in the user's language
  8. Comprehensive testing across different locales

GridView is fundamental for displaying collections, and proper localization ensures your app looks polished and feels natural for users in any language.