← Back to Blog

Flutter FractionalTranslation Localization: Proportional Movement for Multilingual Apps

flutterfractionaltranslationpositioningbadgeslocalizationrtl

Flutter FractionalTranslation Localization: Proportional Movement for Multilingual Apps

FractionalTranslation moves a widget by a fraction of its own size, making it perfect for creating directional animations and position adjustments that adapt to different layouts. In multilingual apps, FractionalTranslation enables RTL-aware sliding effects, notification badges, and dynamic positioning that responds correctly to text direction. This guide covers comprehensive strategies for using FractionalTranslation in Flutter localization.

Understanding FractionalTranslation in Localization

FractionalTranslation widgets benefit localization for:

  • RTL-aware slides: Moving content in the correct direction for the locale
  • Notification badges: Positioning badges that adapt to text direction
  • Tooltip positioning: Placing tooltips relative to elements in RTL/LTR contexts
  • Slide animations: Creating directional transitions for multilingual content
  • Overlapping elements: Positioning overlays relative to their own size
  • Responsive offsets: Size-independent positioning for varying text lengths

Basic FractionalTranslation with Localized Content

Start with a simple directional translation:

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

class LocalizedFractionalTranslationDemo extends StatelessWidget {
  const LocalizedFractionalTranslationDemo({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.fractionalTranslationTitle)),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.overlapDemoLabel,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 16),

            // Overlapping cards with directional offset
            SizedBox(
              height: 180,
              child: Stack(
                children: [
                  // Back card
                  Positioned.fill(
                    child: Card(
                      color: Theme.of(context).colorScheme.primaryContainer,
                      child: Padding(
                        padding: const EdgeInsets.all(16),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              l10n.backCardTitle,
                              style: Theme.of(context).textTheme.titleLarge,
                            ),
                            const SizedBox(height: 8),
                            Text(l10n.backCardDescription),
                          ],
                        ),
                      ),
                    ),
                  ),

                  // Front card with fractional offset
                  Positioned.fill(
                    child: FractionalTranslation(
                      // Move 10% of width in reading direction, 20% down
                      translation: Offset(
                        isRtl ? -0.1 : 0.1,
                        0.2,
                      ),
                      child: Card(
                        elevation: 8,
                        color: Theme.of(context).colorScheme.surface,
                        child: Padding(
                          padding: const EdgeInsets.all(16),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                l10n.frontCardTitle,
                                style: Theme.of(context).textTheme.titleLarge,
                              ),
                              const SizedBox(height: 8),
                              Text(l10n.frontCardDescription),
                            ],
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

ARB File Structure for FractionalTranslation

{
  "fractionalTranslationTitle": "Fractional Translation Demo",
  "@fractionalTranslationTitle": {
    "description": "Title for fractional translation demo page"
  },
  "overlapDemoLabel": "Overlapping Cards",
  "backCardTitle": "Background Card",
  "backCardDescription": "This card is positioned at the base layer.",
  "frontCardTitle": "Foreground Card",
  "frontCardDescription": "This card is offset using FractionalTranslation."
}

RTL-Aware Badge Positioning

Position badges that adapt to text direction:

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

class LocalizedBadgePositioning extends StatelessWidget {
  const LocalizedBadgePositioning({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.badgePositioningTitle)),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.menuItemsLabel,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 16),

            // Menu items with badges
            _buildMenuItem(
              context,
              l10n,
              isRtl,
              icon: Icons.inbox,
              title: l10n.menuInbox,
              badgeCount: 5,
              badgeColor: Colors.red,
            ),
            const SizedBox(height: 12),
            _buildMenuItem(
              context,
              l10n,
              isRtl,
              icon: Icons.chat,
              title: l10n.menuMessages,
              badgeCount: 12,
              badgeColor: Colors.blue,
            ),
            const SizedBox(height: 12),
            _buildMenuItem(
              context,
              l10n,
              isRtl,
              icon: Icons.notifications,
              title: l10n.menuNotifications,
              badgeCount: 99,
              badgeColor: Colors.orange,
            ),
            const SizedBox(height: 12),
            _buildMenuItem(
              context,
              l10n,
              isRtl,
              icon: Icons.shopping_cart,
              title: l10n.menuCart,
              badgeCount: 3,
              badgeColor: Colors.green,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildMenuItem(
    BuildContext context,
    AppLocalizations l10n,
    bool isRtl, {
    required IconData icon,
    required String title,
    required int badgeCount,
    required Color badgeColor,
  }) {
    return Card(
      child: ListTile(
        leading: Stack(
          clipBehavior: Clip.none,
          children: [
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: badgeColor.withOpacity(0.1),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Icon(icon, color: badgeColor),
            ),

            // Badge positioned using FractionalTranslation
            if (badgeCount > 0)
              Positioned(
                top: 0,
                right: isRtl ? null : 0,
                left: isRtl ? 0 : null,
                child: FractionalTranslation(
                  // Move badge by 50% of its size outward
                  translation: Offset(
                    isRtl ? -0.5 : 0.5,
                    -0.5,
                  ),
                  child: Container(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 6,
                      vertical: 2,
                    ),
                    decoration: BoxDecoration(
                      color: badgeColor,
                      borderRadius: BorderRadius.circular(10),
                    ),
                    constraints: const BoxConstraints(minWidth: 20),
                    child: Text(
                      badgeCount > 99 ? '99+' : badgeCount.toString(),
                      style: const TextStyle(
                        color: Colors.white,
                        fontSize: 12,
                        fontWeight: FontWeight.bold,
                      ),
                      textAlign: TextAlign.center,
                    ),
                  ),
                ),
              ),
          ],
        ),
        title: Text(title),
        trailing: Icon(
          isRtl ? Icons.chevron_left : Icons.chevron_right,
          color: Theme.of(context).colorScheme.onSurfaceVariant,
        ),
      ),
    );
  }
}

