# nu_plugin_fluent

A [Nushell](https://nushell.sh/) plugin for [Fluent](https://projectfluent.org/) internationalization (i18n) and localization (l10n) workflows.

## Overview

This plugin provides powerful tools for managing multilingual applications using Mozilla's Fluent localization system. It enables you to parse, validate, localize, and manage Fluent Translation List (`.ftl`) files directly from Nushell.

## Installing

Clone this repository

> [!WARNING]
> **nu_plugin_fluent** has dependencies to nushell source via local path in Cargo.toml
> Nushell and plugins require to be **sync** with same **version**

Clone [Nushell](https://nushell.sh/) alongside this plugin or change dependencies in [Cargo.toml](Cargo.toml)

This plugin is also included as submodule in [nushell-plugins](https://repo.jesusperez.pro/jesus/nushell-plugins)
as part of plugins collection for [Provisioning project](https://rlung.librecloud.online/jesus/provisioning)

Build from source

```nushell
> cd nu_plugin_fluent
> cargo install --path .
```

### Nushell

In a [Nushell](https://nushell.sh/)

```nushell
> plugin add ~/.cargo/bin/nu_plugin_fluent
```

## Commands

### `fluent-parse`

Parse a Fluent Translation List (`.ftl`) file and extract its message structure.

```nushell
> fluent-parse <file>
```

**Parameters:**
- **file** `<path>`: FTL file to parse

**Example:**
```nushell
> fluent-parse locales/en-US/main.ftl
╭───────────────┬─────────────────────────────╮
│ file          │ locales/en-US/main.ftl      │
│ message_count │ 3                           │
│ messages      │ [list of parsed messages]   │
╰───────────────┴─────────────────────────────╯
```

### `fluent-localize`

Localize a message using the Fluent translation system with hierarchical fallback support.

```nushell
> fluent-localize <message_id> <locale> [--files] [--bundle] [--args] [--fallback]
```

**Parameters:**
- **message_id** `<string>`: Message ID to localize
- **locale** `<string>`: Locale code (e.g., en-US, es-ES)

**Flags:**
- **--files** `-f` `<list>`: FTL files to load
- **--bundle** `-b` `<record>`: Pre-loaded message bundle
- **--args** `-a` `<record>`: Arguments for message interpolation
- **--fallback** `-F`: Return message ID if translation not found

**Examples:**

Basic localization:
```nushell
> fluent-localize welcome-message en-US --files [locales/en-US/main.ftl]
"Welcome to our application!"
```

With arguments:
```nushell
> fluent-localize user-greeting en-US --files [locales/en-US/main.ftl] --args {name: "Alice"}
"Hello, Alice! Welcome back."
```

With fallback:
```nushell
> fluent-localize missing-message es-ES --files [locales/es-ES/main.ftl] --fallback
"[[missing-message]]"
```

### `fluent-validate`

Validate the syntax of a Fluent Translation List (`.ftl`) file.

```nushell
> fluent-validate <file>
```

**Parameters:**
- **file** `<path>`: FTL file to validate

**Example:**
```nushell
> fluent-validate locales/en-US/main.ftl
╭────────┬─────────────────────────╮
│ valid  │ true                    │
│ file   │ locales/en-US/main.ftl  │
│ errors │ []                      │
╰────────┴─────────────────────────╯
```

### `fluent-extract`

Extract message IDs from a Fluent Translation List (`.ftl`) file.

```nushell
> fluent-extract <file>
```

**Parameters:**
- **file** `<path>`: FTL file to extract messages from

**Example:**
```nushell
> fluent-extract locales/en-US/main.ftl
╭───┬─────────────────╮
│ 0 │ welcome-message │
│ 1 │ user-greeting   │
│ 2 │ goodbye-message │
╰───┴─────────────────╯
```

### `fluent-list-locales`

List available locales from a directory structure.

```nushell
> fluent-list-locales <directory>
```

**Parameters:**
- **directory** `<path>`: Directory containing locale folders

**Example:**
```nushell
> fluent-list-locales ./locales
╭───┬───────╮
│ 0 │ en-US │
│ 1 │ es-ES │
│ 2 │ fr-FR │
│ 3 │ de-DE │
╰───┴───────╯
```

### `fluent-create-bundle`

Create a merged Fluent bundle from global and page-specific locale files.

```nushell
> fluent-create-bundle <locale> [--global] [--page] [--fallback] [--override]
```

**Parameters:**
- **locale** `<string>`: Locale code (e.g., en-US)

**Flags:**
- **--global** `-g` `<list>`: Global FTL files to include
- **--page** `-p` `<list>`: Page-specific FTL files to include
- **--fallback** `-f` `<list>`: Fallback locales in order
- **--override** `-o`: Allow page files to override global messages

**Examples:**

Create bundle with global and page-specific files:
```nushell
> fluent-create-bundle en-US --global [global/en-US/common.ftl] --page [pages/blog/en-US/blog.ftl]
```

Create bundle with fallback support:
```nushell
> fluent-create-bundle es-ES --fallback [en-US] --global [global/es-ES/common.ftl]
```

## Workflow Examples

### Complete Localization Workflow

1. **Set up your locale directory structure:**
```
locales/
├── en-US/
│   ├── common.ftl
│   └── pages.ftl
├── es-ES/
│   ├── common.ftl
│   └── pages.ftl
└── fr-FR/
    ├── common.ftl
    └── pages.ftl
```

2. **List available locales:**
```nushell
> fluent-list-locales ./locales
```

3. **Validate all locale files:**
```nushell
> ls locales/**/*.ftl | each { |file| fluent-validate $file.name }
```

4. **Extract all message IDs for translation coverage:**
```nushell
> ls locales/en-US/*.ftl | each { |file|
    fluent-extract $file.name | wrap messages | insert file $file.name
} | flatten
```

5. **Create a localization function:**
```nushell
def localize [message_id: string, locale: string = "en-US"] {
    let files = (ls $"locales/($locale)/*.ftl" | get name)
    fluent-localize $message_id $locale --files $files --fallback
}
```

6. **Use the localization function:**
```nushell
> localize "welcome-message" "es-ES"
"¡Bienvenido a nuestra aplicación!"
```

### Quality Assurance Workflow

Check for missing translations across locales:

```nushell
def check-translation-coverage [] {
    let base_locale = "en-US"
    let base_messages = (ls $"locales/($base_locale)/*.ftl"
        | each { |file| fluent-extract $file.name }
        | flatten | uniq)

    ls locales/*/
    | get name
    | path basename
    | where $it != $base_locale
    | each { |locale|
        let locale_messages = (ls $"locales/($locale)/*.ftl"
            | each { |file| fluent-extract $file.name }
            | flatten | uniq)

        let missing = ($base_messages | where $it not-in $locale_messages)
        {locale: $locale, missing_count: ($missing | length), missing: $missing}
    }
}

> check-translation-coverage
```

## Fluent File Format

Example `.ftl` file structure:

**locales/en-US/common.ftl**
```fluent
# Simple message
welcome-message = Welcome to our application!

# Message with variables
user-greeting = Hello, { $name }! Welcome back.

# Message with attributes
login-button =
    .label = Sign In
    .aria-label = Sign in to your account
    .tooltip = Click here to access your account

# Message with variants
unread-emails = You have { $count ->
    [one] one unread email
   *[other] { $count } unread emails
}.

# Message with functions
last-login = Last login: { DATETIME($date, month: "long", day: "numeric") }
```

## Features

- ✅ **Parse FTL files** - Extract and analyze message structure
- ✅ **Validate syntax** - Ensure FTL files are syntactically correct
- ✅ **Localize messages** - Translate messages with variable interpolation
- ✅ **Fallback support** - Hierarchical locale fallback system
- ✅ **Bundle management** - Merge global and page-specific translations
- ✅ **Message extraction** - List all message IDs for coverage analysis
- ✅ **Locale discovery** - Auto-detect available locales
- ✅ **Quality assurance** - Tools for translation completeness checking

## Use Cases

- **Web Applications**: Manage frontend translations with dynamic content
- **CLI Tools**: Internationalize command-line applications
- **Documentation**: Maintain multilingual documentation systems
- **Content Management**: Handle localized content workflows
- **Quality Assurance**: Automate translation coverage and validation
- **Build Systems**: Integrate i18n validation into CI/CD pipelines

## Integration with Nushell

This plugin leverages Nushell's powerful data processing capabilities:

```nushell
# Batch validate all locale files
ls locales/**/*.ftl
| par-each { |file| fluent-validate $file.name }
| where valid == false

# Generate translation progress report
def translation-report [] {
    fluent-list-locales ./locales
    | each { |locale|
        let files = (ls $"locales/($locale)/*.ftl" | get name)
        let message_count = ($files | each { |f| fluent-extract $f } | flatten | length)
        {locale: $locale, messages: $message_count}
    }
}

# Find untranslated messages
def find-missing-translations [base_locale: string, target_locale: string] {
    let base_msgs = (ls $"locales/($base_locale)/*.ftl" | each { |f| fluent-extract $f.name } | flatten)
    let target_msgs = (ls $"locales/($target_locale)/*.ftl" | each { |f| fluent-extract $f.name } | flatten)
    $base_msgs | where $it not-in $target_msgs
}
```

## Contributing

Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.

## License

This project is licensed under the MIT License.

## Related Projects

- [Fluent](https://projectfluent.org/) - Mozilla's localization system
- [Nushell](https://nushell.sh/) - A new type of shell
- [nu_plugin_tera](../nu_plugin_tera/) - Tera templating plugin for Nushell