← Back to Blog

Flutter PageView Localization: Onboarding, Tutorials, and Carousel Content

flutterpageviewonboardingcarousellocalizationaccessibility

Flutter PageView Localization: Onboarding, Tutorials, and Carousel Content

PageView is essential for onboarding flows, image carousels, tutorials, and swipeable content. Localizing PageView requires handling page indicators, navigation buttons, swipe instructions, and content across different languages. This guide covers everything you need to know about localizing PageView in Flutter.

Understanding PageView Localization

PageView requires localization for:

  • Onboarding content: Titles, descriptions, and images
  • Page indicators: Current page and total count
  • Navigation buttons: Next, Skip, Get Started
  • Carousel captions: Image descriptions and alt text
  • Swipe instructions: Accessibility hints
  • RTL support: Proper swipe direction for Arabic, Hebrew

Basic Onboarding PageView

Start with a localized onboarding flow:

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

class LocalizedOnboarding extends StatefulWidget {
  final VoidCallback onComplete;

  const LocalizedOnboarding({
    super.key,
    required this.onComplete,
  });

  @override
  State<LocalizedOnboarding> createState() => _LocalizedOnboardingState();
}

class _LocalizedOnboardingState extends State<LocalizedOnboarding> {
  final PageController _pageController = PageController();
  int _currentPage = 0;

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

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

