← Back to Blog

Flutter Wrap Widget Localization: Responsive Tags, Chips, and Flow Layouts

flutterwrapchipstagslocalizationresponsive

Flutter Wrap Widget Localization: Responsive Tags, Chips, and Flow Layouts

The Wrap widget is essential for creating responsive layouts where children flow naturally across lines. Localizing Wrap requires handling varying text lengths, RTL support, dynamic spacing, and accessibility features. This guide covers everything you need to know about localizing Wrap in Flutter.

Understanding Wrap Localization

Wrap requires localization for:

  • Tag and chip labels: Text that varies significantly in length
  • Flow direction: Proper handling for RTL languages
  • Spacing adjustments: Accommodating different text densities
  • Action labels: Buttons and interactive elements
  • Empty states: When no items are available
  • Overflow handling: Managing very long translations

Basic Wrap with Localized Tags

Start with a simple localized tag layout:

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

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

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

    final tags = [
      l10n.tagPopular,
      l10n.tagNew,
      l10n.tagFeatured,
      l10n.tagSale,
      l10n.tagLimited,
      l10n.tagExclusive,
      l10n.tagBestSeller,
      l10n.tagRecommended,
    ];

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.tagsTitle),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.selectTagsMessage,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 16),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: tags.map((tag) {
                return Chip(
                  label: Text(tag),
                  onDeleted: () {},
                  deleteButtonTooltipMessage: l10n.removeTagTooltip,
                );
              }).toList(),
            ),
          ],
        ),
      ),
    );
  }
}

Wrap with Adaptive Spacing for Different Languages

Adjust spacing based on language characteristics:

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

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

  double _getSpacing(BuildContext context) {
    final locale = Localizations.localeOf(context);

    // Languages with compact characters can use smaller spacing
    final compactLanguages = ['zh', 'ja', 'ko'];
    if (compactLanguages.contains(locale.languageCode)) {
      return 4.0;
    }

    // Languages with longer words need more spacing
    final verboseLanguages = ['de', 'ru', 'fi', 'pl'];
    if (verboseLanguages.contains(locale.languageCode)) {
      return 12.0;
    }

    return 8.0;
  }

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

    final categories = [
      l10n.categoryElectronics,
      l10n.categoryClothing,
      l10n.categoryHome,
      l10n.categorySports,
      l10n.categoryBooks,
      l10n.categoryMusic,
      l10n.categoryMovies,
      l10n.categoryGames,
    ];

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.categoriesTitle),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.browseCategoriesMessage,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 16),
            Wrap(
              spacing: spacing,
              runSpacing: spacing,
              children: categories.map((category) {
                return ActionChip(
                  label: Text(category),
                  onPressed: () {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text(l10n.selectedCategory(category)),
                      ),
                    );
                  },
                );
              }).toList(),
            ),
          ],
        ),
      ),
    );
  }
}

Wrap with RTL Support

Handle right-to-left layouts properly:

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

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

  @override
  State<RTLWrap> createState() => _RTLWrapState();
}

class _RTLWrapState extends State<RTLWrap> {
  final Set<String> _selectedFilters = {};

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

    final filters = [
      (l10n.filterPrice, Icons.attach_money),
      (l10n.filterRating, Icons.star),
      (l10n.filterDistance, Icons.location_on),
      (l10n.filterOpen, Icons.access_time),
      (l10n.filterDelivery, Icons.delivery_dining),
      (l10n.filterPickup, Icons.store),
    ];

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.filtersTitle),
        actions: [
          if (_selectedFilters.isNotEmpty)
            TextButton(
              onPressed: () {
                setState(() => _selectedFilters.clear());
              },
              child: Text(l10n.clearAllButton),
            ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.activeFiltersLabel,
              style: Theme.of(context).textTheme.titleSmall?.copyWith(
                    color: Colors.grey[600],
                  ),
            ),
            const SizedBox(height: 12),
            Wrap(
              // Wrap automatically handles RTL direction
              spacing: 8,
              runSpacing: 8,
              children: filters.map((filter) {
                final (label, icon) = filter;
                final isSelected = _selectedFilters.contains(label);

                return FilterChip(
                  label: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Icon(
                        icon,
                        size: 16,
                        color: isSelected
                            ? Theme.of(context).colorScheme.onPrimary
                            : Theme.of(context).colorScheme.onSurface,
                      ),
                      const SizedBox(width: 4),
                      Text(label),
                    ],
                  ),
                  selected: isSelected,
                  onSelected: (selected) {
                    setState(() {
                      if (selected) {
                        _selectedFilters.add(label);
                      } else {
                        _selectedFilters.remove(label);
                      }
                    });
                  },
                );
              }).toList(),
            ),
            if (_selectedFilters.isNotEmpty) ...[
              const SizedBox(height: 24),
              Text(
                l10n.selectedFiltersCount(_selectedFilters.length),
                style: Theme.of(context).textTheme.bodyMedium,
              ),
            ],
          ],
        ),
      ),
    );
  }
}