Animated Slide Transitions

Create directional slide animations:

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

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

  @override
  State<LocalizedSlideTransitions> createState() =>
      _LocalizedSlideTransitionsState();
}

class _LocalizedSlideTransitionsState extends State<LocalizedSlideTransitions>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<Offset> _slideAnimation;
  int _currentIndex = 0;

  final List<Map<String, dynamic>> _slides = [];

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _initializeSlides();
    _updateAnimation();
  }

  void _initializeSlides() {
    final l10n = AppLocalizations.of(context)!;
    _slides.clear();
    _slides.addAll([
      {
        'title': l10n.slideWelcomeTitle,
        'description': l10n.slideWelcomeDescription,
        'icon': Icons.waving_hand,
        'color': Colors.blue,
      },
      {
        'title': l10n.slideFeaturesTitle,
        'description': l10n.slideFeaturesDescription,
        'icon': Icons.star,
        'color': Colors.amber,
      },
      {
        'title': l10n.slideStartTitle,
        'description': l10n.slideStartDescription,
        'icon': Icons.rocket_launch,
        'color': Colors.green,
      },
    ]);
  }

  void _updateAnimation() {
    final isRtl = Directionality.of(context) == TextDirection.rtl;
    // Slide direction based on text direction
    _slideAnimation = Tween<Offset>(
      begin: Offset(isRtl ? -1.0 : 1.0, 0),
      end: Offset.zero,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeOutCubic,
    ));
  }

  void _nextSlide() {
    if (_currentIndex < _slides.length - 1) {
      setState(() {
        _currentIndex++;
        _controller.reset();
        _controller.forward();
      });
    }
  }

  void _previousSlide() {
    if (_currentIndex > 0) {
      setState(() {
        _currentIndex--;
        _controller.reset();
        _controller.forward();
      });
    }
  }

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

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

    if (_slides.isEmpty) return const SizedBox();

    final slide = _slides[_currentIndex];

    return Scaffold(
      appBar: AppBar(title: Text(l10n.onboardingTitle)),
      body: Column(
        children: [
          Expanded(
            child: AnimatedBuilder(
              animation: _slideAnimation,
              builder: (context, child) {
                return FractionalTranslation(
                  translation: _slideAnimation.value,
                  child: child,
                );
              },
              child: Padding(
                padding: const EdgeInsets.all(32),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Container(
                      padding: const EdgeInsets.all(32),
                      decoration: BoxDecoration(
                        color: (slide['color'] as Color).withOpacity(0.1),
                        shape: BoxShape.circle,
                      ),
                      child: Icon(
                        slide['icon'] as IconData,
                        size: 80,
                        color: slide['color'] as Color,
                      ),
                    ),
                    const SizedBox(height: 32),
                    Text(
                      slide['title'] as String,
                      style: Theme.of(context).textTheme.headlineMedium,
                      textAlign: TextAlign.center,
                    ),
                    const SizedBox(height: 16),
                    Text(
                      slide['description'] as String,
                      style: Theme.of(context).textTheme.bodyLarge?.copyWith(
                        color: Theme.of(context).colorScheme.onSurfaceVariant,
                      ),
                      textAlign: TextAlign.center,
                    ),
                  ],
                ),
              ),
            ),
          ),

          // Indicators
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: List.generate(_slides.length, (index) {
              final isActive = index == _currentIndex;
              return AnimatedContainer(
                duration: const Duration(milliseconds: 200),
                margin: const EdgeInsets.symmetric(horizontal: 4),
                width: isActive ? 24 : 8,
                height: 8,
                decoration: BoxDecoration(
                  color: isActive
                      ? Theme.of(context).colorScheme.primary
                      : Theme.of(context).colorScheme.outlineVariant,
                  borderRadius: BorderRadius.circular(4),
                ),
              );
            }),
          ),
          const SizedBox(height: 24),

          // Navigation buttons
          Padding(
            padding: const EdgeInsets.all(24),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                TextButton(
                  onPressed: _currentIndex > 0 ? _previousSlide : null,
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Icon(isRtl ? Icons.chevron_right : Icons.chevron_left),
                      Text(l10n.previousButton),
                    ],
                  ),
                ),
                if (_currentIndex < _slides.length - 1)
                  ElevatedButton(
                    onPressed: _nextSlide,
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Text(l10n.nextButton),
                        Icon(isRtl ? Icons.chevron_left : Icons.chevron_right),
                      ],
                    ),
                  )
                else
                  ElevatedButton(
                    onPressed: () {},
                    child: Text(l10n.getStartedButton),
                  ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

Tooltip Positioning with FractionalTranslation

Position tooltips relative to their anchor:

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

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

  @override
  State<LocalizedTooltipPositioning> createState() =>
      _LocalizedTooltipPositioningState();
}