    final pages = [
      OnboardingPage(
        icon: Icons.translate,
        title: l10n.onboardingTitle1,
        description: l10n.onboardingDescription1,
        color: Colors.blue,
      ),
      OnboardingPage(
        icon: Icons.speed,
        title: l10n.onboardingTitle2,
        description: l10n.onboardingDescription2,
        color: Colors.green,
      ),
      OnboardingPage(
        icon: Icons.security,
        title: l10n.onboardingTitle3,
        description: l10n.onboardingDescription3,
        color: Colors.purple,
      ),
    ];

    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            // Skip button
            Align(
              alignment: AlignmentDirectional.topEnd,
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: TextButton(
                  onPressed: widget.onComplete,
                  child: Text(l10n.skipButton),
                ),
              ),
            ),
            // Page content
            Expanded(
              child: PageView.builder(
                controller: _pageController,
                itemCount: pages.length,
                onPageChanged: (index) {
                  setState(() => _currentPage = index);
                },
                itemBuilder: (context, index) {
                  final page = pages[index];
                  return Padding(
                    padding: const EdgeInsets.all(32),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Icon(
                          page.icon,
                          size: 120,
                          color: page.color,
                        ),
                        const SizedBox(height: 32),
                        Text(
                          page.title,
                          style: Theme.of(context).textTheme.headlineMedium,
                          textAlign: TextAlign.center,
                        ),
                        const SizedBox(height: 16),
                        Text(
                          page.description,
                          style: Theme.of(context).textTheme.bodyLarge?.copyWith(
                                color: Colors.grey[600],
                              ),
                          textAlign: TextAlign.center,
                        ),
                      ],
                    ),
                  );
                },
              ),
            ),
            // Page indicator
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 16),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: List.generate(
                  pages.length,
                  (index) => AnimatedContainer(
                    duration: const Duration(milliseconds: 300),
                    margin: const EdgeInsets.symmetric(horizontal: 4),
                    width: _currentPage == index ? 24 : 8,
                    height: 8,
                    decoration: BoxDecoration(
                      color: _currentPage == index
                          ? Theme.of(context).primaryColor
                          : Colors.grey[300],
                      borderRadius: BorderRadius.circular(4),
                    ),
                  ),
                ),
              ),
            ),
            // Navigation buttons
            Padding(
              padding: const EdgeInsets.all(16),
              child: Row(
                children: [
                  if (_currentPage > 0)
                    TextButton(
                      onPressed: () {
                        _pageController.previousPage(
                          duration: const Duration(milliseconds: 300),
                          curve: Curves.easeInOut,
                        );
                      },
                      child: Text(l10n.previousButton),
                    )
                  else
                    const SizedBox(width: 80),
                  const Spacer(),
                  Text(
                    l10n.pageIndicator(_currentPage + 1, pages.length),
                    style: TextStyle(color: Colors.grey[600]),
                  ),
                  const Spacer(),
                  _currentPage < pages.length - 1
                      ? ElevatedButton(
                          onPressed: () {
                            _pageController.nextPage(
                              duration: const Duration(milliseconds: 300),
                              curve: Curves.easeInOut,
                            );
                          },
                          child: Text(l10n.nextButton),
                        )
                      : ElevatedButton(
                          onPressed: widget.onComplete,
                          child: Text(l10n.getStartedButton),
                        ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class OnboardingPage {
  final IconData icon;
  final String title;
  final String description;
  final Color color;

  OnboardingPage({
    required this.icon,
    required this.title,
    required this.description,
    required this.color,
  });
}

Image Carousel with Localized Captions

Create a carousel with localized image descriptions:

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

class LocalizedImageCarousel extends StatefulWidget {
  final List<CarouselItem> items;

  const LocalizedImageCarousel({
    super.key,
    required this.items,
  });

  @override
  State<LocalizedImageCarousel> createState() => _LocalizedImageCarouselState();
}

class _LocalizedImageCarouselState extends State<LocalizedImageCarousel> {
  final PageController _pageController = PageController();
  int _currentPage = 0;

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

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

    return Column(
      children: [
        // Carousel
        SizedBox(
          height: 300,
          child: PageView.builder(
            controller: _pageController,
            itemCount: widget.items.length,
            onPageChanged: (index) {
              setState(() => _currentPage = index);
            },
            itemBuilder: (context, index) {
              final item = widget.items[index];
              return Padding(
                padding: const EdgeInsets.symmetric(horizontal: 16),
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(16),
                  child: Stack(
                    fit: StackFit.expand,
                    children: [
                      // Image placeholder
                      Container(
                        color: Colors.grey[300],
                        child: Center(
                          child: Icon(
                            Icons.image,
                            size: 64,
                            color: Colors.grey[500],
                          ),
                        ),
                      ),
                      // Gradient overlay for text
                      Positioned(
                        bottom: 0,
                        left: 0,
                        right: 0,
                        child: Container(
                          padding: const EdgeInsets.all(16),
                          decoration: BoxDecoration(
                            gradient: LinearGradient(
                              begin: Alignment.bottomCenter,
                              end: Alignment.topCenter,
                              colors: [
                                Colors.black.withOpacity(0.8),
                                Colors.transparent,
                              ],
                            ),
                          ),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                item.title,
                                style: const TextStyle(
                                  color: Colors.white,
                                  fontSize: 18,
                                  fontWeight: FontWeight.bold,
                                ),
                              ),
                              const SizedBox(height: 4),
                              Text(
                                item.description,
                                style: TextStyle(
                                  color: Colors.white.withOpacity(0.8),
                                  fontSize: 14,
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              );
            },
          ),
        ),
        const SizedBox(height: 16),
        // Indicator dots
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            IconButton(
              icon: const Icon(Icons.chevron_left),
              onPressed: _currentPage > 0
                  ? () {
                      _pageController.previousPage(
                        duration: const Duration(milliseconds: 300),
                        curve: Curves.easeInOut,
                      );
                    }
                  : null,
              tooltip: l10n.previousImageTooltip,
            ),
            ...List.generate(
              widget.items.length,
              (index) => GestureDetector(
                onTap: () {
                  _pageController.animateToPage(
                    index,
                    duration: const Duration(milliseconds: 300),
                    curve: Curves.easeInOut,
                  );
                },
                child: Container(
                  margin: const EdgeInsets.symmetric(horizontal: 4),
                  width: 10,
                  height: 10,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    color: _currentPage == index
                        ? Theme.of(context).primaryColor
                        : Colors.grey[300],
                  ),
                ),
              ),
            ),
            IconButton(
              icon: const Icon(Icons.chevron_right),
              onPressed: _currentPage < widget.items.length - 1
                  ? () {
                      _pageController.nextPage(
                        duration: const Duration(milliseconds: 300),
                        curve: Curves.easeInOut,
                      );
                    }
                  : null,
              tooltip: l10n.nextImageTooltip,
            ),
          ],
        ),
        // Image counter
        Text(
          l10n.imageCounter(_currentPage + 1, widget.items.length),
          style: TextStyle(color: Colors.grey[600]),
        ),
      ],
    );
  }
}

class CarouselItem {
  final String imageUrl;
  final String title;
  final String description;

  CarouselItem({
    required this.imageUrl,
    required this.title,
    required this.description,
  });
}

// Usage with localized content
class CarouselExample extends StatelessWidget {
  const CarouselExample({super.key});

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

    final items = [
      CarouselItem(
        imageUrl: 'assets/images/feature1.jpg',
        title: l10n.carouselTitle1,
        description: l10n.carouselDescription1,
      ),
      CarouselItem(
        imageUrl: 'assets/images/feature2.jpg',
        title: l10n.carouselTitle2,
        description: l10n.carouselDescription2,
      ),
      CarouselItem(
        imageUrl: 'assets/images/feature3.jpg',
        title: l10n.carouselTitle3,
        description: l10n.carouselDescription3,
      ),
    ];

    return LocalizedImageCarousel(items: items);
  }
}

Tutorial PageView with Steps

Create a step-by-step tutorial:

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

class LocalizedTutorial extends StatefulWidget {
  final VoidCallback onComplete;

  const LocalizedTutorial({
    super.key,
    required this.onComplete,
  });

  @override
  State<LocalizedTutorial> createState() => _LocalizedTutorialState();
}

class _LocalizedTutorialState extends State<LocalizedTutorial> {
  final PageController _pageController = PageController();
  int _currentStep = 0;
  bool _stepCompleted = false;

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

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

    final steps = [
      TutorialStep(
        number: 1,
        title: l10n.tutorialStep1Title,
        instruction: l10n.tutorialStep1Instruction,
        hint: l10n.tutorialStep1Hint,
        icon: Icons.account_circle,
      ),
      TutorialStep(
        number: 2,
        title: l10n.tutorialStep2Title,
        instruction: l10n.tutorialStep2Instruction,
        hint: l10n.tutorialStep2Hint,
        icon: Icons.settings,
      ),
      TutorialStep(
        number: 3,
        title: l10n.tutorialStep3Title,
        instruction: l10n.tutorialStep3Instruction,
        hint: l10n.tutorialStep3Hint,
        icon: Icons.notifications,
      ),
      TutorialStep(
        number: 4,
        title: l10n.tutorialStep4Title,
        instruction: l10n.tutorialStep4Instruction,
        hint: l10n.tutorialStep4Hint,
        icon: Icons.check_circle,
      ),
    ];

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.tutorialTitle),
        actions: [
          TextButton(
            onPressed: widget.onComplete,
            child: Text(
              l10n.skipTutorialButton,
              style: const TextStyle(color: Colors.white),
            ),
          ),
        ],
      ),
      body: Column(
        children: [
          // Progress indicator
          LinearProgressIndicator(
            value: (_currentStep + 1) / steps.length,
            backgroundColor: Colors.grey[200],
          ),
          Padding(
            padding: const EdgeInsets.all(16),
            child: Text(
              l10n.stepProgress(_currentStep + 1, steps.length),
              style: TextStyle(color: Colors.grey[600]),
            ),
          ),
          // Tutorial content
          Expanded(
            child: PageView.builder(
              controller: _pageController,
              physics: const NeverScrollableScrollPhysics(),
              itemCount: steps.length,
              onPageChanged: (index) {
                setState(() {
                  _currentStep = index;
                  _stepCompleted = false;
                });
              },
              itemBuilder: (context, index) {
                final step = steps[index];
                return SingleChildScrollView(
                  padding: const EdgeInsets.all(24),
                  child: Column(
                    children: [
                      // Step icon
                      Container(
                        width: 100,
                        height: 100,
                        decoration: BoxDecoration(
                          color: Theme.of(context).primaryColor.withOpacity(0.1),
                          shape: BoxShape.circle,
                        ),
                        child: Icon(
                          step.icon,
                          size: 50,
                          color: Theme.of(context).primaryColor,
                        ),
                      ),
                      const SizedBox(height: 24),
                      // Step number badge
                      Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 12,
                          vertical: 4,
                        ),
                        decoration: BoxDecoration(
                          color: Theme.of(context).primaryColor,
                          borderRadius: BorderRadius.circular(12),
                        ),
                        child: Text(
                          l10n.stepNumber(step.number),
                          style: const TextStyle(
                            color: Colors.white,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                      const SizedBox(height: 16),
                      // Title
                      Text(
                        step.title,
                        style: Theme.of(context).textTheme.headlineSmall,
                        textAlign: TextAlign.center,
                      ),
                      const SizedBox(height: 16),
                      // Instruction
                      Text(
                        step.instruction,
                        style: Theme.of(context).textTheme.bodyLarge,
                        textAlign: TextAlign.center,
                      ),
                      const SizedBox(height: 24),
                      // Hint card
                      Card(
                        color: Colors.amber[50],
                        child: Padding(
                          padding: const EdgeInsets.all(16),
                          child: Row(
                            children: [
                              Icon(
                                Icons.lightbulb_outline,
                                color: Colors.amber[800],
                              ),
                              const SizedBox(width: 12),
                              Expanded(
                                child: Column(
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: [
                                    Text(
                                      l10n.tipLabel,
                                      style: TextStyle(
                                        fontWeight: FontWeight.bold,
                                        color: Colors.amber[800],
                                      ),
                                    ),
                                    Text(step.hint),
                                  ],
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                      const SizedBox(height: 24),
                      // Complete step checkbox
                      CheckboxListTile(
                        title: Text(l10n.markStepCompleteLabel),
                        value: _stepCompleted,
                        onChanged: (value) {
                          setState(() => _stepCompleted = value ?? false);
                        },
                      ),
                    ],
                  ),
                );
              },
            ),
          ),
          // Navigation
          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: [
                  if (_currentStep > 0)
                    OutlinedButton(
                      onPressed: () {
                        _pageController.previousPage(
                          duration: const Duration(milliseconds: 300),
                          curve: Curves.easeInOut,
                        );
                      },
                      child: Text(l10n.previousStepButton),
                    )
                  else
                    const SizedBox(width: 100),
                  const Spacer(),
                  _currentStep < steps.length - 1
                      ? ElevatedButton(
                          onPressed: _stepCompleted
                              ? () {
                                  _pageController.nextPage(
                                    duration: const Duration(milliseconds: 300),
                                    curve: Curves.easeInOut,
                                  );
                                }
                              : null,
                          child: Text(l10n.nextStepButton),
                        )
                      : ElevatedButton(
                          onPressed: _stepCompleted ? widget.onComplete : null,
                          child: Text(l10n.completeTutorialButton),
                        ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class TutorialStep {
  final int number;
  final String title;
  final String instruction;
  final String hint;
  final IconData icon;

  TutorialStep({
    required this.number,
    required this.title,
    required this.instruction,
    required this.hint,
    required this.icon,
  });
}

RTL Support for PageView

Handle right-to-left swipe direction:

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

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

  @override
  State<RTLPageView> createState() => _RTLPageViewState();
}

class _RTLPageViewState extends State<RTLPageView> {
  late PageController _pageController;
  int _currentPage = 0;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final isRTL = Directionality.of(context) == TextDirection.rtl;
    // For RTL, PageView automatically reverses direction
    // But we need to handle initial page correctly
    _pageController = PageController(initialPage: 0);
  }

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

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

    final pages = [
      PageContent(
        title: l10n.pageTitle1,
        content: l10n.pageContent1,
        color: Colors.blue,
      ),
      PageContent(
        title: l10n.pageTitle2,
        content: l10n.pageContent2,
        color: Colors.green,
      ),
      PageContent(
        title: l10n.pageTitle3,
        content: l10n.pageContent3,
        color: Colors.orange,
      ),
    ];

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.galleryTitle),
      ),
      body: Column(
        children: [
          // Swipe hint
          Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(
                  isRTL ? Icons.arrow_forward : Icons.arrow_back,
                  color: Colors.grey[400],
                  size: 16,
                ),
                const SizedBox(width: 8),
                Text(
                  l10n.swipeToNavigate,
                  style: TextStyle(color: Colors.grey[600]),
                ),
                const SizedBox(width: 8),
                Icon(
                  isRTL ? Icons.arrow_back : Icons.arrow_forward,
                  color: Colors.grey[400],
                  size: 16,
                ),
              ],
            ),
          ),
          // PageView
          Expanded(
            child: PageView.builder(
              controller: _pageController,
              itemCount: pages.length,
              onPageChanged: (index) {
                setState(() => _currentPage = index);
              },
              itemBuilder: (context, index) {
                final page = pages[index];
                return Container(
                  margin: const EdgeInsets.all(16),
                  decoration: BoxDecoration(
                    color: page.color.withOpacity(0.1),
                    borderRadius: BorderRadius.circular(16),
                    border: Border.all(
                      color: page.color.withOpacity(0.3),
                    ),
                  ),
                  child: Padding(
                    padding: const EdgeInsets.all(24),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          page.title,
                          style: Theme.of(context)
                              .textTheme
                              .headlineMedium
                              ?.copyWith(color: page.color),
                          textAlign: TextAlign.center,
                        ),
                        const SizedBox(height: 16),
                        Text(
                          page.content,
                          style: Theme.of(context).textTheme.bodyLarge,
                          textAlign: TextAlign.center,
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),
          ),
          // Navigation with RTL-aware arrows
          Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                IconButton(
                  icon: Icon(isRTL ? Icons.arrow_forward : Icons.arrow_back),
                  onPressed: _currentPage > 0
                      ? () {
                          _pageController.previousPage(
                            duration: const Duration(milliseconds: 300),
                            curve: Curves.easeInOut,
                          );
                        }
                      : null,
                  tooltip: l10n.previousPageTooltip,
                ),
                // Page dots
                Row(
                  children: List.generate(
                    pages.length,
                    (index) => Container(
                      margin: const EdgeInsets.symmetric(horizontal: 4),
                      width: 10,
                      height: 10,
                      decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        color: _currentPage == index
                            ? Theme.of(context).primaryColor
                            : Colors.grey[300],
                      ),
                    ),
                  ),
                ),
                IconButton(
                  icon: Icon(isRTL ? Icons.arrow_back : Icons.arrow_forward),
                  onPressed: _currentPage < pages.length - 1
                      ? () {
                          _pageController.nextPage(
                            duration: const Duration(milliseconds: 300),
                            curve: Curves.easeInOut,
                          );
                        }
                      : null,
                  tooltip: l10n.nextPageTooltip,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class PageContent {
  final String title;
  final String content;
  final Color color;

  PageContent({
    required this.title,
    required this.content,
    required this.color,
  });
}

Accessibility for PageView

Ensure proper screen reader support:

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

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

  @override
  State<AccessiblePageView> createState() => _AccessiblePageViewState();
}

class _AccessiblePageViewState extends State<AccessiblePageView> {
  final PageController _pageController = PageController();
  int _currentPage = 0;
  final int _totalPages = 5;

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

  void _goToPage(int page) {
    if (page >= 0 && page < _totalPages) {
      _pageController.animateToPage(
        page,
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeInOut,
      );
    }
  }

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

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.presentationTitle),
      ),
      body: Column(
        children: [
          // Accessibility controls
          Semantics(
            label: l10n.pageNavigationLabel,
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Semantics(
                    button: true,
                    label: l10n.goToFirstPageLabel,
                    child: IconButton(
                      icon: const Icon(Icons.first_page),
                      onPressed: _currentPage > 0 ? () => _goToPage(0) : null,
                      tooltip: l10n.firstPageTooltip,
                    ),
                  ),
                  Semantics(
                    button: true,
                    label: l10n.previousPageLabel,
                    child: IconButton(
                      icon: const Icon(Icons.chevron_left),
                      onPressed:
                          _currentPage > 0 ? () => _goToPage(_currentPage - 1) : null,
                      tooltip: l10n.previousPageTooltip,
                    ),
                  ),
                  Semantics(
                    liveRegion: true,
                    label: l10n.currentPageAnnouncement(
                      _currentPage + 1,
                      _totalPages,
                    ),
                    child: Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 16,
                        vertical: 8,
                      ),
                      decoration: BoxDecoration(
                        color: Theme.of(context).primaryColor.withOpacity(0.1),
                        borderRadius: BorderRadius.circular(8),
                      ),
                      child: Text(
                        l10n.pageIndicator(_currentPage + 1, _totalPages),
                        style: const TextStyle(fontWeight: FontWeight.bold),
                      ),
                    ),
                  ),
                  Semantics(
                    button: true,
                    label: l10n.nextPageLabel,
                    child: IconButton(
                      icon: const Icon(Icons.chevron_right),
                      onPressed: _currentPage < _totalPages - 1
                          ? () => _goToPage(_currentPage + 1)
                          : null,
                      tooltip: l10n.nextPageTooltip,
                    ),
                  ),
                  Semantics(
                    button: true,
                    label: l10n.goToLastPageLabel,
                    child: IconButton(
                      icon: const Icon(Icons.last_page),
                      onPressed: _currentPage < _totalPages - 1
                          ? () => _goToPage(_totalPages - 1)
                          : null,
                      tooltip: l10n.lastPageTooltip,
                    ),
                  ),
                ],
              ),
            ),
          ),
          // PageView with accessibility
          Expanded(
            child: Semantics(
              label: l10n.slideContentLabel,
              hint: l10n.slideSwipeHint,
              child: PageView.builder(
                controller: _pageController,
                itemCount: _totalPages,
                onPageChanged: (index) {
                  setState(() => _currentPage = index);
                  // Announce page change
                  SemanticsService.announce(
                    l10n.pageChangedAnnouncement(index + 1, _totalPages),
                    TextDirection.ltr,
                  );
                },
                itemBuilder: (context, index) {
                  return Semantics(
                    label: l10n.slideAccessibilityLabel(index + 1),
                    child: Container(
                      margin: const EdgeInsets.all(16),
                      decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(16),
                        boxShadow: [
                          BoxShadow(
                            color: Colors.black.withOpacity(0.1),
                            blurRadius: 10,
                          ),
                        ],
                      ),
                      child: Padding(
                        padding: const EdgeInsets.all(24),
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            Text(
                              l10n.slideTitle(index + 1),
                              style: Theme.of(context).textTheme.headlineMedium,
                            ),
                            const SizedBox(height: 16),
                            Text(
                              l10n.slideDescription(index + 1),
                              style: Theme.of(context).textTheme.bodyLarge,
                              textAlign: TextAlign.center,
                            ),
                          ],
                        ),
                      ),
                    ),
                  );
                },
              ),
            ),
          ),
          // Quick navigation for accessibility
          Padding(
            padding: const EdgeInsets.all(16),
            child: Wrap(
              spacing: 8,
              children: List.generate(
                _totalPages,
                (index) => Semantics(
                  button: true,
                  label: l10n.goToSlideLabel(index + 1),
                  child: ChoiceChip(
                    label: Text('${index + 1}'),
                    selected: _currentPage == index,
                    onSelected: (selected) {
                      if (selected) _goToPage(index);
                    },
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

ARB Translations for PageView

Add these entries to your ARB files:

{
  "onboardingTitle1": "Easy Translations",
  "@onboardingTitle1": {
    "description": "First onboarding page title"
  },
  "onboardingDescription1": "Translate your app into any language with just a few clicks.",
  "onboardingTitle2": "Lightning Fast",
  "onboardingDescription2": "Our AI-powered translations are ready in seconds, not days.",
  "onboardingTitle3": "Secure & Private",
  "onboardingDescription3": "Your content is encrypted and never shared with third parties.",

  "skipButton": "Skip",
  "previousButton": "Previous",
  "nextButton": "Next",
  "getStartedButton": "Get Started",
  "pageIndicator": "{current} of {total}",
  "@pageIndicator": {
    "placeholders": {
      "current": {"type": "int"},
      "total": {"type": "int"}
    }
  },

  "previousImageTooltip": "Previous image",
  "nextImageTooltip": "Next image",
  "imageCounter": "Image {current} of {total}",
  "@imageCounter": {
    "placeholders": {
      "current": {"type": "int"},
      "total": {"type": "int"}
    }
  },
  "carouselTitle1": "Featured Product",
  "carouselDescription1": "Check out our latest collection",
  "carouselTitle2": "New Arrivals",
  "carouselDescription2": "Fresh items just added",
  "carouselTitle3": "Best Sellers",
  "carouselDescription3": "Most popular this week",

  "tutorialTitle": "Getting Started",
  "skipTutorialButton": "Skip Tutorial",
  "stepProgress": "Step {current} of {total}",
  "@stepProgress": {
    "placeholders": {
      "current": {"type": "int"},
      "total": {"type": "int"}
    }
  },
  "stepNumber": "Step {number}",
  "@stepNumber": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "tutorialStep1Title": "Create Your Profile",
  "tutorialStep1Instruction": "Start by setting up your profile with your name and preferences.",
  "tutorialStep1Hint": "You can always update your profile later in Settings.",
  "tutorialStep2Title": "Configure Settings",
  "tutorialStep2Instruction": "Customize the app to match your workflow and preferences.",
  "tutorialStep2Hint": "Enable dark mode for comfortable night-time use.",
  "tutorialStep3Title": "Enable Notifications",
  "tutorialStep3Instruction": "Stay informed with important updates and reminders.",
  "tutorialStep3Hint": "You can customize which notifications you receive.",
  "tutorialStep4Title": "You're All Set!",
  "tutorialStep4Instruction": "You're ready to start using the app. Explore all features!",
  "tutorialStep4Hint": "Check the Help section anytime for more tips.",
  "tipLabel": "Tip",
  "markStepCompleteLabel": "I've completed this step",
  "previousStepButton": "Previous",
  "nextStepButton": "Next Step",
  "completeTutorialButton": "Finish Tutorial",

  "galleryTitle": "Gallery",
  "swipeToNavigate": "Swipe to navigate",
  "pageTitle1": "Welcome",
  "pageContent1": "This is the first page of our gallery.",
  "pageTitle2": "Features",
  "pageContent2": "Discover all the amazing features available.",
  "pageTitle3": "Get Started",
  "pageContent3": "Begin your journey with us today.",
  "previousPageTooltip": "Previous page",
  "nextPageTooltip": "Next page",

  "presentationTitle": "Presentation",
  "pageNavigationLabel": "Page navigation controls",
  "goToFirstPageLabel": "Go to first page",
  "firstPageTooltip": "First page",
  "previousPageLabel": "Go to previous page",
  "nextPageLabel": "Go to next page",
  "goToLastPageLabel": "Go to last page",
  "lastPageTooltip": "Last page",
  "currentPageAnnouncement": "Currently on page {current} of {total}",
  "@currentPageAnnouncement": {
    "placeholders": {
      "current": {"type": "int"},
      "total": {"type": "int"}
    }
  },
  "slideContentLabel": "Presentation slides",
  "slideSwipeHint": "Swipe left or right to navigate between slides",
  "pageChangedAnnouncement": "Now showing page {current} of {total}",
  "@pageChangedAnnouncement": {
    "placeholders": {
      "current": {"type": "int"},
      "total": {"type": "int"}
    }
  },
  "slideAccessibilityLabel": "Slide {number}",
  "@slideAccessibilityLabel": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "slideTitle": "Slide {number}",
  "@slideTitle": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "slideDescription": "Content for slide number {number}",
  "@slideDescription": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "goToSlideLabel": "Go to slide {number}",
  "@goToSlideLabel": {
    "placeholders": {
      "number": {"type": "int"}
    }
  }
}

Testing PageView Localization

Write tests for your localized PageView:

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

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

      expect(find.text('Easy Translations'), findsOneWidget);
      expect(find.text('Skip'), findsOneWidget);
      expect(find.text('Next'), findsOneWidget);
    });

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

      expect(find.text('Traducciones Fáciles'), findsOneWidget);
      expect(find.text('Omitir'), findsOneWidget);
      expect(find.text('Siguiente'), findsOneWidget);
    });

    testWidgets('navigates to next page and updates indicator', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          locale: const Locale('en'),
          home: LocalizedOnboarding(onComplete: () {}),
        ),
      );

      // Initial page indicator
      expect(find.text('1 of 3'), findsOneWidget);

      // Tap next button
      await tester.tap(find.text('Next'));
      await tester.pumpAndSettle();

      // Updated page indicator
      expect(find.text('2 of 3'), findsOneWidget);
      expect(find.text('Lightning Fast'), findsOneWidget);
    });

    testWidgets('shows Get Started on last page', (tester) async {
      await tester.pumpWidget(
        MaterialApp(
          localizationsDelegates: AppLocalizations.localizationsDelegates,
          supportedLocales: AppLocalizations.supportedLocales,
          locale: const Locale('en'),
          home: LocalizedOnboarding(onComplete: () {}),
        ),
      );

      // Navigate to last page
      await tester.tap(find.text('Next'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('Next'));
      await tester.pumpAndSettle();

      // Last page shows Get Started button
      expect(find.text('Get Started'), findsOneWidget);
      expect(find.text('Secure & Private'), 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: RTLPageView(),
          ),
        ),
      );

      // Verify RTL content is displayed
      expect(find.text('المعرض'), findsOneWidget);
    });
  });
}

Summary

Localizing PageView in Flutter requires:

  1. Onboarding content with localized titles and descriptions
  2. Page indicators showing current position
  3. Navigation buttons in the user's language
  4. Carousel captions for image descriptions
  5. Tutorial steps with localized instructions
  6. RTL support with proper swipe direction hints
  7. Accessibility announcements for page changes
  8. Comprehensive testing across different locales

PageView is essential for onboarding and content presentation, and proper localization ensures your app makes a great first impression for users in any language.