Project Fluent (fluent-rs)

repository·main·Indexed 23 days ago

https://github.com/projectfluent/fluent-rs

A Rust implementation of the Project Fluent localization system designed to handle complex natural language features like plurals, gender, and conjugations. The ecosystem includes the core fluent crate, fluent-bundle for low-level message management, fluent-fallback for language fallback logic, fluent-resmgr for resource file management, fluent-pseudo for pseudolocalization, and fluent-syntax for parsing and serializing FTL syntax.

Tokens
25.7K
Snippets
44
Records
104
Agent score
79%

What's inside fluent-rs

  1. Overview of Project Fluent (fluent-rs)

    main
    Project Fluent (fluent-rs) is a collection of Rust crates that implement Project Fluent, a localization system. It is designed to handle the expressive power of natural languages, allowing for simple translations while supporting complex linguistic concepts like gender, plurals, and conjugations through an easy-to-read syntax.
  2. Use fluent-syntax for parsing and serializing Fluent syntax

    main
    The fluent-syntax crate provides low-level APIs for working with the Fluent syntax. It is designed for developers who need to perform parsing, manipulate the Abstract Syntax Tree (AST), or serialize Fluent syntax structures. It serves as the foundational layer for other Fluent crates in the fluent-rs workspace.
  3. What is FTL (Fluent Translation List) syntax?

    main

    FTL is the localization file format used by Project Fluent. It is designed to be human-readable while supporting complex natural language features such as gender, plurals, and conjugations.

    Example of FTL syntax with a variable:

    hello-user = Hello, { $username }!
  4. What is the Localization struct?

    main
    The Localization struct is a high-level implementation that encapsulates a collection of locale bundles and manages fallback logic between different locales. It acts as a state manager that can be used directly in an application or as a reference for managing fluent-bundle and fluent-resmgr states. It is intended to remain available throughout the lifecycle of a UI, handling events like locale changes and resource updates.
  5. How to use IntlMemoizer for lazy-initialized formatters

    main

    The intl-memoizer crate is designed to manage the memoization of expensive, read-only internationalization (intl) formatters like PluralRules or DateTimeFormat.

    Mental Model

    1. Cost Assumption: Creating a new formatter instance is assumed to be expensive, while calling its format or select methods is cheap.
    2. Hierarchy:
      • IntlMemoizer: A main memoizer that holds weak references to per-language memoizers. It acts as a singleton to manage instances across all FluentBundle instances.
      • IntlLangMemoizer: A per-locale memoizer that manages the actual instances for a specific language.
    3. Workflow:
      • Implement the Memoizable trait for your formatter type.
      • Use IntlMemoizer::default() to create the main memoizer.
      • Use get_for_lang(lang) to retrieve an Rc<IntlLangMemoizer> for a specific locale.
      • Use with_try_get::<FormatterType, _, _>(options, closure) to lazily construct and run the formatter. Subsequent calls with the same options will reuse the existing instance.
    /// Internationalization formatter should implement the Memoizable trait.
    impl Memoizable for NumberFormat {
      ...
    }
    
    // The main memoizer has weak references to all of the per-language memoizers.
    let mut memoizer = IntlMemoizer::default();
    
    // The formatter memoization happens per-locale.
    let lang = "en-US".parse().expect("Failed to parse.");
    let lang_memoizer: Rc<IntlLangMemoizer> = memoizer.get_for_lang(lang);
    
    // Run the formatter
    let options: NumberFormatOptions = NumberFormatOptions {
        minimum_fraction_digits: 3,
        maximum_fraction_digits: 5,
    };
    
    // Format pi with the options. This will lazily construct the NumberFormat.
    let pi = lang_memoizer
        .with_try_get::<NumberFormat, _, _>((options,), |nf| nf.format(3.141592653))
        .unwrap();
    
    // Running it again with the same options will use the previous formatter.
    let two = lang_memoizer
        .with_try_get::<NumberFormat, _, _>((options,), |nf| nf.format(2.0))
        .unwrap();
  6. Core Concepts: FluentBundle and FluentResource

    main

    The fluent crate provides a low-level API for localization:

    • FluentResource: Represents a parsed FTL (Fluent Translation List) resource. It is created using FluentResource::try_new(ftl_string).
    • FluentBundle: The main container for localization. It holds one or more FluentResource objects and is associated with specific language IDs (using unic_langid). It is responsible for retrieving messages and formatting patterns into final strings.

    Note: While FluentBundle is the primary struct, higher-level ergonomic APIs like fluent-resmgr and fluent-fallback are being developed to wrap this low-level interface.

  7. Choose the right Fluent crate for your project

    main

    The fluent-rs workspace is divided into several specialized crates. Depending on your needs, you should choose the appropriate package:

    • fluent: An umbrella crate that exposes the combined features of all fluent-rs crates with additional convenience macros. Best for most general-purpose users.
    • fluent-bundle: A low-level implementation for managing a collection of localization messages for a single locale.
    • fluent-fallback: A high-level abstraction for managing locale bundles and the runtime localization lifecycle.
    • fluent-resmgr: A standalone solution for managing resource files and retrieving locale bundles.
    • fluent-syntax: A low-level API for parsing, AST manipulation, and serializing Fluent syntax.
    • fluent-pseudo: Provides pseudolocalization and transformation APIs.
    • fluent-testing: Provides mock scenarios for testing components built with fluent-rs.
    • intl-memoizer: A specialized memoizer for storing lazy-initialized internationalization (intl) formatters.
    • fluent-cli: A collection of command-line tools for developers working with Fluent.
  8. Quickstart: Using Fluent in Rust

    main

    To use Fluent in your Rust project, you need to create a FluentResource from an FTL string, initialize a FluentBundle with a supported language ID, and add the resource to that bundle. You can then retrieve messages by their ID and format them using format_pattern.

    use fluent::{FluentBundle, FluentResource};
    use unic_langid::langid;
    
    fn main() {
        let ftl_string = "hello-world = Hello, world!".to_owned();
        let res = FluentResource::try_new(ftl_string)
            .expect("Failed to parse an FTL string.");
    
        let langid_en = langid!("en-US");
        let mut bundle = FluentBundle::new(vec![langid_en]);
    
        bundle.add_resource(&res)
            .expect("Failed to add FTL resources to the bundle.");
    
        let msg = bundle.get_message("hello-world")
            .expect("Message doesn't exist.");
        let mut errors = vec![];
        let pattern = msg.value
            .expect("Message has no value.");
        let value = bundle.format_pattern(&pattern, None, &mut errors);
    
        assert_eq!(&value, "Hello, world!");
    }
  9. Use the Fluent Resource Manager to manage locale bundles

    main

    The fluent-resmgr crate provides a standalone solution for managing Fluent resource files and retrieving locale bundles. You can initialize a ResourceManager with a path template that defines how to locate resource files based on locale and resource ID, then use it to fetch bundles for formatting values.

    Note: The path template in the example uses placeholders like {locale} and {res_id} to map to the file system structure.

    use fluent_resmgr::resource_manager::ResourceManager;
    
    fn main() {
        // Initialize the manager with a path template
        let mgr = ResourceManager::new("./examples/resources/{locale}/{res_id}".into());
    
        // Retrieve a bundle for specific locales and resources
        let bundle = mgr.get_bundle(locales, resources);
    
        // Format a value using the bundle
        let value = bundle.format_value("hello-world", None);
    
        assert_eq!(&value, "Hello, world!");
    }
  10. Test fluent-rs locally against the Firefox codebase

    main

    To test fluent-rs changes against a local Firefox source tree, follow these steps to replace the vendored Firefox packages with your local fluent-rs development versions:

    1. Clone Firefox: Bootstrap a copy of the Firefox source code without Artifact Builds (required to modify Rust dependencies).
    2. Remove Vendored Packages: Delete the following packages from the mozilla-unified/third_party/rust directory:
      • fluent
      • fluent-bundle
      • fluent-fallback
      • fluent-pseudo
      • fluent-syntax
      • fluent-testing
      • intl-memoizer
    3. Bump Versions: Use cargo-release to bump the version of all fluent-rs packages to avoid version mismatch errors in Firefox.
    4. Update Firefox Dependencies: Update the Cargo.toml files in the Firefox codebase to point to your local fluent-rs directory using the { path = "..." } syntax.
    5. Verify: Run ./mach vendor rust at the root of the mozilla-unified directory to ensure the dependencies are correctly integrated.