Saltar al contenido principal

Internacionalizar aplicaciones de Flutter

Cómo internacionalizar tu aplicación de Flutter.

Si tu aplicación podría desplegarse para usuarios que hablan otro idioma, entonces necesitarás internacionalizarla. Eso significa que necesitas escribir la aplicación de una manera que haga posible localizar valores como texto y layouts para cada idioma o locale que admita la aplicación. Flutter proporciona widgets y clases que ayudan con la internacionalización y las propias librerías de Flutter están internacionalizadas.

Esta página cubre conceptos y flujos de trabajo necesarios para localizar una aplicación de Flutter usando las clases MaterialApp y CupertinoApp, ya que la mayoría de las aplicaciones se escriben de esa manera. Sin embargo, las aplicaciones escritas usando la clase de nivel más bajo WidgetsApp también se pueden internacionalizar usando las mismas clases y lógica.

Introducción a las localizaciones en Flutter

#

Esta sección proporciona un tutorial sobre cómo crear e internacionalizar una nueva aplicación de Flutter, junto con cualquier configuración adicional que una plataforma de destino pueda requerir.

Puedes encontrar el código fuente de este ejemplo en gen_l10n_example.

Configurar una aplicación internacionalizada: el paquete Flutter_localizations

#

Por defecto, Flutter solo proporciona localizaciones en inglés de EE. UU. Para añadir soporte para otros idiomas, una aplicación debe especificar propiedades adicionales de MaterialApp (o CupertinoApp), e incluir un paquete llamado flutter_localizations.

Para empezar, comienza creando una nueva aplicación de Flutter en el directorio que elijas con el comando flutter create.

flutter create <name_of_flutter_app>

Para usar flutter_localizations, añade el paquete como una dependencia en tu archivo pubspec.yaml, así como el paquete intl:

flutter pub add flutter_localizations --sdk=flutter
flutter pub add intl:any

Esto crea un archivo pubspec.yml con las siguientes entradas:

yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: any

Luego importa la librería flutter_localizations y especifica localizationsDelegates y supportedLocales para tu MaterialApp o CupertinoApp:

dart
import 'package:flutter_localizations/flutter_localizations.dart';
dart
return const MaterialApp(
  title: 'Localizations Sample App',
  localizationsDelegates: [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    Locale('en'), // English
    Locale('es'), // Spanish
  ],
  home: MyHomePage(),
);

Después de introducir el paquete flutter_localizations y añadir el código anterior, los paquetes Material y Cupertino deberían estar correctamente localizados en uno de los locales admitidos. Los widgets deberían adaptarse a los mensajes localizados, junto con un layout correcto de izquierda a derecha o de derecha a izquierda.

Prueba cambiar el locale de la plataforma de destino a español (es) y los mensajes deberían estar localizados.

Las aplicaciones basadas en WidgetsApp son similares excepto que el GlobalMaterialLocalizations.delegate no es necesario.

Se prefiere el constructor completo Locale.fromSubtags ya que admite scriptCode, aunque el constructor por defecto de Locale sigue siendo completamente válido.

Los elementos de la lista localizationsDelegates son factorys que producen colecciones de valores localizados. GlobalMaterialLocalizations.delegate proporciona strings localizados y otros valores para la librería de Material Components. GlobalWidgetsLocalizations.delegate define la dirección del texto por defecto, ya sea de izquierda a derecha o de derecha a izquierda, para la librería de widgets.

Más información sobre estas propiedades de la aplicación, los tipos de los que dependen, y cómo se estructuran típicamente las aplicaciones internacionalizadas de Flutter, se cubre en esta página.

Anular el locale

#

Localizations.override es un constructor factory para el widget Localizations que permite manejar la situación (típicamente poco común) en la que una sección de tu aplicación necesita estar localizada en un locale diferente al locale configurado para tu dispositivo.

Para observar este comportamiento, añade una llamada a Localizations.override y un simple CalendarDatePicker:

dart
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text(widget.title)),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          // Add the following code
          Localizations.override(
            context: context,
            locale: const Locale('es'),
            // Using a Builder to get the correct BuildContext.
            // Alternatively, you can create a new widget and Localizations.override
            // will pass the updated BuildContext to the new widget.
            child: Builder(
              builder: (context) {
                // A toy example for an internationalized Material widget.
                return CalendarDatePicker(
                  initialDate: DateTime.now(),
                  firstDate: DateTime(1900),
                  lastDate: DateTime(2100),
                  onDateChanged: (value) {},
                );
              },
            ),
          ),
        ],
      ),
    ),
  );
}

Haz Hot Reload en la aplicación y el widget CalendarDatePicker debería volver a renderizarse en español.

Añadir tus propios mensajes localizados

#

