Localization (i18n / l10n)

Flow Mobile supports three languages out of the box: English (base), Italian, and Spanish. All user-facing strings are managed through Flutter’s ARB-based localization system.

Architecture

ComponentPath
ARB files (source of truth)lib/l10n/app_en.arb, app_it.arb, app_es.arb
Generated Dart fileslib/l10n/app_localizations*.dart
Configl10n.yaml (synthetic-package: false)
Import pathpackage:flow_app/l10n/app_localizations.dart

The synthetic-package: false setting in l10n.yaml means generated files live inside lib/l10n/ rather than in a synthetic package. This is why the import is package:flow_app/l10n/app_localizations.dart and not flutter_gen/gen_l10n/.

Usage Pattern

import 'package:flow_app/l10n/app_localizations.dart';
 
// In a build method:
final l10n = AppLocalizations.of(context)!;
Text(l10n.someKey);
 
// With parameters:
Text(l10n.photoUploadError(e.toString()));
Text(l10n.participantsCount(count.toString()));

Adding New Strings

  1. Add the key + value to app_en.arb (English is the template).
  2. Add the same key to app_it.arb and app_es.arb with translations.
  3. For parameterized strings, add an @key metadata block:
"photoUploadError": "Error uploading photo: {error}",
"@photoUploadError": {
  "placeholders": {
    "error": { "type": "String" }
  }
}
  1. Regenerate:
flutter gen-l10n
# or simply:
flutter pub get
  1. Use the new key in Dart code via AppLocalizations.of(context)!.photoUploadError(e.toString()).

Key Conventions

PatternExampleWhen
screenActioneditProfile, saveChangesButtons, titles
sectionNamesectionPersonalInfo, sectionVibeCheckSection headers
wizardStepTitlewizardWelcomeTitle, wizardNameSubtitleRegistration wizard
errorDescriptionphotoUploadError, cannotLoadMessagesError messages
labelNameemailLabel, passwordLabel, bioLabelForm field labels

const Propagation

When replacing a const Text('literal') with Text(l10n.key), the const must be removed from Text (since l10n.key is not a compile-time constant). However, const should be kept on:

  • TextStyle instances (they remain constant)
  • Ancestor widgets that don’t depend on the runtime value (e.g., Icon, SizedBox)

If a parent widget like Padding or SliverToBoxAdapter was const and now contains a non-const child, remove const from the parent too.

Locale-Aware Dates

Instead of hardcoded Italian month/day arrays, use the intl package:

import 'package:intl/intl.dart';
 
final locale = Localizations.localeOf(context).toString();
final monthName = DateFormat('MMMM', locale).format(DateTime(2024, month));

This is used in FlowDateWheelPicker and the feed screen’s showDatePicker.

Strings Intentionally NOT Localized

  • Vibe filter labels ("Tutto", "Consigliati", etc.) in the feed screen serve as both display text and filter keys matched against database tags. Localizing them would break the filtering logic.
  • searchFieldLabel in SearchDelegate subclasses has no BuildContext (it’s a getter), so it can’t use AppLocalizations.
  • Brand names (Instagram, TikTok) are kept as-is.

Quality Sweep (2026-04-08)

A cleanup pass on the existing ARB files removed 22 duplicate keys per locale (66 total lines), corrected the age-gate string from 16 to 18 in wizardBirthdaySubtitle, localized two previously hardcoded surfaces (welcome_screen carousel + headline, feed_interruption_widgets invite card and “vibe of the day” label), and stripped decorative emoji from 15 keys (imIn, youreInSquad, findACrewButton, chatEmptyTitle, sectionRecommendedForYou, profileUpdateSuccess, sectionVibeCheck, bugReportSent, joinSquad, crewsNearYou, noConnection, serverError, sessionExpired, checkInputAndRetry, locationNeeded).

The duplicate-key bug class is worth flagging for the future: Flutter’s ARB parser silently keeps the last occurrence, so a drifted duplicate value will be the one that ships at runtime — a category of latent bug that won’t surface until an unlucky merge introduces a divergence. Use:

python -c "import re; from collections import Counter; \
  print(Counter(re.findall(r'^\s*\"([a-zA-Z]\w*)\"\s*:', open('lib/l10n/app_en.arb').read(), re.M)).most_common(10))"

…to spot duplicates before they bite.

Migration Summary (April 2025)

The l10n migration covered ~540+ keys across 20+ screens:

Screens fully localized:

  • Auth: registration_wizard_screen, forgot_password_screen, welcome_screen
  • Feed: feed_screen
  • Events: event_discovery_screen, event_details_screen, events_screen
  • Messaging: chat_screen, chat_details_screen, chats_screen, message_requests_screen, new_chat_screen
  • Profile: profile_screen, edit_profile_screen, profile_setup_screen
  • Settings: settings_screen, privacy_settings_screen
  • Social: friends_screen
  • Search: search_screen
  • Notifications: notifications_screen

Shared widgets localized:

  • error_reporter_overlay, empty_state_widget, error_widget, loading_widgets, flow_date_wheel_picker, minimal_event_card, product_checkout_sheet, squad_bento_section, squad_detail_sheet