Flynt
Fluent linter for Askama templates
Flynt collects the Fluent translation keys used in your Rust code and
Askama templates. Then it compares them against your .ftl files
to report issues.
Flynt does not need to be configured by default. It reads the crates to scan from your Cargo.toml
and reads the locales from the subdirectories in the locales directory.
Install
cargo install flynt
Quick start
cd my-project
flynt
✓ All translation keys validated
Summary:
136 keys used
136 keys defined
2 locales checked: en, fr
Flynt takes the directory to lint as its only argument. It does not care where you run it from:
flynt /path/to/my-project
Checks
Flynt reports every syntax problem in one pass.
Coverage
A key used in code or in a template is defined in every locale.
Consistency
Every locale defines the same set of keys.
Duplicates
No locale defines the same key twice.
Unused
Every defined key is used somewhere. Unused keys are a warning.
Syntax
Every .ftl file parses.
How it works
Flynt finds the relevant files in this order:
1. Cargo.toml
Read Cargo.toml to get the member crates, then scan the src directory of every entry in
[workspace] members, with globs such as crates/* expanded, and [workspace] exclude respected.
For project without [workspace], it scans the crate's own src.
2. Templates
Scan the templates directory next to each of those crates, where one exists.
3. Locales
Scan every subdirectory of locales, each searched recursively for .ftl files.
The layout is locales/<locale>/**/*.ftl.
Ignored files and text
default-members
The crate's [workspace] default-members is ignored.
Comments
Lines that begin with // are not scanned.
A crate can document its own helper in a doc comment without adding a phantom key.
Use --include-comments to scan comment lines anyway.
What it looks for
By default, these four helpers:
{{ "tpl-title" | t(&lang) }}
{{ "mail-count" | tn(&lang, "count", count) }}
loc
loc_with_args
Use --filter and --function if your project names them differently. Both accept the key as a
string literal in the position shown above. Both may be split across lines.
Options
| Flag | .flynt.toml key |
Default |
|---|---|---|
[PATH] |
(command line only) | . |
--config <FILE> |
(command line only) | .flynt.toml |
--no-config |
(command line only) | false |
--manifest-path <FILE> |
manifest-path |
Cargo.toml |
--locales-dir <DIR> |
locales-dir |
locales |
--locale <LOCALE> |
locales |
all subdirectory in locales |
--reference-locale <LOCALE> |
reference-locale |
en, else the first sorted |
--src <DIR> |
src |
<member>/src for each workspace member |
--add-src <DIR> |
add-src |
none |
--templates <DIR> |
templates |
<member>/templates where present |
--add-templates <DIR> |
add-templates |
none |
--template-ext <EXT> |
template-ext |
html |
--filter <NAME> |
filters |
t, tn |
--function <NAME> |
functions |
loc, loc_with_args |
--unused <LEVEL> |
unused |
warn (error, warn, allow) |
--ignore-unused <GLOB> |
ignore-unused |
none |
--attributes[=BOOL] |
attributes |
true |
--include-comments[=BOOL] |
include-comments |
false |
--require-locales[=BOOL] |
require-locales |
true |
--exclude <GLOB> |
exclude |
target/** |
--follow-links[=BOOL] |
follow-links |
true |
--format <FORMAT> |
format |
text (text, json) |
--color <WHEN> |
color |
auto (auto, always, never) |
-q, --quiet[=BOOL] |
quiet |
false |
--locale,--filter, and--functionread better in the singular when repeated on the command line. The configuration file accepts either spelling for each.
Configuration file
Drop a .flynt.toml file at the root of the project. Its keys match the long flag names exactly.
You can write down anything you can pass on the command line:
# .flynt.toml
= "i18n"
= ["en", "fr", "de"]
# This project uses different names for its helper functions.
= ["tr", "trn"]
= ["translate"]
# These keys are assembled at runtime. They only look unused.
= "error"
= ["err-http-*", "dashboard-metric-*"]
Order
- The command line wins over
.flynt.toml. .flynt.tomlwins over what was inferred.- Anything left unspecified falls back to the built-in default.
JSON output
--format json emits the whole report for a CI step or a dashboard. Paths are relative to the
linted directory. Every list is sorted to keep the output stable between runs and easier to diff.
schema_version is bumped on any breaking change.
Use as a library
use ;
let cli = PartialConfig ;
let config = load?;
let report = check?;
for finding in &report.missing_keys
# Ok::
flynt::check produces no output of its own. Render it with flynt::report::render, or act on the
findings directly.
Exit codes
| Code | Meaning |
|---|---|
0 |
Clean, or warnings only |
1 |
Findings at error severity |
2 |
Flynt could not do its job: a bad path, an unreadable manifest, no locales |
Limitations
- Keys assembled at runtime like
format!("err-{code}")cannot be seen. Cover them with--ignore-unused. - Only whole-line
//comments are skipped. Flynt still scans/* ... */blocks. - A crate whose
[lib] pathpoints outsidesrcis not found by inference. Add it with--add-src. - Fluent terms (
-brand = ...) count for the duplicate check. They are never reported as unused. Nothing outside the.ftlfiles can reference a term.
Requirements
Rust 1.85 or later, edition 2024.
License
Licensed under either of
- Apache License, Version 2.0
- MIT License
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.