Después de añadir el paquete flutter_localizations, puedes configurar la localización. Para añadir texto localizado a tu aplicación, completa las siguientes instrucciones:

  1. Añade el paquete intl como dependencia, trayendo la versión fijada por flutter_localizations:

    flutter pub add intl:any
    
  2. Abre el archivo pubspec.yaml y habilita el flag generate. Este flag se encuentra en la sección flutter del archivo pubspec.

    yaml
    # The following section is specific to Flutter.
    flutter:
      generate: true # Add this line
    
  3. Añade un nuevo archivo yaml al directorio raíz del proyecto de Flutter. Nombra este archivo l10n.yaml e incluye el siguiente contenido:

    yaml
    arb-dir: lib/l10n
    template-arb-file: app_en.arb
    output-localization-file: app_localizations.dart
    

    Este archivo configura la herramienta de localización. En este ejemplo, has hecho lo siguiente:

    • Pusiste los archivos de entrada App Resource Bundle (.arb) en ${FLUTTER_PROJECT}/lib/l10n. Los .arb proporcionan recursos de localización para tu aplicación.
    • Estableciste la plantilla en inglés como app_en.arb.
    • Le dijiste a Flutter que genere las localizaciones en el archivo app_localizations.dart.
  4. En ${FLUTTER_PROJECT}/lib/l10n, añade el archivo de plantilla app_en.arb. Por ejemplo:

    json
    {
      "helloWorld": "Hello World!",
      "@helloWorld": {
        "description": "The conventional newborn programmer greeting"
      }
    }
    
  5. Añade otro archivo de bundle llamado app_es.arb en el mismo directorio. En este archivo, añade la traducción al español del mismo mensaje.

    json
    {
        "helloWorld": "¡Hola Mundo!"
    }
    
  6. Ahora, ejecuta flutter pub get o flutter run y la generación de código (codegen) se realiza automáticamente. Deberías encontrar los archivos generados en el directorio en la ruta que especificaste con las opciones arb-dir o output-dir. Alternativamente, también puedes ejecutar flutter gen-l10n para generar los mismos archivos sin ejecutar la aplicación.

  7. Añade la sentencia de importación en app_localizations.dart y AppLocalizations.delegate en tu llamada al constructor para MaterialApp:

    dart
    import 'l10n/app_localizations.dart';
    
    dart
    return const MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: [
        AppLocalizations.delegate, // Add this line
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        Locale('en'), // English
        Locale('es'), // Spanish
      ],
      home: MyHomePage(),
    );
    

    La clase AppLocalizations también proporciona listas auto-generadas de localizationsDelegates y supportedLocales. Puedes usar estas en lugar de proporcionarlas manualmente.

    dart
    const MaterialApp(
      title: 'Localizations Sample App',
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
    );
    
  8. Una vez que la aplicación Material ha iniciado, puedes usar AppLocalizations en cualquier lugar de tu aplicación:

    dart
    appBar: AppBar(
      // The [AppBar] title text should update its message
      // according to the system locale of the target platform.
      // Switching between English and Spanish locales should
      // cause this text to update.
      title: Text(AppLocalizations.of(context)!.helloWorld),
    ),
    

Este código genera un widget Text que muestra "Hello World!" si el locale del dispositivo de destino está configurado en inglés, y "¡Hola Mundo!" si el locale del dispositivo de destino está configurado en español. En los archivos arb, la clave de cada entrada se usa como el nombre del método del getter, mientras que el valor de esa entrada contiene el mensaje localizado.

El gen_l10n_example usa esta herramienta.

Para localizar la descripción de tu aplicación de dispositivo, pasa el string localizado a MaterialApp.onGenerateTitle:

dart
return MaterialApp(
  onGenerateTitle: (context) => DemoLocalizations.of(context).title,

Marcadores de posición, plurales y selecciones

#

También puedes incluir valores de la aplicación en un mensaje con sintaxis especial que usa un marcador de posición (placeholder) para generar un método en lugar de un getter. Un marcador de posición, que debe ser un nombre identificador de Dart válido, se convierte en un parámetro posicional en el método generado en el código de AppLocalizations. Define un nombre de marcador de posición envolviéndolo en llaves como se muestra a continuación:

json
"{placeholderName}"

Define cada marcador de posición en el objeto placeholders en el archivo .arb de la aplicación. Por ejemplo, para definir un mensaje de saludo con un parámetro userName, añade lo siguiente a lib/l10n/app_en.arb:

json
"hello": "Hello {userName}",
"@hello": {
  "description": "A message with a single parameter",
  "placeholders": {
    "userName": {
      "type": "String",
      "example": "Bob"
    }
  }
}

Este fragmento de código añade una llamada al método hello al objeto AppLocalizations.of(context), y el método acepta un parámetro de tipo String; el método hello devuelve un string. Regenera el archivo AppLocalizations.

Reemplaza el código pasado a Builder con lo siguiente:

dart
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    // Returns 'Hello John'
    Text(AppLocalizations.of(context)!.hello('John')),
  ],
);

También puedes usar marcadores de posición numéricos para especificar múltiples valores. Diferentes idiomas tienen diferentes formas de pluralizar palabras. La sintaxis también admite especificar cómo se debe pluralizar una palabra. Un mensaje pluralizado debe incluir un parámetro num que indique cómo pluralizar la palabra en diferentes situaciones. El inglés, por ejemplo, pluraliza "person" a "people", pero eso no es suficiente. El plural de message0 podría ser "no people" o "zero people". El plural de messageFew podría ser "several people", "some people", o "a few people". El plural de messageMany podría ser "most people" o "many people", o "a crowd". Solo el campo más general messageOther es obligatorio. El siguiente ejemplo muestra qué opciones están disponibles:

json
"{countPlaceholder, plural, =0{message0} =1{message1} =2{message2} few{messageFew} many{messageMany} other{messageOther}}"

La expresión anterior se reemplaza por la variación del mensaje (message0, message1, ...) correspondiente al valor del countPlaceholder. Solo el campo messageOther es obligatorio.

El siguiente ejemplo define un mensaje que pluraliza la palabra "wombat":

json
"nWombats": "{count, plural, =0{no wombats} =1{1 wombat} other{{count} wombats}}",
"@nWombats": {
  "description": "A plural message",
  "placeholders": {
    "count": {
      "type": "num",
      "format": "compact"
    }
  }
}

Usa un método plural pasando el parámetro count:

dart
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    ...
    // Returns 'no wombats'
    Text(AppLocalizations.of(context)!.nWombats(0)),
    // Returns '1 wombat'
    Text(AppLocalizations.of(context)!.nWombats(1)),
    // Returns '5 wombats'
    Text(AppLocalizations.of(context)!.nWombats(5)),
  ],
);

De forma similar a los plurales, también puedes elegir un valor basado en un marcador de posición de tipo String. Esto se utiliza más a menudo para admitir idiomas con género. La sintaxis es la siguiente:

json
"{selectPlaceholder, select, case{message} ... other{messageOther}}"

El siguiente ejemplo define un mensaje que selecciona un pronombre basado en el género:

json
"pronoun": "{gender, select, male{he} female{she} other{they}}",
"@pronoun": {
  "description": "A gendered message",
  "placeholders": {
    "gender": {
      "type": "String"
    }
  }
}

Usa esta característica pasando la cadena de género como parámetro:

dart
// Examples of internationalized strings.
return Column(
  children: <Widget>[
    ...
    // Returns 'he'
    Text(AppLocalizations.of(context)!.pronoun('male')),
    // Returns 'she'
    Text(AppLocalizations.of(context)!.pronoun('female')),
    // Returns 'they'
    Text(AppLocalizations.of(context)!.pronoun('other')),
  ],
);

Ten en cuenta que al usar sentencias select, la comparación entre el parámetro y el valor real distingue entre mayúsculas y minúsculas. Es decir, AppLocalizations.of(context)!.pronoun("Male") por defecto recurre al caso "other", y devuelve "they".

Sintaxis de escape

#

A veces, tienes que usar tokens, tales como { y }, como caracteres normales. Para ignorar dichos tokens sin que sean analizados, habilita el flag use-escaping añadiendo lo siguiente a l10n.yaml:

yaml
use-escaping: true

El analizador ignora cualquier cadena de caracteres envuelta con un par de comillas simples. Para usar un carácter de comilla simple normal, usa un par de comillas simples consecutivas. Por ejemplo, el siguiente texto se convierte a un String de Dart:

json
{
  "helloWorld": "Hello! '{Isn''t}' this a wonderful day?"
}

El string resultante es el siguiente:

dart
"Hello! {Isn't} this a wonderful day?"

Mensajes con números y monedas

#

Los números, incluidos los que representan valores de moneda, se muestran de manera muy diferente en distintas localizaciones. La herramienta de generación de localizaciones en flutter_localizations usa la clase NumberFormat en el paquete intl para formatear números según el locale y el formato deseado.

Los tipos int, double y num pueden usar cualquiera de los siguientes constructores de NumberFormat:

Valor de "formato" del mensajeSalida para 1200000
compact"1.2M"
compactCurrency*"$1.2M"
compactSimpleCurrency*"$1.2M"
compactLong"1.2 millones"
currency*"USD1,200,000.00"
decimalPattern"1,200,000"
decimalPatternDigits*"1,200,000"
decimalPercentPattern*"120,000,000%"
percentPattern"120,000,000%"
scientificPattern"1E6"
simpleCurrency*"$1,200,000"

Los constructores con asterisco de NumberFormat en la tabla ofrecen parámetros nombrados opcionales. Esos parámetros se pueden especificar como el valor del objeto optionalParameters del marcador de posición. Por ejemplo, para especificar el parámetro opcional decimalDigits para compactCurrency, realiza los siguientes cambios en el archivo lib/l10n/app_en.arb:

json
"numberOfDataPoints": "Number of data points: {value}",
"@numberOfDataPoints": {
  "description": "A message with a formatted int parameter",
  "placeholders": {
    "value": {
      "type": "int",
      "format": "compactCurrency",
      "optionalParameters": {
        "decimalDigits": 2
      }
    }
  }
}

Mensajes con fechas

#

Las cadenas de fechas se formatean de muchas maneras diferentes dependiendo tanto del locale como de las necesidades de la aplicación.

Los valores del marcador de posición con tipo DateTime se formatean con DateFormat en el paquete intl.

Hay 41 variaciones de formato, identificadas por los nombres de sus constructores factory de DateFormat. En el siguiente ejemplo, el valor de DateTime que aparece en el mensaje helloWorldOn se formatea con DateFormat.yMd:

json
"helloWorldOn": "Hello World on {date}",
"@helloWorldOn": {
  "description": "A message with a date parameter",
  "placeholders": {
    "date": {
      "type": "DateTime",
      "format": "yMd"
    }
  }
}

En una aplicación donde el locale es inglés de EE. UU., la siguiente expresión produciría "7/9/1959". En un locale ruso, produciría "9.07.1959".

dart
AppLocalizations.of(context).helloWorldOn(DateTime.utc(1959, 7, 9))

Localizar para iOS: Actualizar el bundle de la aplicación iOS

#

Aunque las localizaciones son gestionadas por Flutter, necesitas añadir los idiomas admitidos en el proyecto de Xcode. Esto garantiza que tu entrada en la App Store muestre correctamente los idiomas admitidos.

Para configurar los locales admitidos por tu aplicación, usa las siguientes instrucciones:

  1. Abre el archivo Xcode ios/Runner.xcodeproj de tu proyecto.

  2. En el Project Navigator, selecciona el archivo de proyecto Runner debajo de Projects.

  3. Selecciona la pestaña Info en el editor del proyecto.

  4. En la sección Localizations, haz clic en el botón Add (+) para añadir los idiomas y regiones admitidos a tu proyecto. Cuando se te pida elegir archivos e idioma de referencia, simplemente selecciona Finish.

  5. Xcode crea automáticamente archivos .strings vacíos y actualiza el archivo ios/Runner.xcodeproj/project.pbxproj. Estos archivos son utilizados por la App Store para determinar qué idiomas y regiones admite tu aplicación.

Temas avanzados para una mayor personalización

#

Esta sección cubre formas adicionales de personalizar una aplicación de Flutter localizada.

Definición avanzada de locales

#

Algunos idiomas con múltiples variantes requieren más que solo un código de idioma para diferenciarse correctamente.

Por ejemplo, diferenciar completamente todas las variantes de chino requiere especificar el código de idioma, el código de escritura (script code), y el código de país. Esto se debe a la existencia de escritura simplificada y tradicional, así como a diferencias regionales en la forma en que se escriben los caracteres dentro del mismo tipo de escritura.

Para expresar completamente cada variante de chino para los códigos de país CN, TW y HK, la lista de locales admitidos debería incluir:

dart
supportedLocales: [
  Locale.fromSubtags(languageCode: 'zh'), // generic Chinese 'zh'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hans',
  ), // generic simplified Chinese 'zh_Hans'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hant',
  ), // generic traditional Chinese 'zh_Hant'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hans',
    countryCode: 'CN',
  ), // 'zh_Hans_CN'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hant',
    countryCode: 'TW',
  ), // 'zh_Hant_TW'
  Locale.fromSubtags(
    languageCode: 'zh',
    scriptCode: 'Hant',
    countryCode: 'HK',
  ), // 'zh_Hant_HK'
],

Esta definición completa y explícita garantiza que tu aplicación pueda distinguir y proporcionar contenido localizado con todos los matices a todas las combinaciones de estos códigos de país. Si el locale preferido del usuario no está especificado, Flutter selecciona la coincidencia más cercana, que probablemente contiene diferencias con respecto a lo que el usuario espera. Flutter solo se resuelve a locales definidos en supportedLocales y proporciona contenido localizado diferenciado por scriptCode para idiomas comúnmente utilizados. Consulta Localizations para obtener información sobre cómo se resuelven los locales admitidos y los locales preferidos.

Aunque el chino es un ejemplo principal, otros idiomas como el francés (fr_FR, fr_CA) también deberían estar completamente diferenciados para una localización con más matices.

Rastrear el locale: La clase Locale y el widget Localizations

#

La clase Locale identifica el idioma del usuario. Los dispositivos móviles admiten establecer el locale para todas las aplicaciones, usualmente usando un menú de configuración del sistema. Las aplicaciones internacionalizadas responden mostrando valores que son específicos del locale. Por ejemplo, si el usuario cambia el locale del dispositivo de inglés a francés, entonces un widget Text que originalmente mostraba "Hello World" se reconstruiría con "Bonjour le monde".

El widget Localizations define el locale para su hijo y los recursos localizados de los que depende el hijo. El widget WidgetsApp crea un widget Localizations y lo reconstruye si el locale del sistema cambia.

Siempre puedes consultar el locale actual de una aplicación con Localizations.localeOf():

dart
Locale myLocale = Localizations.localeOf(context);

Especificar el parámetro supportedLocales de la aplicación

#

Aunque la librería flutter_localizations admite muchos idiomas y variantes de idioma, por defecto solo están disponibles las traducciones en idioma inglés. Depende del desarrollador decidir exactamente qué idiomas admitir.

El parámetro supportedLocales de MaterialApp limita los cambios de locale. Cuando el usuario cambia la configuración de locale en su dispositivo, el widget Localizations de la aplicación solo sigue el cambio si el nuevo locale es miembro de esta lista. Si no se encuentra una coincidencia exacta para el locale del dispositivo, entonces se utiliza el primer locale admitido con un languageCode coincidente. Si eso falla, entonces se utiliza el primer elemento de la lista supportedLocales.

