# Internationalization (i18n)

Castella provides a built-in internationalization system for multi-language support with runtime locale switching.

## Overview

The i18n system consists of:

- **I18nManager** - Singleton manager for translations and locale switching
- **LocaleString** - Reactive string that auto-updates UI when locale changes
- **t() / tn()** - Convenience functions for translation
- **Loaders** - Load translations from YAML or JSON files

## Basic Usage

```python
from castella.i18n import I18nManager, load_yaml_catalog

# Get the singleton manager
manager = I18nManager()

# Load translation catalogs
manager.load_catalog("en", load_yaml_catalog("locales/en.yaml"))
manager.load_catalog("ja", load_yaml_catalog("locales/ja.yaml"))

# Set the active locale
manager.set_locale("en")

# Translate a key
text = manager.t("greeting")  # "Hello!"

# With string interpolation
text = manager.t("welcome", name="World")  # "Hello, World!"
```

### I18nManager Methods

| Method | Description |
|--------|-------------|
| `load_catalog(locale, catalog)` | Load a translation catalog for a locale |
| `set_locale(locale)` | Set the active locale |
| `set_fallback_locale(locale)` | Set fallback locale for missing translations |
| `t(key, **kwargs)` | Translate a key with optional interpolation |
| `tn(key, count, **kwargs)` | Translate with pluralization |
| `locale` | Property: current locale |
| `available_locales` | Property: list of loaded locales |

## Loading Translations

### From YAML Files

```python
from castella.i18n import load_yaml_catalog

catalog = load_yaml_catalog("locales/en.yaml")
manager.load_catalog("en", catalog)
```

### From JSON Files

```python
from castella.i18n import load_json_catalog

catalog = load_json_catalog("locales/en.json")
manager.load_catalog("en", catalog)
```

### From a Directory

```python
from castella.i18n import load_catalogs_from_directory

# Load all .yaml and .json files from a directory
catalogs = load_catalogs_from_directory("locales/")
for locale, catalog in catalogs.items():
    manager.load_catalog(locale, catalog)
```

### Programmatically

```python
from castella.i18n import create_catalog_from_dict

catalog = create_catalog_from_dict("en", {
    "greeting": "Hello!",
    "button": {
        "save": "Save",
        "cancel": "Cancel",
    },
})
manager.load_catalog("en", catalog)
```

## LocaleString (Reactive)

`LocaleString` automatically updates the UI when the locale changes. Use it with widgets for reactive translations.

```python
from castella import Text
from castella.i18n import LocaleString

# UI automatically updates when locale changes
Text(LocaleString("dashboard.title"))

# With interpolation
Text(LocaleString("greeting", name="World"))
```

### How It Works

1. `LocaleString` observes the `I18nManager` for locale changes
2. When locale changes, `LocaleString` notifies attached widgets
3. Widgets re-render with the new translation

### Comparison: t() vs LocaleString

| Use Case | Function |
|----------|----------|
| Static translation (one-time) | `t("key")` |
| Reactive translation (auto-update UI) | `LocaleString("key")` |

```python
# Static - won't update when locale changes
label = t("greeting")

# Reactive - updates automatically
Text(LocaleString("greeting"))
```

## Pluralization

Handle pluralized strings that vary based on count.

### Using tn()

```python
from castella.i18n import tn

# Returns "1 item" or "5 items" based on count and locale
text = tn("items", count=1)  # "1 item"
text = tn("items", count=5)  # "5 items"
```

### Using LocalePluralString

```python
from castella import Text
from castella.i18n import LocalePluralString

# Reactive pluralized string
Text(LocalePluralString("items", count=5))
```

### Defining Plural Forms in YAML

```yaml
# locales/en.yaml
locale: en
items:
  one: "{count} item"
  other: "{count} items"
```

```yaml
# locales/ja.yaml
locale: ja
items:
  other: "{count}個のアイテム"  # Japanese doesn't distinguish singular/plural
```

### Supported Plural Categories

Castella follows CLDR plural rules:

| Category | Description | Example Languages |
|----------|-------------|-------------------|
| `zero` | Zero quantity | Arabic |
| `one` | Singular | English, German, French |
| `two` | Dual | Arabic, Welsh |
| `few` | Few | Russian, Polish |
| `many` | Many | Russian, Polish |
| `other` | Default/Plural | All languages |

Built-in rules for: English, Japanese, Chinese, French, German, Russian.

## Translation File Format

### YAML Format

```yaml
# locales/en.yaml
locale: en

# Simple strings
greeting: "Hello!"
farewell: "Goodbye!"

# Nested keys (accessed as "button.save")
button:
  save: "Save"
  cancel: "Cancel"
  delete: "Delete"

# String interpolation
welcome: "Welcome, {name}!"

# Plural forms
items:
  one: "{count} item"
  other: "{count} items"

notifications:
  one: "You have {count} notification"
  other: "You have {count} notifications"
```

### JSON Format

```json
{
  "locale": "en",
  "greeting": "Hello!",
  "button": {
    "save": "Save",
    "cancel": "Cancel"
  },
  "welcome": "Welcome, {name}!",
  "items": {
    "one": "{count} item",
    "other": "{count} items"
  }
}
```

### Accessing Nested Keys

Use dot notation to access nested translations:

```python
manager.t("button.save")        # "Save"
manager.t("button.cancel")      # "Cancel"
```

## System Locale Detection

`I18nManager` can auto-detect the system locale:

1. `CASTELLA_LOCALE` environment variable (highest priority)
2. System locale (`LC_ALL`, `LANG`)
3. Default: `"en"`

```bash
# Force Japanese locale
export CASTELLA_LOCALE=ja
```

## Complete Example

```python
from castella import App, Button, Column, Component, Row, Text
from castella.frame import Frame
from castella.i18n import I18nManager, LocaleString, load_yaml_catalog


class I18nDemo(Component):
    def __init__(self):
        super().__init__()
        self._manager = I18nManager()

        # Load translations
        self._manager.load_catalog("en", load_yaml_catalog("locales/en.yaml"))
        self._manager.load_catalog("ja", load_yaml_catalog("locales/ja.yaml"))
        self._manager.set_locale("en")

    def view(self):
        return Column(
            # Reactive translation - auto-updates on locale change
            Text(LocaleString("greeting")),
            Text(LocaleString("welcome", name="Castella")),

            Row(
                Button(LocaleString("button.save")).on_click(self._save),
                Button(LocaleString("button.cancel")),
            ),

            # Language switcher
            Row(
                Button("English").on_click(lambda _: self._manager.set_locale("en")),
                Button("日本語").on_click(lambda _: self._manager.set_locale("ja")),
            ),
        )

    def _save(self, _):
        print("Saved!")


App(Frame("I18n Demo", 500, 300), I18nDemo()).run()
```

**locales/en.yaml:**
```yaml
locale: en
greeting: "Hello!"
welcome: "Welcome, {name}!"
button:
  save: "Save"
  cancel: "Cancel"
```

**locales/ja.yaml:**
```yaml
locale: ja
greeting: "こんにちは！"
welcome: "ようこそ、{name}さん！"
button:
  save: "保存"
  cancel: "キャンセル"
```