class _LocalizedTooltipPositioningState
    extends State<LocalizedTooltipPositioning> {
  String? _activeTooltip;

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

    return Scaffold(
      appBar: AppBar(title: Text(l10n.tooltipDemoTitle)),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.tooltipInstructions,
              style: Theme.of(context).textTheme.bodyLarge,
            ),
            const SizedBox(height: 32),

            // Form with tooltip hints
            _buildFieldWithTooltip(
              context,
              l10n,
              isRtl,
              fieldId: 'email',
              label: l10n.emailFieldLabel,
              hint: l10n.emailFieldHint,
              tooltipMessage: l10n.emailTooltip,
              icon: Icons.email,
            ),
            const SizedBox(height: 20),
            _buildFieldWithTooltip(
              context,
              l10n,
              isRtl,
              fieldId: 'password',
              label: l10n.passwordFieldLabel,
              hint: l10n.passwordFieldHint,
              tooltipMessage: l10n.passwordTooltip,
              icon: Icons.lock,
            ),
            const SizedBox(height: 20),
            _buildFieldWithTooltip(
              context,
              l10n,
              isRtl,
              fieldId: 'phone',
              label: l10n.phoneFieldLabel,
              hint: l10n.phoneFieldHint,
              tooltipMessage: l10n.phoneTooltip,
              icon: Icons.phone,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildFieldWithTooltip(
    BuildContext context,
    AppLocalizations l10n,
    bool isRtl, {
    required String fieldId,
    required String label,
    required String hint,
    required String tooltipMessage,
    required IconData icon,
  }) {
    final isActive = _activeTooltip == fieldId;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Expanded(
              child: Text(
                label,
                style: Theme.of(context).textTheme.titleSmall,
              ),
            ),
            // Help icon with tooltip
            Stack(
              clipBehavior: Clip.none,
              children: [
                GestureDetector(
                  onTap: () {
                    setState(() {
                      _activeTooltip = isActive ? null : fieldId;
                    });
                  },
                  child: Container(
                    padding: const EdgeInsets.all(4),
                    decoration: BoxDecoration(
                      color: isActive
                          ? Theme.of(context).colorScheme.primaryContainer
                          : Colors.transparent,
                      shape: BoxShape.circle,
                    ),
                    child: Icon(
                      Icons.help_outline,
                      size: 20,
                      color: isActive
                          ? Theme.of(context).colorScheme.primary
                          : Theme.of(context).colorScheme.onSurfaceVariant,
                    ),
                  ),
                ),

                // Tooltip positioned with FractionalTranslation
                if (isActive)
                  Positioned(
                    top: 0,
                    right: isRtl ? null : 0,
                    left: isRtl ? 0 : null,
                    child: FractionalTranslation(
                      // Position tooltip to the side and below
                      translation: Offset(
                        isRtl ? 0.5 : -0.5,
                        1.2,
                      ),
                      child: Container(
                        width: 200,
                        padding: const EdgeInsets.all(12),
                        decoration: BoxDecoration(
                          color: Theme.of(context).colorScheme.inverseSurface,
                          borderRadius: BorderRadius.circular(8),
                          boxShadow: [
                            BoxShadow(
                              color: Colors.black26,
                              blurRadius: 8,
                              offset: const Offset(0, 4),
                            ),
                          ],
                        ),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Row(
                              children: [
                                Icon(
                                  Icons.info,
                                  size: 16,
                                  color: Theme.of(context)
                                      .colorScheme
                                      .onInverseSurface,
                                ),
                                const SizedBox(width: 8),
                                Expanded(
                                  child: Text(
                                    l10n.tooltipHelpLabel,
                                    style: TextStyle(
                                      color: Theme.of(context)
                                          .colorScheme
                                          .onInverseSurface,
                                      fontWeight: FontWeight.bold,
                                      fontSize: 12,
                                    ),
                                  ),
                                ),
                                GestureDetector(
                                  onTap: () {
                                    setState(() => _activeTooltip = null);
                                  },
                                  child: Icon(
                                    Icons.close,
                                    size: 16,
                                    color: Theme.of(context)
                                        .colorScheme
                                        .onInverseSurface,
                                  ),
                                ),
                              ],
                            ),
                            const SizedBox(height: 8),
                            Text(
                              tooltipMessage,
                              style: TextStyle(
                                color: Theme.of(context)
                                    .colorScheme
                                    .onInverseSurface,
                                fontSize: 13,
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                  ),
              ],
            ),
          ],
        ),
        const SizedBox(height: 8),
        TextField(
          decoration: InputDecoration(
            hintText: hint,
            prefixIcon: Icon(icon),
            border: const OutlineInputBorder(),
          ),
        ),
      ],
    );
  }
}

Stacked Profile Cards

Create overlapping profile cards:

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

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

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

    final profiles = [
      {
        'name': l10n.profile1Name,
        'role': l10n.profile1Role,
        'color': Colors.blue,
        'offset': 0.0,
      },
      {
        'name': l10n.profile2Name,
        'role': l10n.profile2Role,
        'color': Colors.green,
        'offset': 0.15,
      },
      {
        'name': l10n.profile3Name,
        'role': l10n.profile3Role,
        'color': Colors.orange,
        'offset': 0.30,
      },
      {
        'name': l10n.profile4Name,
        'role': l10n.profile4Role,
        'color': Colors.purple,
        'offset': 0.45,
      },
    ];

    return Scaffold(
      appBar: AppBar(title: Text(l10n.teamMembersTitle)),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.teamSectionTitle,
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            const SizedBox(height: 8),
            Text(
              l10n.teamSectionDescription,
              style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                color: Theme.of(context).colorScheme.onSurfaceVariant,
              ),
            ),
            const SizedBox(height: 32),

            // Stacked profile avatars
            SizedBox(
              height: 80,
              child: Stack(
                children: profiles.asMap().entries.map((entry) {
                  final index = entry.key;
                  final profile = entry.value;
                  final horizontalOffset = profile['offset'] as double;

                  return Positioned(
                    left: isRtl ? null : 0,
                    right: isRtl ? 0 : null,
                    child: FractionalTranslation(
                      translation: Offset(
                        isRtl ? -horizontalOffset * 5 : horizontalOffset * 5,
                        0,
                      ),
                      child: _buildProfileAvatar(
                        context,
                        profile['name'] as String,
                        profile['role'] as String,
                        profile['color'] as Color,
                        index,
                      ),
                    ),
                  );
                }).toList(),
              ),
            ),
            const SizedBox(height: 24),

            // Additional team info
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Row(
                  children: [
                    Icon(
                      Icons.group,
                      color: Theme.of(context).colorScheme.primary,
                    ),
                    const SizedBox(width: 12),
                    Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            l10n.totalTeamMembers(profiles.length),
                            style: Theme.of(context).textTheme.titleMedium,
                          ),
                          Text(
                            l10n.viewAllMembers,
                            style: TextStyle(
                              color: Theme.of(context).colorScheme.primary,
                            ),
                          ),
                        ],
                      ),
                    ),
                    Icon(
                      isRtl ? Icons.chevron_left : Icons.chevron_right,
                      color: Theme.of(context).colorScheme.onSurfaceVariant,
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildProfileAvatar(
    BuildContext context,
    String name,
    String role,
    Color color,
    int index,
  ) {
    return Tooltip(
      message: '$name - $role',
      child: Container(
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          border: Border.all(
            color: Theme.of(context).colorScheme.surface,
            width: 3,
          ),
          boxShadow: [
            BoxShadow(
              color: Colors.black26,
              blurRadius: 4,
              offset: const Offset(0, 2),
            ),
          ],
        ),
        child: CircleAvatar(
          radius: 35,
          backgroundColor: color,
          child: Text(
            name.split(' ').map((n) => n[0]).take(2).join(),
            style: const TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
              fontSize: 18,
            ),
          ),
        ),
      ),
    );
  }
}

