{"id":14986779,"url":"https://github.com/xampprocky/fluent-templates","last_synced_at":"2026-02-17T02:04:55.051Z","repository":{"id":39860269,"uuid":"270637874","full_name":"XAMPPRocky/fluent-templates","owner":"XAMPPRocky","description":"Easily add Fluent to your Rust project.","archived":false,"fork":false,"pushed_at":"2025-01-12T18:24:31.000Z","size":236,"stargazers_count":140,"open_issues_count":11,"forks_count":31,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-14T12:19:03.314Z","etag":null,"topics":["fluent","fluent-templates","handlebars","rust","tera"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/XAMPPRocky.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"XAMPPRocky"}},"created_at":"2020-06-08T11:12:03.000Z","updated_at":"2025-03-12T19:23:08.000Z","dependencies_parsed_at":"2024-02-19T19:26:26.797Z","dependency_job_id":"95eae238-21c6-47da-9139-f43a6b20cd61","html_url":"https://github.com/XAMPPRocky/fluent-templates","commit_stats":{"total_commits":193,"total_committers":27,"mean_commits":7.148148148148148,"dds":0.5233160621761659,"last_synced_commit":"f1f70a6e817c7c5fc3bf72d83202b63573fd7a58"},"previous_names":[],"tags_count":87,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/XAMPPRocky%2Ffluent-templates","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/XAMPPRocky%2Ffluent-templates/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/XAMPPRocky%2Ffluent-templates/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/XAMPPRocky%2Ffluent-templates/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/XAMPPRocky","download_url":"https://codeload.github.com/XAMPPRocky/fluent-templates/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248877968,"owners_count":21176244,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fluent","fluent-templates","handlebars","rust","tera"],"created_at":"2024-09-24T14:13:31.714Z","updated_at":"2026-02-17T02:04:55.045Z","avatar_url":"https://github.com/XAMPPRocky.png","language":"Rust","funding_links":["https://github.com/sponsors/XAMPPRocky"],"categories":[],"sub_categories":[],"readme":"# Fluent Templates: A High level Fluent API.\n\n![Build \u0026 Test](https://github.com/XAMPPRocky/fluent-templates/workflows/Build%20\u0026%20Test/badge.svg)\n[![crates.io](https://img.shields.io/crates/d/fluent-templates.svg)](https://crates.io/crates/fluent-templates)\n[![Help Wanted](https://img.shields.io/github/issues/XAMPPRocky/fluent-templates/help%20wanted?color=green)](https://github.com/XAMPPRocky/fluent-templates/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)\n[![Lines Of Code](https://tokei.rs/b1/github/XAMPPRocky/fluent-templates?category=code)](https://github.com/XAMPPRocky/tokei)\n[![Documentation](https://docs.rs/fluent-templates/badge.svg)](https://docs.rs/fluent-templates/)\n\n`fluent-templates` lets you to easily integrate [Fluent] localisation into\nyour Rust application or library. It does this by providing a high level\n\"loader\" API that loads fluent strings based on simple language negotiation,\nand the `FluentLoader` struct which is a `Loader` agnostic container type\nthat comes with optional trait implementations for popular templating\nengines such as handlebars or tera that allow you to be able to use your\nlocalisations in your templates with no boilerplate.\n\n## Loaders\nCurrently this crate provides two different kinds of loaders that cover two\nmain use cases.\n\n- [`static_loader!`] — A procedural macro that loads your fluent resources\n  at *compile-time* into your binary and creates a new [`StaticLoader`]\n  static variable that allows you to access the localisations.\n  `static_loader!` is most useful when you want to localise your\n  application and want to ship your fluent resources with your binary.\n\n- [`ArcLoader`] — A struct that loads your fluent resources at *run-time*\n  using `Arc` as its backing storage. `ArcLoader` is most useful for when\n  you want to be able to change and/or update localisations at run-time, or\n  if you're writing a developer tool that wants to provide fluent\n  localisation in your own application such as a static site generator.\n\n\n## `static_loader!`\nThe easiest way to use `fluent-templates` is to use the [`static_loader!`]\nprocedural macro that will create a new [`StaticLoader`] static variable.\n\n### Basic Example\n```rust\nfluent_templates::static_loader! {\n    // Declare our `StaticLoader` named `LOCALES`.\n    static LOCALES = {\n        // The directory of localisations and fluent resources.\n        locales: \"./tests/locales\",\n        // The language to falback on if something is not present.\n        fallback_language: \"en-US\",\n        // Optional: A fluent resource that is shared with every locale.\n        core_locales: \"./tests/locales/core.ftl\",\n    };\n}\n```\n\n### Customise Example\nYou can also modify each `FluentBundle` on initialisation to be able to\nchange configuration or add resources from Rust.\n```rust\nuse std::sync::LazyLock;\nuse fluent_bundle::FluentResource;\nuse fluent_templates::static_loader;\n\nstatic_loader! {\n    // Declare our `StaticLoader` named `LOCALES`.\n    static LOCALES = {\n        // The directory of localisations and fluent resources.\n        locales: \"./tests/locales\",\n        // The language to falback on if something is not present.\n        fallback_language: \"en-US\",\n        // Optional: A fluent resource that is shared with every locale.\n        core_locales: \"./tests/locales/core.ftl\",\n        // Optional: A function that is run over each fluent bundle.\n        customise: |bundle| {\n            // Since this will be called for each locale bundle and\n            // `FluentResource`s need to be either `\u0026'static` or behind an\n            // `Arc` it's recommended you use lazily initialised\n            // static variables.\n            static CRATE_VERSION_FTL: LazyLock\u003cFluentResource\u003e = LazyLock::new(|| {\n                let ftl_string = String::from(\n                    concat!(\"-crate-version = {}\", env!(\"CARGO_PKG_VERSION\"))\n                );\n\n                FluentResource::try_new(ftl_string).unwrap()\n            });\n\n            bundle.add_resource(\u0026CRATE_VERSION_FTL);\n        }\n    };\n}\n```\n\n## Locales Directory\n`fluent-templates` will collect all subdirectories that match a valid\n[Unicode Language Identifier][uli] and bundle all fluent files found in\nthose directories and map those resources to the respective identifier.\nIt also supports locale files named `locales/\u003clang\u003e.ftl`, which are merged\nwith any resources found in `locales/\u003clang\u003e/`.\n`fluent-templates` will recurse through each language directory as needed\nand will respect any `.gitignore` or `.ignore` files present.\n\n[uli]: https://docs.rs/unic-langid/0.9.0/unic_langid/\n\n### Example Layout\n```text\nlocales\n├── core.ftl\n├── en-US.ftl\n├── en-US\n│   └── extra.ftl\n├── fr\n│   └── main.ftl\n├── zh-CN\n│   └── main.ftl\n└── zh-TW\n    └── main.ftl\n```\n\n### Looking up fluent resources\nYou can use the [`Loader`] trait to `lookup` a given fluent resource, and\nprovide any additional arguments as needed with `lookup_with_args`.\n\n#### Example\n```fluent\n # In `locales/en-US/main.ftl`\n hello-world = Hello World!\n greeting = Hello { $name }!\n\n # In `locales/fr/main.ftl`\n hello-world = Bonjour le monde!\n greeting = Bonjour { $name }!\n\n # In `locales/de/main.ftl`\n hello-world = Hallo Welt!\n greeting = Hallo { $name }!\n```\n\n```rust\nuse std::collections::HashMap;\n\nuse unic_langid::{LanguageIdentifier, langid};\nuse fluent_templates::{Loader, static_loader};\n\nconst US_ENGLISH: LanguageIdentifier = langid!(\"en-US\");\nconst FRENCH: LanguageIdentifier = langid!(\"fr\");\nconst GERMAN: LanguageIdentifier = langid!(\"de\");\n\nstatic_loader! {\n    static LOCALES = {\n        locales: \"./tests/locales\",\n        fallback_language: \"en-US\",\n        // Removes unicode isolating marks around arguments, you typically\n        // should only set to false when testing.\n        customise: |bundle| bundle.set_use_isolating(false),\n    };\n}\n\nfn main() {\n    assert_eq!(\"Hello World!\", LOCALES.lookup(\u0026US_ENGLISH, \"hello-world\"));\n    assert_eq!(\"Bonjour le monde!\", LOCALES.lookup(\u0026FRENCH, \"hello-world\"));\n    assert_eq!(\"Hallo Welt!\", LOCALES.lookup(\u0026GERMAN, \"hello-world\"));\n\n    let args = {\n        let mut map = HashMap::new();\n        map.insert(String::from(\"name\"), \"Alice\".into());\n        map\n    };\n\n    assert_eq!(\"Hello Alice!\", LOCALES.lookup_with_args(\u0026US_ENGLISH, \"greeting\", \u0026args));\n    assert_eq!(\"Bonjour Alice!\", LOCALES.lookup_with_args(\u0026FRENCH, \"greeting\", \u0026args));\n    assert_eq!(\"Hallo Alice!\", LOCALES.lookup_with_args(\u0026GERMAN, \"greeting\", \u0026args));\n}\n```\n\n### Tera\nWith the `tera` feature you can use `FluentLoader` as a Tera function.\nIt accepts a `key` parameter pointing to a fluent resource and `lang` for\nwhat language to get that key for. Optionally you can pass extra arguments\nto the function as arguments to the resource. `fluent-templates` will\nautomatically convert argument keys from Tera's `snake_case` to the fluent's\npreferred `kebab-case` arguments.\n\n```toml\nfluent-templates = { version = \"*\", features = [\"tera\"] }\n```\n\n```rust\nuse fluent_templates::{FluentLoader, static_loader};\n\nstatic_loader! {\n    static LOCALES = {\n        locales: \"./tests/locales\",\n        fallback_language: \"en-US\",\n        // Removes unicode isolating marks around arguments, you typically\n        // should only set to false when testing.\n        customise: |bundle| bundle.set_use_isolating(false),\n    };\n}\n\nfn main() {\n    let mut tera = tera::Tera::default();\n    let ctx = tera::Context::default();\n    tera.register_function(\"fluent\", FluentLoader::new(\u0026*LOCALES));\n    assert_eq!(\n        \"Hello World!\",\n        tera.render_str(r#\"{{ fluent(key=\"hello-world\", lang=\"en-US\") }}\"#, \u0026ctx).unwrap()\n    );\n    assert_eq!(\n        \"Hello Alice!\",\n        tera.render_str(r#\"{{ fluent(key=\"greeting\", lang=\"en-US\", name=\"Alice\") }}\"#, \u0026ctx).unwrap()\n    );\n}\n```\n\n### Handlebars\nIn handlebars, `fluent-templates` will read the `lang` field in your\n[`handlebars::Context`] while rendering.\n\n```toml\nfluent-templates = { version = \"*\", features = [\"handlebars\"] }\n```\n\n```rust\nuse fluent_templates::{FluentLoader, static_loader};\n\nstatic_loader! {\n    static LOCALES = {\n        locales: \"./tests/locales\",\n        fallback_language: \"en-US\",\n        // Removes unicode isolating marks around arguments, you typically\n        // should only set to false when testing.\n        customise: |bundle| bundle.set_use_isolating(false),\n    };\n}\n\nfn main() {\n    let mut handlebars = handlebars::Handlebars::new();\n    handlebars.register_helper(\"fluent\", Box::new(FluentLoader::new(\u0026*LOCALES)));\n    let data = serde_json::json!({\"lang\": \"zh-CN\"});\n    assert_eq!(\"Hello World!\", handlebars.render_template(r#\"{{fluent \"hello-world\"}}\"#, \u0026data).unwrap());\n    assert_eq!(\"Hello Alice!\", handlebars.render_template(r#\"{{fluent \"greeting\" name=\"Alice\"}}\"#, \u0026data).unwrap());\n}\n```\n\n### Handlebars helper syntax.\nThe main helper provided is the `{{fluent}}` helper. If you have the\nfollowing Fluent file:\n\n```fluent\nfoo-bar = \"foo bar\"\nplaceholder = this has a placeholder { $variable }\nplaceholder2 = this has { $variable1 } { $variable2 }\n```\n\nYou can include the strings in your template with\n\n```hbs\n\u003c!-- will render \"foo bar\" --\u003e\n{{fluent \"foo-bar\"}}\n\u003c!-- will render \"this has a placeholder baz\" --\u003e\n{{fluent \"placeholder\" variable=\"baz\"}}\n```\n\nYou may also use the `{{fluentparam}}` helper to specify [variables],\nespecially if you need them to be multiline.\n\n```hbs\n{{#fluent \"placeholder2\"}}\n    {{#fluentparam \"variable1\"}}\n        first line\n        second line\n    {{/fluentparam}}\n    {{#fluentparam \"variable2\"}}\n        first line\n        second line\n    {{/fluentparam}}\n{{/fluent}}\n```\n\n### FAQ\n\n#### Why is there extra characters around the values of arguments?\nThese are called \"Unicode Isolating Marks\" that used to allow the text to be bidirectional.\nYou can disable this with `FluentBundle::set_isolating_marks` being set to `false`.\n\n```rust\nstatic_loader! {\n    static LOCALES = {\n        locales: \"./tests/locales\",\n        fallback_language: \"en-US\",\n        // Removes unicode isolating marks around arguments.\n        customise: |bundle| bundle.set_use_isolating(false),\n    };\n}\n```\n\n[variables]: https://projectfluent.org/fluent/guide/variables.html\n[`static_loader!`]: https://docs.rs/fluent-templates/0.5.4/fluent_templates/macro.static_loader.html\n[`StaticLoader`]: https://docs.rs/fluent-templates/0.5.4/fluent_templates/struct.StaticLoader.html\n[`ArcLoader`]: https://docs.rs/fluent-templates/0.5.4/fluent_templates/struct.ArcLoader.html\n[`Loader`]: https://docs.rs/fluent-templates/0.5.4/fluent_templates/trait.Loader.html\n[`handlebars::Context`]: https://docs.rs/handlebars/3.1.0/handlebars/struct.Context.html\n[fluent]: https://projectfluent.org\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxampprocky%2Ffluent-templates","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxampprocky%2Ffluent-templates","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxampprocky%2Ffluent-templates/lists"}