Wrap with Selectable Skills/Interests

Create a localized skill selection interface:

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

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

  @override
  State<SkillSelectionWrap> createState() => _SkillSelectionWrapState();
}

class _SkillSelectionWrapState extends State<SkillSelectionWrap> {
  final Set<String> _selectedSkills = {};
  static const int maxSelections = 5;

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

    final skillGroups = {
      l10n.skillGroupTechnical: [
        l10n.skillFlutter,
        l10n.skillDart,
        l10n.skillJavaScript,
        l10n.skillPython,
        l10n.skillSwift,
        l10n.skillKotlin,
      ],
      l10n.skillGroupDesign: [
        l10n.skillUIDesign,
        l10n.skillUXResearch,
        l10n.skillPrototyping,
        l10n.skillBranding,
      ],
      l10n.skillGroupSoft: [
        l10n.skillCommunication,
        l10n.skillTeamwork,
        l10n.skillProblemSolving,
        l10n.skillLeadership,
      ],
    };

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.selectSkillsTitle),
      ),
      body: Column(
        children: [
          Expanded(
            child: SingleChildScrollView(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    l10n.selectSkillsMessage(maxSelections),
                    style: Theme.of(context).textTheme.bodyLarge,
                  ),
                  const SizedBox(height: 8),
                  Text(
                    l10n.skillsSelectedCount(
                      _selectedSkills.length,
                      maxSelections,
                    ),
                    style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                          color: _selectedSkills.length >= maxSelections
                              ? Colors.orange
                              : Colors.grey[600],
                        ),
                  ),
                  const SizedBox(height: 24),
                  ...skillGroups.entries.map((group) {
                    return Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          group.key,
                          style:
                              Theme.of(context).textTheme.titleMedium?.copyWith(
                                    fontWeight: FontWeight.bold,
                                  ),
                        ),
                        const SizedBox(height: 12),
                        Wrap(
                          spacing: 8,
                          runSpacing: 8,
                          children: group.value.map((skill) {
                            final isSelected = _selectedSkills.contains(skill);
                            final canSelect = _selectedSkills.length <
                                    maxSelections ||
                                isSelected;

                            return ChoiceChip(
                              label: Text(skill),
                              selected: isSelected,
                              onSelected: canSelect
                                  ? (selected) {
                                      setState(() {
                                        if (selected) {
                                          _selectedSkills.add(skill);
                                        } else {
                                          _selectedSkills.remove(skill);
                                        }
                                      });
                                    }
                                  : null,
                            );
                          }).toList(),
                        ),
                        const SizedBox(height: 24),
                      ],
                    );
                  }),
                ],
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16),
            child: SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _selectedSkills.isNotEmpty
                    ? () {
                        ScaffoldMessenger.of(context).showSnackBar(
                          SnackBar(
                            content: Text(
                              l10n.skillsSavedMessage(_selectedSkills.length),
                            ),
                          ),
                        );
                      }
                    : null,
                child: Text(l10n.saveSkillsButton),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Wrap with Input Chips

Handle user input with localized chips:

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

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

  @override
  State<InputChipsWrap> createState() => _InputChipsWrapState();
}

class _InputChipsWrapState extends State<InputChipsWrap> {
  final List<String> _tags = [];
  final TextEditingController _controller = TextEditingController();

  void _addTag(String tag) {
    final trimmed = tag.trim();
    if (trimmed.isNotEmpty && !_tags.contains(trimmed)) {
      setState(() {
        _tags.add(trimmed);
      });
      _controller.clear();
    }
  }