Complete ARB File for FractionalTranslation

{
  "@@locale": "en",

  "fractionalTranslationTitle": "Fractional Translation Demo",
  "overlapDemoLabel": "Overlapping Cards",
  "backCardTitle": "Background Card",
  "backCardDescription": "This card is positioned at the base layer.",
  "frontCardTitle": "Foreground Card",
  "frontCardDescription": "This card is offset using FractionalTranslation.",

  "badgePositioningTitle": "Badge Positioning",
  "menuItemsLabel": "Menu Items",
  "menuInbox": "Inbox",
  "menuMessages": "Messages",
  "menuNotifications": "Notifications",
  "menuCart": "Shopping Cart",

  "onboardingTitle": "Onboarding",
  "slideWelcomeTitle": "Welcome",
  "slideWelcomeDescription": "Discover all the amazing features waiting for you.",
  "slideFeaturesTitle": "Powerful Features",
  "slideFeaturesDescription": "Everything you need to boost your productivity.",
  "slideStartTitle": "Get Started",
  "slideStartDescription": "Join thousands of users and start your journey today.",
  "previousButton": "Previous",
  "nextButton": "Next",
  "getStartedButton": "Get Started",

  "tooltipDemoTitle": "Tooltip Demo",
  "tooltipInstructions": "Tap the help icons to see tooltips",
  "tooltipHelpLabel": "Help",
  "emailFieldLabel": "Email Address",
  "emailFieldHint": "Enter your email",
  "emailTooltip": "We'll use this email for account recovery and important notifications.",
  "passwordFieldLabel": "Password",
  "passwordFieldHint": "Enter your password",
  "passwordTooltip": "Password must be at least 8 characters with uppercase, lowercase, and numbers.",
  "phoneFieldLabel": "Phone Number",
  "phoneFieldHint": "Enter your phone",
  "phoneTooltip": "Optional. Used for two-factor authentication and account security.",

  "teamMembersTitle": "Team Members",
  "teamSectionTitle": "Meet Our Team",
  "teamSectionDescription": "The amazing people behind our success",
  "profile1Name": "Sarah Johnson",
  "profile1Role": "CEO",
  "profile2Name": "Michael Chen",
  "profile2Role": "CTO",
  "profile3Name": "Emily Davis",
  "profile3Role": "Designer",
  "profile4Name": "Ahmed Hassan",
  "profile4Role": "Developer",
  "totalTeamMembers": "{count} team members",
  "@totalTeamMembers": {
    "placeholders": {"count": {"type": "int"}}
  },
  "viewAllMembers": "View all members"
}

Best Practices Summary

  1. Adjust translation for RTL: Invert horizontal offset values for RTL languages
  2. Use fractions for responsive positioning: Size-independent offsets work across layouts
  3. Combine with Stack: FractionalTranslation works well inside Stack widgets
  4. Animate translations: Use AnimatedBuilder for smooth directional animations
  5. Consider text length: Test with different language text lengths
  6. Badge positioning: Position badges at corners that respect text direction
  7. Tooltip placement: Place tooltips in the reading direction
  8. Performance: FractionalTranslation is a simple transform, very efficient
  9. Clip behavior: Set clipBehavior on parent Stack to control overflow
  10. Test across locales: Verify overlapping elements look correct in all languages

Conclusion

FractionalTranslation provides a powerful way to position widgets relative to their own size, making it ideal for creating directional layouts that adapt to different locales. By properly adjusting translation offsets for RTL languages, you ensure that sliding animations, badge positions, and overlapping elements feel natural to users regardless of their text direction.

The key to successful localization with FractionalTranslation is thinking in terms of "reading direction" rather than absolute left/right positions. This approach creates a consistent experience across all supported locales.