Una aplicación que quiera usar un método diferente de "resolución de locale" puede proporcionar un localeResolutionCallback. Por ejemplo, para hacer que tu aplicación acepte incondicionalmente cualquier locale que seleccione el usuario:

dart
MaterialApp(
  localeResolutionCallback: (locale, supportedLocales) {
    return locale;
  },
);

Configurar el archivo l10n.yaml

#

El archivo l10n.yaml te permite configurar la herramienta gen-l10n para especificar lo siguiente:

  • dónde se ubican todos los archivos de entrada
  • dónde deberían crearse todos los archivos de salida
  • qué nombre de clase Dart darle a tu delegado de localizaciones

Para ver una lista completa de opciones, ejecuta flutter gen-l10n --help en la línea de comandos o consulta la siguiente tabla:

OpciónDescripción
arb-dir El directorio donde se ubican la plantilla y los archivos arb traducidos. El valor por defecto es lib/l10n .
output-dir El directorio donde se escriben las clases de localización generadas. Esta opción solo es relevante si quieres generar el código de localizaciones en otro lugar del proyecto de Flutter. También necesitas establecer el flag synthetic-package en false.

La aplicación debe importar el archivo especificado en la opción output-localization-file desde este directorio. Si no se especifica, el valor por defecto es el mismo directorio que el directorio de entrada especificado en arb-dir .
template-arb-file El archivo arb de plantilla que se utiliza como base para generar los archivos de mensajes y localización de Dart. El valor por defecto es app_en.arb .
output-localization-file El nombre de archivo para las clases de salida de localización y delegado de localizaciones. El valor por defecto es app_localizations.dart .
untranslated-messages-file La ubicación de un archivo que describe los mensajes de localización que aún no se han traducido. El uso de esta opción crea un archivo JSON en la ubicación de destino, con el siguiente formato:

"locale": ["message_1", "message_2" ... "message_n"]

Si no se especifica esta opción, se imprime un resumen de los mensajes que no se han traducido en la línea de comandos.
output-class El nombre de clase Dart a utilizar para las clases de salida de localización y delegado de localizaciones. El valor por defecto es AppLocalizations .
preferred-supported-locales La lista de locales admitidos preferidos para la aplicación. Por defecto, la herramienta genera la lista de locales admitidos en orden alfabético. Usa este flag para establecer por defecto un locale diferente.

Por ejemplo, pasa [ en_US ] para establecer inglés americano por defecto si el dispositivo lo admite.
header El encabezado a anteponer a los archivos de localizaciones de Dart generados. Esta opción toma un string.

Por ejemplo, pasa "/// All localized files." para anteponer este string al archivo Dart generado.

Alternativamente, consulta la opción header-file para pasar un archivo de texto para encabezados más largos.
header-file El encabezado a anteponer a los archivos de localizaciones de Dart generados. El valor de esta opción es el nombre del archivo que contiene el texto de encabezado que se inserta en la parte superior de cada archivo Dart generado.

Alternativamente, consulta la opción header para pasar un string para un encabezado más simple.

Este archivo debe colocarse en el directorio especificado en arb-dir .
[no-]use-deferred-loading Especifica si se debe generar el archivo de localización de Dart con los locales importados como diferidos (deferred), lo que permite la carga diferida (lazy loading) de cada locale en Flutter Web.

Esto puede reducir el tiempo de inicio inicial de una aplicación web al disminuir el tamaño del bundle de JavaScript. Cuando este flag se establece en true, los mensajes para un locale en particular solo son descargados y cargados por la aplicación de Flutter según se necesiten. Para proyectos con muchos locales diferentes y muchas cadenas de localización, puede mejorar el rendimiento diferir la carga. Para proyectos con un número pequeño de locales, la diferencia es insignificante y podría ralentizar el inicio en comparación con incluir las localizaciones con el resto de la aplicación.

Ten en cuenta que este flag no afecta a otras plataformas como mobile o desktop.
gen-inputs-and-outputs-list Cuando se especifica, la herramienta genera un archivo JSON que contiene las entradas y salidas de la herramienta, llamado gen_l10n_inputs_and_outputs.json .

Esto puede ser útil para realizar un seguimiento de qué archivos del proyecto de Flutter se utilizaron al generar el último conjunto de localizaciones. Por ejemplo, el sistema de build de la herramienta de Flutter usa este archivo para realizar un seguimiento de cuándo llamar a gen_l10n durante Hot Reload.

El valor de esta opción es el directorio donde se genera el archivo JSON. Cuando es null, el archivo JSON no se generará.
synthetic-package Determina si los archivos de salida generados se generan como un paquete sintético o en un directorio especificado en el proyecto de Flutter. Este flag es true por defecto. Cuando synthetic-package se establece en false , genera los archivos de localizaciones en el directorio especificado por arb-dir por defecto. Si se especifica output-dir , los archivos se generan allí.
project-dir Cuando se especifica, la herramienta usa la ruta pasada a esta opción como el directorio raíz del proyecto de Flutter.

Cuando es null, se utiliza la ruta relativa al directorio de trabajo actual.
[no-]required-resource-attributes Requiere que todos los id de recurso contengan un atributo de recurso correspondiente.

Por defecto, los mensajes simples no requerirán metadatos, pero se recomienda encarecidamente ya que esto proporciona contexto sobre el significado de un mensaje a los lectores.

Los atributos de recurso siguen siendo necesarios para los mensajes plurales.
[no-]nullable-getter Especifica si el getter de la clase de localizaciones admite nulos (is nullable).

Por defecto, este valor es true para que Localizations.of(context) devuelva un valor anulable para compatibilidad hacia atrás. Si este valor es false, entonces se realiza una verificación de null en el valor devuelto de Localizations.of(context) , eliminando la necesidad de verificación de null en el código del usuario.
[no-]format Cuando se especifica, el comando dart format se ejecuta después de generar los archivos de localización.
use-escaping Especifica si se habilita el uso de comillas simples como sintaxis de escape.
[no-]suppress-warningsCuando se especifica, se suprimen todas las advertencias.
[no-]relax-syntax Cuando se especifica, la sintaxis se relaja de modo que el carácter especial "{" se trata como un string si no va seguido de un marcador de posición válido y "}" se trata como un string si no cierra ningún "{" anterior que se trate como un carácter especial.
[no-]use-named-parameters Si se deben usar parámetros nombrados para los métodos de localización generados.

Cómo funciona la internacionalización en Flutter

#

Esta sección cubre los detalles técnicos de cómo funcionan las localizaciones en Flutter. Si estás planeando admitir tu propio conjunto de mensajes localizados, el siguiente contenido será de ayuda. De lo contrario, puedes omitir esta sección.

Cargar y recuperar valores localizados

#

El widget Localizations se utiliza para cargar y buscar objetos que contienen colecciones de valores localizados. Las aplicaciones se refieren a estos objetos con Localizations.of(context,type). Si el locale del dispositivo cambia, el widget Localizations carga automáticamente los valores para el nuevo locale y luego reconstruye los widgets que lo usaron. Esto sucede porque Localizations funciona como un InheritedWidget. Cuando una función de build se refiere a un inherited widget, se crea una dependencia implícita en el inherited widget. Cuando un inherited widget cambia (cuando cambia el locale del widget Localizations), sus contextos dependientes son reconstruidos.

Los valores localizados son cargados por la lista de LocalizationsDelegates del widget Localizations. Cada delegado debe definir un método asíncrono load() que produce un objeto que encapsula una colección de valores localizados. Típicamente estos objetos definen un método por valor localizado.

En una aplicación grande, diferentes módulos o paquetes pueden empaquetarse con sus propias localizaciones. Es por eso que el widget Localizations gestiona una tabla de objetos, uno por cada LocalizationsDelegate. Para recuperar el objeto producido por uno de los métodos load de un LocalizationsDelegate, especifica un BuildContext y el tipo de objeto.

Por ejemplo, los strings localizados para los widgets de Material Components están definidos por la clase MaterialLocalizations. Las instancias de esta clase son creadas por un LocalizationDelegate proporcionado por la clase MaterialApp. Se pueden recuperar con Localizations.of():

dart
Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);

Esta expresión particular Localizations.of() se usa con frecuencia, por lo que la clase MaterialLocalizations proporciona un atajo conveniente:

dart
static MaterialLocalizations of(BuildContext context) {
  return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
}

/// References to the localized values defined by MaterialLocalizations
/// are typically written like this:

tooltip: MaterialLocalizations.of(context).backButtonTooltip,

Definir una clase para los recursos localizados de la aplicación

#

Armar una aplicación de Flutter internacionalizada usualmente comienza con la clase que encapsula los valores localizados de la aplicación. El ejemplo que sigue es típico de tales clases.

Código fuente completo de intl_example para esta aplicación.

Este ejemplo se basa en las API y herramientas proporcionadas por el paquete intl. La sección Una clase alternativa para los recursos localizados de la aplicación describe un ejemplo que no depende del paquete intl.

La clase DemoLocalizations (definida en el siguiente fragmento de código) contiene los strings de la aplicación (solo uno para el ejemplo) traducidos a los locales que admite la aplicación. Usa la función initializeMessages() generada por el paquete intl de Dart, Intl.message(), para buscarlos.

dart
class DemoLocalizations {
  DemoLocalizations(this.localeName);

  static Future<DemoLocalizations> load(Locale locale) {
    final String name =
        locale.countryCode == null || locale.countryCode!.isEmpty
        ? locale.languageCode
        : locale.toString();
    final String localeName = Intl.canonicalizedLocale(name);

    return initializeMessages(localeName).then((_) {
      return DemoLocalizations(localeName);
    });
  }

  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations)!;
  }

  final String localeName;

  String get title {
    return Intl.message(
      'Hello World',
      name: 'title',
      desc: 'Title for the Demo application',
      locale: localeName,
    );
  }
}

Una clase basada en el paquete intl importa un catálogo de mensajes generado que proporciona la función initializeMessages() y el almacenamiento de respaldo por locale para Intl.message(). El catálogo de mensajes se produce mediante una herramienta de intl que analiza el código fuente en busca de clases que contengan llamadas a Intl.message(). En este caso, esa sería simplemente la clase DemoLocalizations.

Añadir soporte para un nuevo idioma

#

Una aplicación que necesita admitir un idioma que no está incluido en GlobalMaterialLocalizations tiene que hacer algún trabajo adicional: debe proporcionar alrededor de 70 traducciones ("localizaciones") para palabras o frases y los patrones de fecha y símbolos para el locale.

Consulta lo siguiente para ver un ejemplo de cómo añadir soporte para el idioma noruego Nynorsk.

Una nueva subclase de GlobalMaterialLocalizations define las localizaciones de las que depende la librería Material. También debe definirse una nueva subclase de LocalizationsDelegate, que sirve como factory para la subclase de GlobalMaterialLocalizations.

Aquí está el código fuente para el ejemplo completo add_language, menos las traducciones reales a Nynorsk.

La subclase de GlobalMaterialLocalizations específica del locale se llama NnMaterialLocalizations, y la subclase de LocalizationsDelegate es _NnMaterialLocalizationsDelegate. El valor de NnMaterialLocalizations.delegate es una instancia del delegado, y es todo lo que necesita una aplicación que usa estas localizaciones.

La clase delegada incluye localizaciones básicas de formato de fecha y número. Todas las demás localizaciones se definen mediante getters de propiedades con valor String en NnMaterialLocalizations, como este:

dart
@override
String get moreButtonTooltip => r'More';

@override
String get aboutListTileTitleRaw => r'About $applicationName';

@override
String get alertDialogLabel => r'Alert';

Estas son las traducciones al inglés, por supuesto. Para completar el trabajo, necesitas cambiar el valor de retorno de cada getter a una cadena apropiada en Nynorsk.

Los getters devuelven cadenas "raw" de Dart que tienen un prefijo r, tales como r'About $applicationName', porque a veces las cadenas contienen variables con un prefijo $. Las variables se expanden mediante métodos de localización parametrizados:

dart
@override
String get pageRowsInfoTitleRaw => r'$firstRow–$lastRow of $rowCount';

@override
String get pageRowsInfoTitleApproximateRaw =>
    r'$firstRow–$lastRow of about $rowCount';

Los patrones de fecha y símbolos del locale también deben especificarse, los cuales se definen en el código fuente de la siguiente manera:

dart
const nnLocaleDatePatterns = {
  'd': 'd.',
  'E': 'ccc',
  'EEEE': 'cccc',
  'LLL': 'LLL',
  // ...
}
dart
const Map<String, Object?> nnDateSymbols = {
  'NAME': 'nn',
  'ERAS': <dynamic>['f.Kr.', 'e.Kr.'],

Estos valores deben ser modificados para que el locale use el formato de fecha correcto. Desafortunadamente, dado que la librería intl no comparte la misma flexibilidad para el formateo de números, el formateo para un locale existente debe ser usado como sustituto en _NnMaterialLocalizationsDelegate:

dart
class _NnMaterialLocalizationsDelegate
    extends LocalizationsDelegate<MaterialLocalizations> {
  const _NnMaterialLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) => locale.languageCode == 'nn';

  @override
  Future<MaterialLocalizations> load(Locale locale) async {
    final String localeName = intl.Intl.canonicalizedLocale(locale.toString());

    // The locale (in this case `nn`) needs to be initialized into the custom
    // date symbols and patterns setup that Flutter uses.
    date_symbol_data_custom.initializeDateFormattingCustom(
      locale: localeName,
      patterns: nnLocaleDatePatterns,
      symbols: intl.DateSymbols.deserializeFromMap(nnDateSymbols),
    );

    return SynchronousFuture<MaterialLocalizations>(
      NnMaterialLocalizations(
        localeName: localeName,
        // The `intl` library's NumberFormat class is generated from CLDR data
        // (see https://github.com/dart-lang/i18n/blob/main/pkgs/intl/lib/number_symbols_data.dart).
        // Unfortunately, there is no way to use a locale that isn't defined in
        // this map and the only way to work around this is to use a listed
        // locale's NumberFormat symbols. So, here we use the number formats
        // for 'en_US' instead.
        decimalFormat: intl.NumberFormat('#,##0.###', 'en_US'),
        twoDigitZeroPaddedFormat: intl.NumberFormat('00', 'en_US'),
        // DateFormat here will use the symbols and patterns provided in the
        // `date_symbol_data_custom.initializeDateFormattingCustom` call above.
        // However, an alternative is to simply use a supported locale's
        // DateFormat symbols, similar to NumberFormat above.
        fullYearFormat: intl.DateFormat('y', localeName),
        compactDateFormat: intl.DateFormat('yMd', localeName),
        shortDateFormat: intl.DateFormat('yMMMd', localeName),
        mediumDateFormat: intl.DateFormat('EEE, MMM d', localeName),
        longDateFormat: intl.DateFormat('EEEE, MMMM d, y', localeName),
        yearMonthFormat: intl.DateFormat('MMMM y', localeName),
        shortMonthDayFormat: intl.DateFormat('MMM d'),
      ),
    );
  }

  @override
  bool shouldReload(_NnMaterialLocalizationsDelegate old) => false;
}