  void _removeTag(String tag) {
    setState(() {
      _tags.remove(tag);
    });
  }

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

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

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.addTagsTitle),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.addTagsDescription,
              style: Theme.of(context).textTheme.bodyMedium,
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _controller,
              decoration: InputDecoration(
                hintText: l10n.tagInputHint,
                suffixIcon: IconButton(
                  icon: const Icon(Icons.add),
                  tooltip: l10n.addTagTooltip,
                  onPressed: () => _addTag(_controller.text),
                ),
                border: const OutlineInputBorder(),
              ),
              onSubmitted: _addTag,
              textInputAction: TextInputAction.done,
            ),
            const SizedBox(height: 16),
            if (_tags.isEmpty)
              Text(
                l10n.noTagsMessage,
                style: TextStyle(
                  color: Colors.grey[600],
                  fontStyle: FontStyle.italic,
                ),
              )
            else
              Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Text(
                        l10n.yourTagsLabel,
                        style: Theme.of(context).textTheme.titleSmall,
                      ),
                      TextButton(
                        onPressed: () {
                          setState(() => _tags.clear());
                        },
                        child: Text(l10n.clearAllButton),
                      ),
                    ],
                  ),
                  const SizedBox(height: 8),
                  Wrap(
                    spacing: 8,
                    runSpacing: 8,
                    children: _tags.map((tag) {
                      return InputChip(
                        label: Text(tag),
                        onDeleted: () => _removeTag(tag),
                        deleteButtonTooltipMessage: l10n.removeTagTooltip,
                      );
                    }).toList(),
                  ),
                ],
              ),
            const Spacer(),
            Text(
              l10n.tagCountInfo(_tags.length),
              style: Theme.of(context).textTheme.bodySmall?.copyWith(
                    color: Colors.grey[600],
                  ),
            ),
          ],
        ),
      ),
    );
  }
}

Accessibility for Wrap

Ensure proper accessibility labels:

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

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

  @override
  State<AccessibleWrap> createState() => _AccessibleWrapState();
}

class _AccessibleWrapState extends State<AccessibleWrap> {
  final Set<String> _selectedOptions = {};

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

    final options = [
      (l10n.optionExpress, l10n.optionExpressDescription),
      (l10n.optionStandard, l10n.optionStandardDescription),
      (l10n.optionEconomy, l10n.optionEconomyDescription),
      (l10n.optionPickup, l10n.optionPickupDescription),
    ];

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.shippingOptionsTitle),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Semantics(
              header: true,
              child: Text(
                l10n.selectShippingMessage,
                style: Theme.of(context).textTheme.titleMedium,
              ),
            ),
            const SizedBox(height: 16),
            Semantics(
              label: l10n.shippingOptionsAccessibilityLabel,
              child: Wrap(
                spacing: 8,
                runSpacing: 8,
                children: options.map((option) {
                  final (label, description) = option;
                  final isSelected = _selectedOptions.contains(label);

                  return Semantics(
                    button: true,
                    selected: isSelected,
                    label: '$label. $description. ${isSelected ? l10n.selectedLabel : l10n.notSelectedLabel}',
                    child: ChoiceChip(
                      label: Text(label),
                      selected: isSelected,
                      onSelected: (selected) {
                        setState(() {
                          if (selected) {
                            _selectedOptions.add(label);
                          } else {
                            _selectedOptions.remove(label);
                          }
                        });

                        // Announce selection change
                        final message = selected
                            ? l10n.optionSelectedAnnouncement(label)
                            : l10n.optionDeselectedAnnouncement(label);
                        ScaffoldMessenger.of(context).showSnackBar(
                          SnackBar(
                            content: Text(message),
                            duration: const Duration(seconds: 1),
                          ),
                        );
                      },
                      tooltip: description,
                    ),
                  );
                }).toList(),
              ),
            ),
            const SizedBox(height: 24),
            if (_selectedOptions.isNotEmpty)
              Semantics(
                liveRegion: true,
                child: Text(
                  l10n.selectedOptionsCount(_selectedOptions.length),
                  style: Theme.of(context).textTheme.bodyMedium,
                ),
              ),
          ],
        ),
      ),
    );
  }
}

Wrap with Long Text Handling

