June 24, 2026·4 min read·engineeringfishlocalizationdiagnostics

fish 4.8 begins moving Rust messages to Fluent

fish 4.8 adopts Fluent for Rust message localization while retaining gettext alongside it. Named message variables and project tooling change how translators maintain diagnostics.

Fish's existing gettext implementation does not support every gettext feature. Its 4.8.0 contribution guide specifically names message context and plurals among the unsupported capabilities. The project uses custom extraction and runtime tools, with no runtime dependency on the gettext library. That is the implementation context for fish 4.8.0's adoption of Fluent for messages defined in Rust.

Released on June 24, fish 4.8.0 says Rust messages can and should now use Fluent. The release notes also identify new tooling based on fluent-ftl-tools. This is an incremental migration. The versioned guide says some Rust messages still use gettext while others use Fluent, and fish-script messages continue to use gettext.

Fish appeals to me because it makes the interactive command line useful without requiring extensive configuration. Diagnostics belong to that experience. A shell that identifies a problem while a user is editing needs to explain it in language that user can understand. Translation support affects that explanation as directly as the wording of the original diagnostic.

Named variables let a translation choose its order

The guide describes Fluent messages in terms of message identifiers and named variables. At runtime, the identifier selects a localized message according to the user's language settings. A variable appears in a Fluent message with this syntax:

{ $variable_name }

Naming the variable lets a translator place it where the target language requires it. The guide contrasts this with fish's gettext placeholders, whose translated strings must retain the same placeholders in the same order. That constraint comes from fish's implementation and formatting expectations. It should not be generalized into a claim about every possible use of gettext.

This is a practical reason to change message infrastructure. The source language's word order does not determine the order that makes sense in another language. A diagnostic containing a command name and another value needs to preserve which value is which even when the surrounding sentence changes. Named variables make that relationship explicit in the translation.

The source guide asks new Rust code to use localize!, supplying a message identifier, an English definition, and named argument values. These names must agree with the variables referenced in the message. Translation is therefore connected to a defined call in the source, not an arbitrary string replacement after formatting has already occurred.

Catalog maintenance becomes part of the change

Fish stores Fluent translations in localization/fluent with .ftl names. Gettext catalogs remain under localization/po with .po names. Contributors need to know which system owns the message they are changing, because updating one catalog does not automatically translate messages owned by the other.

The guide explains that source changes can invalidate translations. Its tooling detects changes to message definitions and removes translations for identifiers that no longer exist. When it cannot resolve a change automatically, it annotates affected translations for attention. Checks fail while those annotations remain. This makes unfinished translation maintenance visible to the development process.

The tradeoff includes tooling maturity. The guide explicitly says Fluent's ecosystem has less available tooling than gettext's and provides project commands to fill that gap. Contributors can format catalogs and ask which identifiers still lack translations. A focused check can target the catalog being edited:

cargo xtask fluent check localization/fluent/pt_BR.ftl

This is a command from the documented contributor workflow. It checks a translation file in a fish source checkout. It does not configure the installed shell's language or imply that every diagnostic has already migrated to Fluent.

Debug builds provide a shorter feedback cycle for Fluent translation changes. According to the guide, restarting fish is enough to see those updates without recompiling. That helps translators inspect the message in context, where its variables and surrounding output are visible. Release builds and gettext changes have different build requirements documented in the same guide.

The formatter can also read FTL text from standard input and write the formatted version to standard output. The guide supplies editor integration examples using that interface. This lets catalog formatting participate in the same editing workflow as other source files, without requiring translators to reproduce the formatting rules by hand.

Language selection has explicit fallback rules

Fish offers status language as well as environment-based language selection. The guide's example includes:

status language list-available
status language set pt_BR

The first command lists recognized identifiers. The second selects one. This is useful during translation work because it separates whether a catalog is recognized from whether a particular message has been translated. It also lets contributors inspect behavior without relying entirely on inherited locale variables.

The guide distinguishes fallback behavior. Environment-based selection can fall back from a regional identifier to a more general catalog. status language uses exact matches and overrides environment configuration. It can accept an ordered list of languages, which gives the user direct control over the lookup sequence.

I like that explicit control for an interactive shell. A translation should improve comprehension, and a user needs a straightforward way to select the messages they can use. The contribution guide also prioritizes Rust messages for translators, so the project communicates where work has the greatest immediate value during the transition.

The same release repairs concurrent history visibility

Fish 4.8 also fixes history search forgetting commands after they were rerun in concurrent sessions. That is separate from the preceding release's termination-corruption repair. The release-tag changelog states the new scope directly, so an account of this release should preserve the distinction between file corruption and a session's view of shared history.

The release also gives cd explicit logical and physical modes through -L and -P. These let the caller choose how symbolic links are handled during a directory change. That is another case where making an implicit choice explicit improves the user's ability to explain the result.

For localization contributors, the immediate work remains concrete. A Rust diagnostic needs a defined message identifier and matching named arguments. Its target-language catalog needs the corresponding translation, and the project's checks need to accept the maintained definition. Fish 4.8 supplies that workflow while both localization systems remain present.