9.8 KiB
nu_plugin_fluent
A Nushell plugin for Fluent 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 alongside this plugin or change dependencies in Cargo.toml
This plugin is also included as submodule in nushell-plugins as part of plugins collection for Provisioning project
Build from source
> cd nu_plugin_fluent
> cargo install --path .
Nushell
In a Nushell
> plugin add ~/.cargo/bin/nu_plugin_fluent
Commands
fluent-parse
Parse a Fluent Translation List (.ftl
) file and extract its message structure.
> fluent-parse <file>
Parameters:
- file
<path>
: FTL file to parse
Example:
> 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.
> 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:
> fluent-localize welcome-message en-US --files [locales/en-US/main.ftl]
"Welcome to our application!"
With arguments:
> fluent-localize user-greeting en-US --files [locales/en-US/main.ftl] --args {name: "Alice"}
"Hello, Alice! Welcome back."
With fallback:
> 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.
> fluent-validate <file>
Parameters:
- file
<path>
: FTL file to validate
Example:
> 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.
> fluent-extract <file>
Parameters:
- file
<path>
: FTL file to extract messages from
Example:
> 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.
> fluent-list-locales <directory>
Parameters:
- directory
<path>
: Directory containing locale folders
Example:
> 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.
> 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:
> fluent-create-bundle en-US --global [global/en-US/common.ftl] --page [pages/blog/en-US/blog.ftl]
Create bundle with fallback support:
> fluent-create-bundle es-ES --fallback [en-US] --global [global/es-ES/common.ftl]
Workflow Examples
Complete Localization Workflow
- 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
- List available locales:
> fluent-list-locales ./locales
- Validate all locale files:
> ls locales/**/*.ftl | each { |file| fluent-validate $file.name }
- Extract all message IDs for translation coverage:
> ls locales/en-US/*.ftl | each { |file|
fluent-extract $file.name | wrap messages | insert file $file.name
} | flatten
- Create a localization function:
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
}
- Use the localization function:
> localize "welcome-message" "es-ES"
"¡Bienvenido a nuestra aplicación!"
Quality Assurance Workflow
Check for missing translations across locales:
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
# 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:
# 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 - Mozilla's localization system
- Nushell - A new type of shell
- nu_plugin_tera - Tera templating plugin for Nushell