Guía de implementación de internacionalización (i18n) para la app móvil de e-commerce agrícola
flutter pub add flutter_localizations --sdk=flutter & flutter pub add intl:any
generate: true en la sección flutter: de pubspec.yaml
l10n.yaml en raíz del proyectoarb-dir, template-arb-file y output-localization-file
MaterialApp con delegates y supportedLocalesAppLocalizations.delegate + los tres GlobalXxx delegates en main.dart
app_es.arb, app_en.arb, app_ca.arbname: cultivashop description: App móvil de e-commerce agrícola local version: 1.0.0+1 environment: sdk: ^3.5.0 dependencies: flutter: sdk: flutter flutter_localizations: # paquete oficial del SDK sdk: flutter intl: any # 'any' delega la versión al SDK flutter: generate: true # activa generación automática de AppLocalizations uses-material-design: true
arb-dir: lib/l10n # directorio de los archivos .arb template-arb-file: app_es.arb # ES como idioma base (template) output-localization-file: app_localizations.dart synthetic-package: true # genera en flutter_gen (recomendado) use-escaping: true # activa escape con '' en mensajes ICU nullable-getter: false # AppLocalizations.of(context) no nullable
appTitlewelcomeUseruserNamecartItemCountuserGreetingerrorNetworkerrorOutOfStockerrorSessionExpired{ "@@locale": "es", "appTitle": "CultivaShop", "@appTitle": { "description": "Nombre de la aplicación" }, "welcomeUser": "¡Bienvenido/a, {userName}!", "@welcomeUser": { "description": "Saludo en la pantalla de inicio", "placeholders": { "userName": { "type": "String", "example": "María" } } }, "cartItemCount": "{count, plural, =0{Sin artículos} =1{1 artículo} other{{count} artículos}}", "@cartItemCount": { "description": "Cantidad de artículos en el carrito", "placeholders": { "count": { "type": "num", "format": "compact" } } }, "userGreeting": "{gender, select, male{Bienvenido} female{Bienvenida} other{Bienvenide}}", "@userGreeting": { "description": "Saludo formal según género", "placeholders": { "gender": { "type": "String" } } }, "errorNetwork": "Sin conexión. Comprueba tu red e inténtalo de nuevo.", "@errorNetwork": { "description": "Error de conectividad" }, "errorOutOfStock": "Este producto está agotado. Te avisaremos cuando vuelva.", "@errorOutOfStock": { "description": "Stock agotado" }, "errorSessionExpired": "Tu sesión ha expirado. Por favor, inicia sesión de nuevo.", "@errorSessionExpired": { "description": "Sesión expirada" } }
{ "@@locale": "en", "appTitle": "CultivaShop", "welcomeUser": "Welcome, {userName}!", "cartItemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}", "userGreeting": "{gender, select, male{Welcome, sir} female{Welcome, ma'am} other{Welcome}}", "errorNetwork": "No connection. Please check your network and try again.", "errorOutOfStock": "This product is out of stock. We'll notify you when it's back.", "errorSessionExpired": "Your session has expired. Please log in again." }
{ "@@locale": "ca", "appTitle": "CultivaShop", "welcomeUser": "Benvingut/da, {userName}!", "cartItemCount": "{count, plural, =0{Sense articles} =1{1 article} other{{count} articles}}", "userGreeting": "{gender, select, male{Benvingut} female{Benvinguda} other{Benvingut/da}}", "errorNetwork": "Sense connexió. Comprova la teva xarxa i torna-ho a intentar.", "errorOutOfStock": "Aquest producte està esgotat. T'avisarem quan torni a estar disponible.", "errorSessionExpired": "La teva sessió ha caducat. Si us plau, torna a iniciar sessió." }
import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; void main() => runApp(CultivaShopApp()); class CultivaShopApp extends StatelessWidget { const CultivaShopApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( // ── Localización ───────────────────────────────────────── localizationsDelegates: const [ AppLocalizations.delegate, // generado por flutter gen-l10n GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [ Locale('es'), // Español — idioma base Locale('en'), // English Locale('ca'), // Català ], // ───────────────────────────────────────────────────────── theme: ThemeData( colorSchemeSeed: const Color(0xFF2D6A4F), // verde CultivaShop useMaterial3: true, ), home: const HomeScreen(), ); } }
import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); return Scaffold( appBar: AppBar( title: Text(l10n.appTitle), // estático ), body: Column( children: [ // placeholder — nombre de usuario Text(l10n.welcomeUser('María')), // plural — carrito Text(l10n.cartItemCount(cartCount)), // select — género Text(l10n.userGreeting(user.gender)), // estáticos — errores (condicional) if (hasNetworkError) Text(l10n.errorNetwork), ], ), ); } }
name: CultivaShop CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: flutter-version: '3.x' - name: Instalar deps + generar l10n run: flutter pub get # dispara generación de AppLocalizations - name: Análisis estático run: flutter analyze - name: Tests run: flutter test
nullable-getter: false en l10n.yaml, AppLocalizations.of(context) devuelve directamente AppLocalizations sin ! ni null-checks. Requiere Flutter 3.18+.
GlobalCupertinoLocalizations.delegate y que el paquete intl_utils reconoce 'ca'. Si ves warning en build, añade el package intl: any y corre flutter pub get de nuevo.