Traducir tu app Flutter es esencial para llegar a una audiencia global. Flutter proporciona excelente soporte integrado para la localización a través del paquete intl y archivos ARB (Application Resource Bundle). Esta guía te guiará a través del proceso de traducir una app Flutter usando Dart.
Entendiendo la Localización en Flutter
Flutter utiliza archivos ARB (Application Resource Bundle) para almacenar traducciones. Estos archivos similares a JSON contienen pares clave-valor para cada idioma. El paquete intl de Flutter maneja la localización automáticamente basándose en la configuración de idioma del dispositivo del usuario.
Paso 1: Añadir Dependencias
Primero, añade los paquetes necesarios a tu pubspec.yaml:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.19.0
flutter:
generate: true
Ejecuta flutter pub get para instalar los paquetes.
Paso 2: Configurar l10n.yaml
Crea un archivo l10n.yaml en la raíz de tu proyecto para configurar la localización:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
Paso 3: Crear Archivos ARB
Crea un directorio lib/l10n y añade archivos ARB para cada idioma:
lib/l10n/app_en.arb (Inglés - por defecto):
{
"@@locale": "en",
"appName": "My App",
"@appName": {
"description": "The application name"
},
"welcomeMessage": "Welcome to our app!",
"@welcomeMessage": {
"description": "Welcome message displayed on home screen"
},
"buttonSubmit": "Submit",
"@buttonSubmit": {
"description": "Submit button label"
},
"errorNetwork": "Network error. Please try again.",
"@errorNetwork": {
"description": "Network error message"
},
"itemsCount": "{count, plural, =0{No items} one{1 item} other{{count} items}}",
"@itemsCount": {
"description": "Number of items",
"placeholders": {
"count": {
"type": "int"
}
}
},
"userGreeting": "Hello, {name}! You have {messageCount, plural, =0{no messages} one{1 message} other{{messageCount} messages}}.",
"@userGreeting": {
"description": "Greeting with user name and message count",
"placeholders": {
"name": {
"type": "String"
},
"messageCount": {
"type": "int"
}
}
}
}
lib/l10n/app_es.arb (Español):
{
"@@locale": "es",
"appName": "Mi Aplicación",
"welcomeMessage": "¡Bienvenido a nuestra aplicación!",
"buttonSubmit": "Enviar",
"errorNetwork": "Error de red. Por favor, inténtalo de nuevo.",
"itemsCount": "{count, plural, =0{No hay elementos} one{1 elemento} other{{count} elementos}}",
"userGreeting": "¡Hola, {name}! Tienes {messageCount, plural, =0{no hay mensajes} one{1 mensaje} other{{messageCount} mensajes}}."
}
lib/l10n/app_fr.arb (Francés):
{
"@@locale": "fr",
"appName": "Mon Application",
"welcomeMessage": "Bienvenue dans notre application!",
"buttonSubmit": "Soumettre",
"errorNetwork": "Erreur réseau. Veuillez réessayer.",
"itemsCount": "{count, plural, =0{Aucun élément} one{1 élément} other{{count} éléments}}",
"userGreeting": "Bonjour, {name}! Vous avez {messageCount, plural, =0{aucun message} one{1 message} other{{messageCount} messages}}."
}
Paso 4: Generar Archivos de Localización
Ejecuta el generador de código:
flutter gen-l10n
O usa:
flutter pub run intl_utils:generate
Esto genera app_localizations.dart y archivos específicos de idioma en .dart_tool/flutter_gen/gen_l10n/.
Paso 5: Configurar MaterialApp
Actualiza tu main.dart para habilitar la localización:
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Localization Demo',
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'), // English
Locale('es'), // Spanish
Locale('fr'), // French
],
home: const HomePage(),
);
}
}
Paso 6: Usar Cadenas Localizadas
Accede a las cadenas localizadas usando AppLocalizations.of(context):
Antes:
Text('Welcome to our app!')
Después:
Text(AppLocalizations.of(context)!.welcomeMessage)
O con un enfoque null-safe:
final l10n = AppLocalizations.of(context);
if (l10n != null) {
Text(l10n.welcomeMessage)
}
Paso 7: Manejar Formato de Cadenas con Variables
Usa placeholders en archivos ARB y Flutter maneja el formato:
Archivo ARB:
{
"userGreeting": "Hello, {name}!",
"@userGreeting": {
"placeholders": {
"name": {
"type": "String"
}
}
}
}
Dart:
Text(AppLocalizations.of(context)!.userGreeting('John'))
// Inglés: "Hello, John!"
// Español: "¡Hola, John!"
Paso 8: Pluralización
Flutter soporta pluralización usando el formato de mensaje ICU:
Archivo ARB:
{
"itemsCount": "{count, plural, =0{No items} one{1 item} other{{count} items}}"
}
Dart:
Text(AppLocalizations.of(context)!.itemsCount(5))
// Inglés: "5 items"
// Español: "5 elementos"
Text(AppLocalizations.of(context)!.itemsCount(1))
// Inglés: "1 item"
// Español: "1 elemento"
Text(AppLocalizations.of(context)!.itemsCount(0))
// Inglés: "No items"
// Español: "No hay elementos"
Paso 9: Formatear Fechas y Números
Usa el paquete intl para el formato localizado:
Fechas:
import 'package:intl/intl.dart';
final date = DateTime.now();
final formatter = DateFormat.yMMMMEEEEd(Intl.getCurrentLocale());
final localizedDate = formatter.format(date);
// Inglés: "Wednesday, March 20, 2025"
// Español: "miércoles, 20 de marzo de 2025"
Números:
import 'package:intl/intl.dart';
final number = 1234.56;
final formatter = NumberFormat.decimalPattern(Intl.getCurrentLocale());
final localizedNumber = formatter.format(number);
// Inglés: "1,234.56"
// Español: "1.234,56"
Moneda:
import 'package:intl/intl.dart';
final amount = 1234.56;
final formatter = NumberFormat.currency(locale: Intl.getCurrentLocale());
final localizedCurrency = formatter.format(amount);
// Inglés (US): "$1,234.56"
// Español (ES): "1.234,56 €"
Paso 10: Manejar Idiomas de Derecha a Izquierda (RTL)
Flutter maneja automáticamente los diseños RTL. Usa el widget Directionality si es necesario:
Directionality(
textDirection: TextDirection.rtl, // o TextDirection.ltr
child: YourWidget(),
)
O detecta automáticamente:
final locale = Localizations.localeOf(context);
final isRTL = locale.languageCode == 'ar' || locale.languageCode == 'he';
Paso 11: Probar tu Localización
Usando Flutter DevTools:
- Ejecuta tu app
- Abre DevTools
- Ve a la pestaña Inspector
- Cambia el locale en la configuración del dispositivo
Usando Configuración del Dispositivo:
- Ve a Settings > Language & Region del dispositivo
- Cambia el idioma del dispositivo
- Reinicia tu app
Programáticamente (para pruebas):
import 'package:flutter/material.dart';
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Locale _locale = const Locale('en');
void _changeLanguage(Locale locale) {
setState(() {
_locale = locale;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
locale: _locale,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('es'),
Locale('fr'),
],
home: LanguageSelector(
onLanguageChanged: _changeLanguage,
),
);
}
}
Mejores Prácticas
1. Usar Claves Descriptivas
Malo:
{
"msg1": "Submit"
}
Bueno:
{
"buttonSubmit": "Submit",
"@buttonSubmit": {
"description": "Submit button label"
}
}
2. Proporcionar Contexto en Archivos ARB
Siempre incluye entradas de metadatos @ para ayudar a los traductores:
{
"deleteButton": "Delete",
"@deleteButton": {
"description": "Button to delete an item. Shown in the item detail view."
}
}
3. Evitar Concatenación de Cadenas
Malo:
Text(AppLocalizations.of(context)!.hello + ' ' + name)
Bueno:
{
"helloUser": "Hello, {name}!",
"@helloUser": {
"placeholders": {
"name": {"type": "String"}
}
}
}
Text(AppLocalizations.of(context)!.helloUser(name))
4. Usar Pluralización Correctamente
Siempre usa el formato plural ICU para elementos contables:
{
"messagesCount": "{count, plural, =0{No messages} one{1 message} other{{count} messages}}"
}
5. Probar Longitudes de Cadenas
Algunos idiomas son más largos que otros. Diseña tu UI para acomodar:
- Alemán y finlandés: 30-50% más largos que el inglés
- Idiomas asiáticos: Pueden necesitar más espacio vertical
- Árabe y hebreo: Consideraciones de diseño RTL
6. Manejar Traducciones Faltantes
Flutter recurre al locale por defecto si falta una traducción. Siempre proporciona un fallback:
final l10n = AppLocalizations.of(context);
final text = l10n?.welcomeMessage ?? 'Welcome'; // Fallback
Errores Comunes
1. Olvidar Ejecutar la Generación de Código
Después de actualizar archivos ARB, siempre ejecuta:
flutter gen-l10n
2. No Incluir Todos los Delegados Requeridos
Asegúrate de incluir los cuatro delegados:
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
3. Codificar Cadenas de Formato
Malo:
Text('\$${amount.toStringAsFixed(2)}')
Bueno:
final formatter = NumberFormat.currency(locale: Intl.getCurrentLocale());
Text(formatter.format(amount))
4. No Probar Casos Extremos
Prueba con:
- Cadenas muy largas
- Caracteres especiales
- Números y fechas
- Formas plurales (0, 1, muchos)
- Idiomas RTL
Avanzado: Cambio Dinámico de Idioma
Para permitir a los usuarios cambiar el idioma dentro de la app:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LocaleProvider extends ChangeNotifier {
Locale _locale = const Locale('en');
Locale get locale => _locale;
Future<void> loadLocale() async {
final prefs = await SharedPreferences.getInstance();
final languageCode = prefs.getString('language_code') ?? 'en';
_locale = Locale(languageCode);
notifyListeners();
}
Future<void> setLocale(Locale locale) async {
if (_locale == locale) return;
_locale = locale;
final prefs = await SharedPreferences.getInstance();
await prefs.setString('language_code', locale.languageCode);
notifyListeners();
}
}
Uso:
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => LocaleProvider()..loadLocale(),
child: Consumer<LocaleProvider>(
builder: (context, localeProvider, _) {
return MaterialApp(
locale: localeProvider.locale,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'),
Locale('es'),
Locale('fr'),
],
home: const HomePage(),
);
},
),
);
}
}
Usando con GetX (Alternativa)
Si estás usando GetX para gestión de estado:
import 'package:get/get.dart';
class LocaleController extends GetxController {
var locale = const Locale('en').obs;
void changeLocale(Locale newLocale) {
locale.value = newLocale;
Get.updateLocale(newLocale);
}
}
Conclusión
Localizar tu app Flutter es sencillo cuando sigues estos pasos:
- Añade los paquetes
flutter_localizationseintl - Crea archivos ARB para cada idioma en
lib/l10n/ - Configura
l10n.yamly ejecuta la generación de código - Configura
localizationsDelegatesenMaterialApp - Usa
AppLocalizations.of(context)en toda tu app - Maneja la pluralización con el formato de mensaje ICU
- Formatea fechas y números usando el paquete
intl - Prueba exhaustivamente en todos los idiomas soportados
Siguiendo estas prácticas, crearás una app que proporciona una experiencia nativa para usuarios de todo el mundo, expandiendo significativamente tu base de usuarios potencial.
Optimiza tu Flujo de Trabajo de Localización Flutter
Gestionar traducciones para múltiples idiomas puede volverse complejo a medida que tu app crece. Considera usar una plataforma de gestión de traducciones para:
- Colaborar con traductores
- Mantener las traducciones sincronizadas con tu código base
- Automatizar el flujo de trabajo de traducción
- Mantener la consistencia en todos los idiomas
- Generar archivos ARB automáticamente
- Integrar con tu pipeline CI/CD
¿Listo para llevar tu app Flutter a nivel global? Explora la plataforma de localización de AZbox y optimiza tu flujo de trabajo de traducción: