# Localization

<div id="introduction"></div>

## Introducción

La localización te permite proporcionar tu aplicación en múltiples idiomas. Nylo v7 facilita la localización de texto usando archivos de idioma JSON.

Aquí tienes un ejemplo rápido:

**lang/en.json**
``` json
{
  "welcome": "Welcome",
  "greeting": "Hello {{name}}"
}
```

**En tu widget:**
``` dart
Text("welcome".tr())              // "Welcome"
Text("greeting".tr(arguments: {"name": "Anthony"}))  // "Hello Anthony"
```

<div id="configuration"></div>

## Configuración

La localización se configura en `lib/config/localization.dart`:

``` dart
final class LocalizationConfig {
  // Código de idioma predeterminado (coincide con tu archivo JSON, ej., 'en' para lang/en.json)
  static final String languageCode =
      getEnv('DEFAULT_LOCALE', defaultValue: "en");

  // LocaleType.device - Usar la configuración de idioma del dispositivo
  // LocaleType.asDefined - Usar el languageCode de arriba
  static final LocaleType localeType =
      getEnv('LOCALE_TYPE', defaultValue: 'asDefined') == 'device'
          ? LocaleType.device
          : LocaleType.asDefined;

  // Directorio que contiene los archivos JSON de idioma
  static const String assetsDirectory = 'lang/';

  // Lista de localizaciones soportadas
  static const List<Locale> supportedLocales = [
    Locale('en'),
    Locale('es'),
    // Agregar más localizaciones según sea necesario
  ];

  // Respaldo cuando no se encuentra una clave en la localización activa
  static const String fallbackLanguageCode = 'en';

  // Códigos de idiomas RTL
  static const List<String> rtlLanguages = ['ar', 'he', 'fa', 'ur'];

  // Registrar advertencias por claves de traducción faltantes
  static final bool debugMissingKeys =
      getEnv('DEBUG_TRANSLATIONS', defaultValue: 'false') == 'true';
}
```

<div id="adding-localized-files"></div>

## Agregar archivos localizados

Agrega tus archivos JSON de idioma al directorio `lang/`:

```
lang/
├── en.json   # Inglés
├── es.json   # Español
├── fr.json   # Francés
└── ...
```

**lang/en.json**
``` json
{
  "welcome": "Welcome",
  "settings": "Settings",
  "navigation": {
    "home": "Home",
    "profile": "Profile"
  }
}
```

**lang/es.json**
``` json
{
  "welcome": "Bienvenido",
  "settings": "Configuración",
  "navigation": {
    "home": "Inicio",
    "profile": "Perfil"
  }
}
```

### Registrar en pubspec.yaml

Asegúrate de que tus archivos de idioma estén incluidos en tu `pubspec.yaml`:

``` yaml
flutter:
  assets:
    - lang/
```

<div id="localizing-text"></div>

## Localizar texto

Usa la extensión `.tr()` o el helper `trans()` para traducir cadenas:

``` dart
// Usando la extensión .tr()
"welcome".tr()

// Usando el helper trans()
trans("welcome")
```

### Claves anidadas

Accede a claves JSON anidadas usando notación de puntos:

**lang/en.json**
``` json
{
  "navigation": {
    "home": "Home",
    "profile": "Profile"
  }
}
```

``` dart
"navigation.home".tr()       // "Home"
trans("navigation.profile")  // "Profile"
```

<div id="arguments"></div>

### Argumentos

Pasa valores dinámicos a tus traducciones usando la sintaxis `{{key}}`:

**lang/en.json**
``` json
{
  "greeting": "Hello {{name}}",
  "items_count": "You have {{count}} items"
}
```

``` dart
"greeting".tr(arguments: {"name": "Anthony"})
// "Hello Anthony"

trans("items_count", arguments: {"count": "5"})
// "You have 5 items"
```

<div id="styled-text-placeholders"></div>

### Marcadores de StyledText

Al usar `StyledText.template` con cadenas localizadas, puedes usar la sintaxis `{{key:text}}`. Esto mantiene la **clave** estable en todas las localizaciones (para que tus estilos y manejadores de tap siempre coincidan), mientras que el **texto** se traduce por localización.

**lang/en.json**
``` json
{
  "learn_skills": "Learn {{lang:Languages}}, {{read:Reading}} and {{speak:Speaking}} skills",
  "already_have_account": "Already have an account? {{login:Login}}"
}
```

**lang/es.json**
``` json
{
  "learn_skills": "Aprende {{lang:Idiomas}}, {{read:Lectura}} y {{speak:Habla}}",
  "already_have_account": "¿Ya tienes una cuenta? {{login:Iniciar sesión}}"
}
```

**En tu widget:**
``` dart
StyledText.template(
  "learn_skills".tr(),
  styles: {
    "lang|read|speak": TextStyle(fontWeight: FontWeight.bold),
  },
)
```

Las claves `lang`, `read` y `speak` son las mismas en cada archivo de localización, por lo que el mapa de estilos funciona para todos los idiomas. El texto de visualización después de `:` es lo que ve el usuario — "Languages" en inglés, "Idiomas" en español, etc.

También puedes usar esto con `onTap`:

``` dart
StyledText.template(
  "already_have_account".tr(),
  styles: {
    "login": TextStyle(fontWeight: FontWeight.bold),
  },
  onTap: {
    "login": () => routeTo(LoginPage.path),
  },
)
```

> **Nota:** La sintaxis `{{key}}` (con prefijo `@`) es para argumentos reemplazados por `.tr(arguments:)` en el momento de la traducción. La sintaxis `{{key:text}}` (sin `@`) es para marcadores de `StyledText` que se analizan en el momento de la renderización. No los mezcles — usa `{{}}` para valores dinámicos y `@{{}}` para spans con estilo.

<div id="updating-the-locale"></div>

## Actualizar la localización

Cambia el idioma de la aplicación en tiempo de ejecución:

``` dart
// Usando NyLocalization directamente
await NyLocalization.instance.setLanguage(
  context,
  language: 'es'  // Debe coincidir con el nombre de tu archivo JSON (es.json)
);
```

Si tu widget extiende `NyPage`, usa el helper `changeLanguage`:

``` dart
class _SettingsPageState extends NyPage<SettingsPage> {

  @override
  Widget view(BuildContext context) {
    return ListView(
      children: [
        ListTile(
          title: Text("English"),
          onTap: () => changeLanguage('en'),
        ),
        ListTile(
          title: Text("Español"),
          onTap: () => changeLanguage('es'),
        ),
      ],
    );
  }
}
```

<div id="setting-a-default-locale"></div>

## Establecer una localización predeterminada

Establece el idioma predeterminado en tu archivo `.env`:

``` bash
DEFAULT_LOCALE="en"
```

O usa la localización del dispositivo estableciendo:

``` bash
LOCALE_TYPE="device"
```

Después de cambiar `.env`, regenera tu configuración de entorno:

``` bash
metro make:env
```

<div id="supported-locales"></div>

## Localizaciones soportadas

Define qué localizaciones soporta tu aplicación en `LocalizationConfig`:

``` dart
static const List<Locale> supportedLocales = [
  Locale('en'),
  Locale('es'),
  Locale('fr'),
  Locale('de'),
  Locale('ar'),
];
```

Esta lista es utilizada por `MaterialApp.supportedLocales` de Flutter.

<div id="fallback-language"></div>

## Idioma de respaldo

Cuando no se encuentra una clave de traducción en la localización activa, Nylo la busca automáticamente en el idioma de respaldo antes de devolver la clave sin procesar. El idioma de respaldo se configura en `lib/config/localization.dart`:

``` dart
static const String fallbackLanguageCode = 'en';
```

Esta resolución en dos pasos se aplica tanto a claves de nivel superior como a claves anidadas con notación de puntos:

1. Buscar la clave en el archivo de localización activo.
2. Si no se encuentra, buscarla en el archivo de localización de respaldo.
3. Si aún no se encuentra, devolver la cadena de clave sin procesar.

Por ejemplo, si el archivo de localización francés no tiene la clave `settings.privacy`, la lógica de respaldo busca `settings.privacy` en el archivo de localización inglés antes de devolver `"settings.privacy"` tal cual.

Esto asegura que tu aplicación nunca muestre claves sin procesar si una traducción solo está parcialmente completa.

<div id="rtl-support"></div>

## Soporte RTL

Nylo v7 incluye soporte integrado para idiomas de derecha a izquierda (RTL):

``` dart
static const List<String> rtlLanguages = ['ar', 'he', 'fa', 'ur'];

// Verificar si el idioma actual es RTL
if (LocalizationConfig.isRtl(currentLanguageCode)) {
  // Manejar diseño RTL
}
```

<div id="debug-missing-keys"></div>

## Depurar claves faltantes

Habilita advertencias para claves de traducción faltantes durante el desarrollo:

En tu archivo `.env`:
``` bash
DEBUG_TRANSLATIONS="true"
```

Esto registra advertencias cuando `.tr()` no puede encontrar una clave, ayudándote a detectar cadenas sin traducir.

<div id="nylocalization-api"></div>

## API de NyLocalization

`NyLocalization` es un singleton que gestiona toda la localización. Más allá del método básico `translate()`, proporciona varios métodos adicionales:

### Verificar si existe una traducción

``` dart
bool exists = NyLocalization.instance.hasTranslation('welcome');
// true si la clave existe en el archivo de idioma actual

// También funciona con claves anidadas
bool nestedExists = NyLocalization.instance.hasTranslation('navigation.home');
```

### Obtener todas las claves de traducción

Útil para depuración para ver qué claves están cargadas:

``` dart
List<String> keys = NyLocalization.instance.getAllKeys();
// ['welcome', 'settings', 'navigation', ...]
```

### Cambiar localización sin reiniciar

Si quieres cambiar la localización silenciosamente (sin reiniciar la aplicación):

``` dart
await NyLocalization.instance.setLocale(locale: Locale('fr'));
```

Esto carga el nuevo archivo de idioma pero **no** reinicia la aplicación. Útil cuando quieres manejar las actualizaciones de UI manualmente.

### Verificar dirección RTL

``` dart
bool isRtl = NyLocalization.instance.isDirectionRTL(context);
```

### Acceder a la localización actual

``` dart
// Obtener el código de idioma actual
String code = NyLocalization.instance.languageCode;  // ej., 'en'

// Obtener el objeto Locale actual
Locale currentLocale = NyLocalization.instance.locale;

// Obtener los delegados de localización de Flutter (usado en MaterialApp)
var delegates = NyLocalization.instance.delegates;
```

### Referencia completa de API

| Método / Propiedad | Devuelve | Descripción |
|---------------------|----------|-------------|
| `instance` | `NyLocalization` | Instancia singleton |
| `translate(key, [arguments])` | `String` | Traducir una clave con argumentos opcionales |
| `hasTranslation(key)` | `bool` | Verificar si existe una clave de traducción |
| `getAllKeys()` | `List<String>` | Obtener todas las claves de traducción cargadas |
| `setLanguage(context, {language, restart})` | `Future<void>` | Cambiar idioma, opcionalmente reiniciar |
| `setLocale({locale})` | `Future<void>` | Cambiar localización sin reiniciar |
| `setDebugMissingKeys(enabled)` | `void` | Habilitar/deshabilitar registro de claves faltantes |
| `isDirectionRTL(context)` | `bool` | Verificar si la dirección actual es RTL |
| `restart(context)` | `void` | Reiniciar la aplicación |
| `languageCode` | `String` | Código de idioma actual |
| `locale` | `Locale` | Objeto Locale actual |
| `delegates` | `Iterable<LocalizationsDelegate>` | Delegados de localización de Flutter |
| `setValuesForTesting({values, fallbackValues})` | `void` | Inyectar mapas de traducción directamente para pruebas unitarias |

<div id="nylocalehelper"></div>

## NyLocaleHelper

`NyLocaleHelper` es una clase utilitaria estática para operaciones de localización. Proporciona métodos para detectar la localización actual, verificar soporte RTL y crear objetos Locale.

``` dart
// Obtener la localización actual del sistema
Locale locale = NyLocaleHelper.getCurrentLocale(context: context);

// Obtener códigos de idioma y país
String langCode = NyLocaleHelper.getLanguageCode(context: context);  // 'en'
String? countryCode = NyLocaleHelper.getCountryCode(context: context);  // 'US' o null

// Verificar si la localización actual coincide
bool isEnglish = NyLocaleHelper.matchesLocale(context, 'en');
bool isUsEnglish = NyLocaleHelper.matchesLocale(context, 'en', 'US');

// Detección RTL
bool isRtl = NyLocaleHelper.isRtlLanguage('ar');  // true
bool currentIsRtl = NyLocaleHelper.isCurrentLocaleRtl(context: context);

// Obtener dirección de texto
TextDirection direction = NyLocaleHelper.getTextDirection('ar');  // TextDirection.rtl
TextDirection currentDir = NyLocaleHelper.getCurrentTextDirection(context: context);

// Crear un Locale desde cadenas
Locale newLocale = NyLocaleHelper.toLocale('en', 'US');
```

### Referencia completa de API

| Método | Devuelve | Descripción |
|--------|----------|-------------|
| `getCurrentLocale({context})` | `Locale` | Obtener la localización actual del sistema |
| `getLanguageCode({context})` | `String` | Obtener el código de idioma actual |
| `getCountryCode({context})` | `String?` | Obtener el código de país actual |
| `matchesLocale(context, languageCode, [countryCode])` | `bool` | Verificar si la localización actual coincide |
| `isRtlLanguage(languageCode)` | `bool` | Verificar si un código de idioma es RTL |
| `isCurrentLocaleRtl({context})` | `bool` | Verificar si la localización actual es RTL |
| `getTextDirection(languageCode)` | `TextDirection` | Obtener TextDirection para un idioma |
| `getCurrentTextDirection({context})` | `TextDirection` | Obtener TextDirection para la localización actual |
| `toLocale(languageCode, [countryCode])` | `Locale` | Crear un Locale desde cadenas |

La constante `rtlLanguages` contiene: `ar`, `he`, `fa`, `ur`, `yi`, `ps`, `ku`, `sd`, `dv`.

<div id="changing-language-from-controller"></div>

## Cambiar idioma desde un controlador

Si usas controladores con tus páginas, puedes cambiar el idioma desde `NyController`:

``` dart
class SettingsController extends NyController {

  void switchToSpanish() {
    changeLanguage('es');
  }

  void switchToEnglishNoRestart() {
    changeLanguage('en', restartState: false);
  }
}
```

El parámetro `restartState` controla si la aplicación se reinicia después de cambiar el idioma. Establécelo en `false` si quieres manejar la actualización de la UI tú mismo.