Handle translations that vary significantly in length:

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

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

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

    // These could have very different lengths in different languages
    final features = [
      l10n.featureFreeShipping,
      l10n.featureEasyReturns,
      l10n.featureSecurePayment,
      l10n.featureCustomerSupport,
      l10n.featureFastDelivery,
      l10n.featureQualityGuarantee,
    ];

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.featuresTitle),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.ourFeaturesMessage,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 16),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: features.map((feature) {
                return ConstrainedBox(
                  // Limit maximum width to prevent overly long chips
                  constraints: BoxConstraints(
                    maxWidth: MediaQuery.of(context).size.width * 0.45,
                  ),
                  child: Chip(
                    avatar: const Icon(Icons.check_circle, size: 18),
                    label: Text(
                      feature,
                      overflow: TextOverflow.ellipsis,
                    ),
                    materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
                  ),
                );
              }).toList(),
            ),
            const SizedBox(height: 32),
            Text(
              l10n.badgesTitle,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 16),
            Wrap(
              spacing: 12,
              runSpacing: 12,
              children: [
                _buildBadge(context, l10n.badgeVerified, Colors.blue),
                _buildBadge(context, l10n.badgeTrusted, Colors.green),
                _buildBadge(context, l10n.badgePremium, Colors.orange),
                _buildBadge(context, l10n.badgeTopRated, Colors.purple),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildBadge(BuildContext context, String label, Color color) {
    return Container(
      constraints: BoxConstraints(
        maxWidth: MediaQuery.of(context).size.width * 0.4,
      ),
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
      decoration: BoxDecoration(
        color: color.withOpacity(0.1),
        borderRadius: BorderRadius.circular(20),
        border: Border.all(color: color),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(Icons.verified, size: 16, color: color),
          const SizedBox(width: 6),
          Flexible(
            child: Text(
              label,
              style: TextStyle(
                color: color,
                fontWeight: FontWeight.bold,
              ),
              overflow: TextOverflow.ellipsis,
            ),
          ),
        ],
      ),
    );
  }
}

ARB Translations for Wrap

Add these entries to your ARB files:

{
  "tagsTitle": "Tags",
  "@tagsTitle": {
    "description": "Title for tags screen"
  },
  "selectTagsMessage": "Select tags to filter results",
  "removeTagTooltip": "Remove tag",
  "tagPopular": "Popular",
  "tagNew": "New",
  "tagFeatured": "Featured",
  "tagSale": "Sale",
  "tagLimited": "Limited Edition",
  "tagExclusive": "Exclusive",
  "tagBestSeller": "Best Seller",
  "tagRecommended": "Recommended",

  "categoriesTitle": "Categories",
  "browseCategoriesMessage": "Browse by category",
  "selectedCategory": "Selected: {category}",
  "@selectedCategory": {
    "placeholders": {
      "category": {"type": "String"}
    }
  },
  "categoryElectronics": "Electronics",
  "categoryClothing": "Clothing",
  "categoryHome": "Home & Garden",
  "categorySports": "Sports",
  "categoryBooks": "Books",
  "categoryMusic": "Music",
  "categoryMovies": "Movies",
  "categoryGames": "Games",

  "filtersTitle": "Filters",
  "clearAllButton": "Clear All",
  "activeFiltersLabel": "Active filters",
  "filterPrice": "Price",
  "filterRating": "Rating",
  "filterDistance": "Distance",
  "filterOpen": "Open Now",
  "filterDelivery": "Delivery",
  "filterPickup": "Pickup",
  "selectedFiltersCount": "{count} filters selected",
  "@selectedFiltersCount": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },

  "selectSkillsTitle": "Select Your Skills",
  "selectSkillsMessage": "Choose up to {max} skills that best describe you",
  "@selectSkillsMessage": {
    "placeholders": {
      "max": {"type": "int"}
    }
  },
  "skillsSelectedCount": "{current} of {max} selected",
  "@skillsSelectedCount": {
    "placeholders": {
      "current": {"type": "int"},
      "max": {"type": "int"}
    }
  },
  "skillGroupTechnical": "Technical Skills",
  "skillGroupDesign": "Design Skills",
  "skillGroupSoft": "Soft Skills",
  "skillFlutter": "Flutter",
  "skillDart": "Dart",
  "skillJavaScript": "JavaScript",
  "skillPython": "Python",
  "skillSwift": "Swift",
  "skillKotlin": "Kotlin",
  "skillUIDesign": "UI Design",
  "skillUXResearch": "UX Research",
  "skillPrototyping": "Prototyping",
  "skillBranding": "Branding",
  "skillCommunication": "Communication",
  "skillTeamwork": "Teamwork",
  "skillProblemSolving": "Problem Solving",
  "skillLeadership": "Leadership",
  "skillsSavedMessage": "{count} skills saved successfully",
  "@skillsSavedMessage": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },
  "saveSkillsButton": "Save Skills",

  "addTagsTitle": "Add Tags",
  "addTagsDescription": "Add tags to help organize your content",
  "tagInputHint": "Enter a tag...",
  "addTagTooltip": "Add tag",
  "noTagsMessage": "No tags added yet",
  "yourTagsLabel": "Your tags",
  "tagCountInfo": "{count} tags added",
  "@tagCountInfo": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },

  "shippingOptionsTitle": "Shipping Options",
  "selectShippingMessage": "Select your preferred shipping methods",
  "shippingOptionsAccessibilityLabel": "Available shipping options",
  "optionExpress": "Express",
  "optionExpressDescription": "Delivery in 1-2 business days",
  "optionStandard": "Standard",
  "optionStandardDescription": "Delivery in 3-5 business days",
  "optionEconomy": "Economy",
  "optionEconomyDescription": "Delivery in 7-10 business days",
  "optionPickup": "Store Pickup",
  "optionPickupDescription": "Pick up at your nearest store",
  "selectedLabel": "selected",
  "notSelectedLabel": "not selected",
  "optionSelectedAnnouncement": "{option} selected",
  "@optionSelectedAnnouncement": {
    "placeholders": {
      "option": {"type": "String"}
    }
  },
  "optionDeselectedAnnouncement": "{option} deselected",
  "@optionDeselectedAnnouncement": {
    "placeholders": {
      "option": {"type": "String"}
    }
  },
  "selectedOptionsCount": "{count} options selected",
  "@selectedOptionsCount": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },

  "featuresTitle": "Features",
  "ourFeaturesMessage": "Why choose us",
  "featureFreeShipping": "Free Shipping",
  "featureEasyReturns": "Easy Returns",
  "featureSecurePayment": "Secure Payment",
  "featureCustomerSupport": "24/7 Customer Support",
  "featureFastDelivery": "Fast Delivery",
  "featureQualityGuarantee": "Quality Guarantee",
  "badgesTitle": "Trust Badges",
  "badgeVerified": "Verified Seller",
  "badgeTrusted": "Trusted Brand",
  "badgePremium": "Premium Quality",
  "badgeTopRated": "Top Rated"
}

Testing Wrap Localization

Write tests for your localized Wrap:

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

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

      expect(find.text('Popular'), findsOneWidget);
      expect(find.text('New'), findsOneWidget);
      expect(find.text('Featured'), findsOneWidget);
    });

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

      expect(find.text('Popular'), findsOneWidget);
      expect(find.text('Nuevo'), findsOneWidget);
      expect(find.text('Destacado'), 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: RTLWrap(),
          ),
        ),
      );

      // Verify Wrap respects RTL direction
      final wrap = tester.widget<Wrap>(find.byType(Wrap));
      expect(wrap, isNotNull);
    });

    testWidgets('skill selection respects maximum', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          locale: const Locale('en'),
          home: const SkillSelectionWrap(),
        ),
      );

      expect(find.text('0 of 5 selected'), findsOneWidget);

      // Select a skill
      await tester.tap(find.text('Flutter'));
      await tester.pumpAndSettle();

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

Summary

Localizing Wrap in Flutter requires:

  1. Flexible spacing that adapts to different language characteristics
  2. RTL support with automatic flow direction handling
  3. Constrained widths for chips with varying text lengths
  4. Grouped sections with localized headers
  5. Input handling for user-generated tags
  6. Accessibility labels for screen reader users
  7. Selection feedback in the user's language
  8. Comprehensive testing across different locales

Wrap is essential for creating responsive, flowing layouts, and proper localization ensures your tags, chips, and badges look polished for users in any language.