Para obtener más información sobre las cadenas de localización, consulta el README de flutter_localizations.

Una vez que hayas implementado tus subclases específicas de idioma de GlobalMaterialLocalizations y LocalizationsDelegate, necesitas añadir el idioma y una instancia del delegado a tu aplicación. El siguiente código establece el idioma de la aplicación en Nynorsk y añade la instancia del delegado NnMaterialLocalizations a la lista localizationsDelegates de la aplicación:

dart
const MaterialApp(
  localizationsDelegates: [
    GlobalWidgetsLocalizations.delegate,
    GlobalMaterialLocalizations.delegate,
    NnMaterialLocalizations.delegate, // Add the newly created delegate
  ],
  supportedLocales: [Locale('en', 'US'), Locale('nn')],
  home: Home(),
),

Flujos de trabajo de internacionalización alternativos

#

Esta sección describe diferentes enfoques para internacionalizar tu aplicación de Flutter.

Una clase alternativa para los recursos localizados de la aplicación

#

El ejemplo anterior se definió en términos del paquete intl de Dart. Puedes elegir tu propio enfoque para gestionar valores localizados en aras de la simplicidad o tal vez para integrarte con un framework de i18n diferente.

Código fuente completo para la aplicación minimal.

En el siguiente ejemplo, la clase DemoLocalizations incluye todas sus traducciones directamente en Maps por idioma:

dart
class DemoLocalizations {
  DemoLocalizations(this.locale);

  final Locale locale;

  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations)!;
  }

  static const _localizedValues = <String, Map<String, String>>{
    'en': {'title': 'Hello World'},
    'es': {'title': 'Hola Mundo'},
  };

  static List<String> languages() => _localizedValues.keys.toList();

  String get title {
    return _localizedValues[locale.languageCode]!['title']!;
  }
}

En la aplicación mínima, el DemoLocalizationsDelegate es ligeramente diferente. Su método load devuelve un SynchronousFuture porque no es necesario que se realice ninguna carga asíncrona.

dart
class DemoLocalizationsDelegate
    extends LocalizationsDelegate<DemoLocalizations> {
  const DemoLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) =>
      DemoLocalizations.languages().contains(locale.languageCode);

  @override
  Future<DemoLocalizations> load(Locale locale) {
    // Returning a SynchronousFuture here because an async "load" operation
    // isn't needed to produce an instance of DemoLocalizations.
    return SynchronousFuture<DemoLocalizations>(DemoLocalizations(locale));
  }

  @override
  bool shouldReload(DemoLocalizationsDelegate old) => false;
}

Usar las herramientas intl de Dart

#

Antes de construir una API usando el paquete intl de Dart, revisa la documentación del paquete intl. La siguiente lista resume el proceso para localizar una aplicación que depende del paquete intl:

La aplicación de demostración depende de un archivo fuente generado llamado l10n/messages_all.dart, que define todas las cadenas localizables utilizadas por la aplicación.

Reconstruir l10n/messages_all.dart requiere dos pasos.

  1. Con el directorio raíz de la aplicación como directorio actual, genera l10n/intl_messages.arb a partir de lib/main.dart:

    dart run intl_translation:extract_to_arb --output-dir=lib/l10n lib/main.dart
    

    El archivo intl_messages.arb es un mapa en formato JSON con una entrada por cada función Intl.message() definida en main.dart. Este archivo sirve como plantilla para las traducciones en inglés y español, intl_en.arb e intl_es.arb. Estas traducciones son creadas por ti, el desarrollador.

  2. Con el directorio raíz de la aplicación como directorio actual, genera intl_messages_<locale>.dart para cada archivo intl_<locale>.arb e intl_messages_all.dart, el cual importa todos los archivos de mensajes:

    dart run intl_translation:generate_from_arb \
        --output-dir=lib/l10n --no-use-deferred-loading \
        lib/main.dart lib/l10n/intl_*.arb
    

    Windows no admite comodines en nombres de archivo. En su lugar, enumera los archivos .arb que fueron generados por el comando intl_translation:extract_to_arb.

    dart run intl_translation:generate_from_arb \
        --output-dir=lib/l10n --no-use-deferred-loading \
        lib/main.dart \
        lib/l10n/intl_en.arb lib/l10n/intl_fr.arb lib/l10n/intl_messages.arb
    

    La clase DemoLocalizations usa la función generada initializeMessages() (definida en intl_messages_all.dart) para cargar los mensajes localizados e Intl.message() para buscarlos.

Más información

#

Si aprendes mejor leyendo código, consulta los siguientes ejemplos.

  • minimal
    El ejemplo minimal está diseñado para ser lo más simple posible.
  • intl_example
    usa las API y herramientas proporcionadas por el paquete intl.

Si el paquete intl de Dart es nuevo para ti, consulta Usar las herramientas intl de Dart.