Files
fish-shell/share/help_sections
Daniel Rainer 8816960b87 l10n: add Fluent localization system
Add an implementation allowing to use Fluent for localization in Rust.

Fluent is significantly more expressive than gettext. It uses message
IDs which, unlike in gettext, are not necessarily the default message
string. This allows for proper support of messages which happen to be
identical in English, but not in other languages. In gettext, this could
be solved to some extent with contexts, but our gettext implementation
does not support that. In Fluent, arguments to the message are specified
as key-value pairs, which gives translators more semantic information
and allows reordering the arguments in the translation, which is
impossible with gettext. Fluent also allows for more complex grammatical
features, such as different plural forms, grammatical cases, and
adapting phrases to the correct gender.

This commit only introduces the infrastructure for using Fluent instead
of gettext, with the goal of eventually replacing gettext for
localization in Rust. Making use of the new infrastructure is left to
follow-up commits.

To localize a message with Fluent, the new `localize!` macro should be
used. Its arguments are key-value pairs. The first pair must consist of
two string literals. The key is a Fluent message ID and the value the
corresponding definition of the message in English. It must be valid
Fluent syntax for a message definition. The remaining key-value pairs
are used for Fluent variables which appear in the message definition.
Each key must match the name of a variable present in the message
definition, and the value is some Rust value which can be displayed by
Fluent. The variable in the Fluent message definition will be replaced
by that value. Example syntax:

```rust
localize!(
    "fish-version" = "{ $package_name }, version { $version }",
    package_name = "fish",
    version = crate::BUILD_VERSION,
)
````

If a message should be available in more than one place, define a
function containing the `localize!` macro and call that function from
the different locations needing access to the message, instead of
putting multiple `localize!` macros with the same message ID into the
code. The `localize_fn!` macro can help with that.

By having the message definition in the Rust sources, we have all the
relevant information for showing English messages in Rust, without
needing to rely on external sources. However, the Fluent library expects
data for all languages in the FTL format, so we have tooling to
automatically generate `en.ftl` from the Rust sources. Having this file
is also useful for other Fluent tooling, as well as being able to track
modifications to messages, which we also have tooling for.

To add translations for a new language, translators can create a new FTL
file in the `localization/fluent` directory with the appropriate file
name. No additional setup is needed. Translations for an individual
message can be added by copying the message from `en.ftl` and adjusting
the definition for the respective language.

These FTL files are included via `rust-embed` and parsed on demand at
runtime, ensuring that only languages specified in the user's language
settings are considered, saving the effort or parsing unneeded files.
`en.ftl` is always parsed, as it implicitly is the last fallback
option, when no other language in the user's precedence list has a
translation for a message ID. We know that `en.ftl` contains a
translation for all message IDs we use because message IDs can only be
used by putting them into the `localize!` macro with a corresponding
English message definition, and `en.ftl` is auto-generated from these
definitions. This generation happens as a 2-step process: First, we
compile with the `fluent-extract` feature, with the
`FISH_FLUENT_EXTRACTION_DIR` environment variable set to point to an
empty directory. This will result in the `localize!` macro passing the
message ID, definition, and variable names specified in subsequent
key-value pairs, to a proc macro which performs some checks on the
provided data and writes the message definition in the FTL format to a
fresh file in `FISH_FLUENT_EXTRACTION_DIR`. This results in one file per
`localize!` macro invocation. In the second step, the data from all
these files is combined into a single file, `en.ftl`, which is
automatically formatted. Before overwriting the old `en.ftl`, checks are
performed to look for changes to message definitions. If any change is
detected, translations of the message might have to be updated.
Some changes, like removal of a message, can be handled automatically,
by deleting all translations. For changes which cannot be handled fully
automatically, the affected translations are marked with an annotation,
which causes our checks to fail while it is present. It is the
responsibility of the developer who changed the message definition to
handle updates to its translations. This can be done manually, by
editing the affected translations and removing the annotation, which
should be the preferred option when translations need to be modified and
the developer knows the language. Otherwise, there is automation in the
form of `cargo xtask fluent resolve-outdated`, which allows specifying
what should happen to some or all translations of a message, with the
available option being:
- delete the translation, used when the meaning of the English message
  changed so much that keeping the old translations would be misleading
- delete the annotation, used when changes to the English message have
  no impact on translations, e.g. for typo fixes
- change the annotation to indicate that translator review is desired,
  used when the meaning or wording of the English message changed
  slightly, but not enough to invalidate the translations

There are several tools available as subcommands of `cargo xtask
fluent`:
- `check`: Checks the FTL files, ensuring that they can be parsed
  without errors, that no duplicate IDs are specified, that they are
  formatted correctly, and that there are no extra IDs, i.e. IDs not
  present in `en.ftl`, which is expected to be complete. More rigorous
  checks could be added, such as checking whether the same set of
  variables are used for a certain ID in all languages. The complexity
  of Fluent's syntax makes this non-trivial, which is the reason it's
  not already implemented.
- `format`: Formats the specified FTL files (or all by default). Also
  has a mode suitable for editor integration to format files from the
  editor. Examples for setting that up in Vim are provided in the
  `CONTRIBUTING.rst` docs.
- `rename`: Renames IDs or associated variables across all FTL files.
- `resolve-outdated`: Described above.
- `show-missing`: Shows which IDs don't have a translation yet.
- `update`: Runs the generation pipeline for `en.ftl` and potential
  translation updates/annotations.

There is one additional tool, which is designed to help with porting
existing messages localized via gettext to the new Fluent
implementation. This is intentionally not added to fish, because it
is only useful for the transition. Once we have ported all messages to
Fluent we won't have a use for it anymore. If you are interested in
using it to port messages, it's the `po-convert` binary in the
`fluent-ftl-tools` package. The CLI is somewhat convoluted, but can be
simplified by wrapping it with a script which hard-codes the path to the
relevant PO and FTL file directories. Then, the remaining information
which needs to be specified is:
- a line number in a PO file to identify the message to be ported
- the new message ID
- the name of each variable, in the order the formatting specifiers
  appear in the gettext msgid.
Specifying the line number and invoking the wrapper script can be
partially automated by using a custom editor shortcut.
The tool will port the msgstr for each language which has one defined,
and always generate and entry for `en.po` based on the msgid.
The tool does not edit Rust code, but suggests a Rust code snippet on
stdout based on the specified message ID and variable names.

Some of our tooling relies on features of the `fluent` package which
are not exported by default, so we use a fork which changes that until
our PR for adding it upstream is accepted.

Part of #11928
2026-06-18 14:09:43 +08:00

337 lines
8.1 KiB
Plaintext

cmds/_
cmds/abbr
cmds/alias
cmds/and
cmds/argparse
cmds/begin
cmds/bg
cmds/bind
cmds/block
cmds/break
cmds/breakpoint
cmds/builtin
cmds/case
cmds/cd
cmds/cdh
cmds/command
cmds/commandline
cmds/complete
cmds/contains
cmds/continue
cmds/count
cmds/dirh
cmds/dirs
cmds/disown
cmds/echo
cmds/else
cmds/emit
cmds/end
cmds/eval
cmds/exec
cmds/exit
cmds/export
cmds/false
cmds/fg
cmds/fish
cmds/fish_add_path
cmds/fish_breakpoint_prompt
cmds/fish_clipboard_copy
cmds/fish_clipboard_paste
cmds/fish_command_not_found
cmds/fish_config
cmds/fish_default_key_bindings
cmds/fish_delta
cmds/fish_git_prompt
cmds/fish_greeting
cmds/fish_hg_prompt
cmds/fish_indent
cmds/fish_is_root_user
cmds/fish_key_reader
cmds/fish_mode_prompt
cmds/fish_opt
cmds/fish_prompt
cmds/fish_right_prompt
cmds/fish_should_add_to_history
cmds/fish_status_to_signal
cmds/fish_svn_prompt
cmds/fish_tab_title
cmds/fish_title
cmds/fish_update_completions
cmds/fish_vcs_prompt
cmds/fish_vi_key_bindings
cmds/for
cmds/funced
cmds/funcsave
cmds/function
cmds/functions
cmds/help
cmds/history
cmds/if
cmds/isatty
cmds/jobs
cmds/math
cmds/nextd
cmds/not
cmds/open
cmds/or
cmds/path
cmds/popd
cmds/prevd
cmds/printf
cmds/prompt_hostname
cmds/prompt_login
cmds/prompt_pwd
cmds/psub
cmds/pushd
cmds/pwd
cmds/random
cmds/read
cmds/realpath
cmds/return
cmds/set
cmds/set_color
cmds/source
cmds/status
cmds/string
cmds/string-collect
cmds/string-escape
cmds/string-join
cmds/string-join0
cmds/string-length
cmds/string-lower
cmds/string-match
cmds/string-pad
cmds/string-repeat
cmds/string-replace
cmds/string-shorten
cmds/string-split
cmds/string-split0
cmds/string-sub
cmds/string-trim
cmds/string-unescape
cmds/string-upper
cmds/suspend
cmds/switch
cmds/test
cmds/time
cmds/trap
cmds/true
cmds/type
cmds/ulimit
cmds/umask
cmds/vared
cmds/wait
cmds/while
commands
commands#helper-commands
commands#helper-functions
commands#keywords
commands#known-functions
commands#the-full-list
commands#tools
completions
completions#useful-functions-for-writing-completions
completions#where-to-put-completions
contributing
contributing#adding-translations-for-a-new-language
contributing#code-style
contributing#commit-history
contributing#configuring-your-editor-for-fish-scripts
contributing#contributing-completions
contributing#contributing-documentation
contributing#contributing-translations
contributing#editing-ftl-files
contributing#editing-po-files
contributing#fish-script-style-guide
contributing#github
contributing#guidelines
contributing#local-testing
contributing#mailing-list
contributing#minimum-supported-rust-version-msrv-policy
contributing#modifications-to-strings-in-source-files-gettext-only
contributing#modifying-existing-translations
contributing#setting-code-up-for-translations
contributing#testing
contributing#updating-dependencies
contributing#versioning
design
design#configurability-is-the-root-of-all-evil
design#the-law-of-discoverability
design#the-law-of-orthogonality
design#the-law-of-responsiveness
design#the-law-of-user-focus
faq
faq#fish-does-not-work-in-a-specific-terminal
faq#how-do-i-change-the-greeting-message
faq#how-do-i-check-whether-a-variable-is-defined
faq#how-do-i-check-whether-a-variable-is-not-empty
faq#how-do-i-customize-my-syntax-highlighting-colors
faq#how-do-i-get-the-exit-status-of-a-command
faq#how-do-i-run-a-command-every-login-what-s-fish-s-equivalent-to-bashrc-or-profile
faq#how-do-i-run-a-command-from-history
faq#how-do-i-run-a-subcommand-the-backtick-doesn-t-work
faq#how-do-i-set-my-prompt
faq#how-do-i-set-or-clear-an-environment-variable
faq#i-m-getting-weird-graphical-glitches-a-staircase-effect-ghost-characters-cursor-in-the-wrong-position
faq#my-command-pkg-config-gives-its-output-as-a-single-long-string
faq#my-command-prints-no-matches-for-wildcard-but-works-in-bash
faq#uninstalling-fish
faq#what-is-the-equivalent-to-this-thing-from-bash-or-other-shells
faq#why-does-my-prompt-show-a-i
faq#why-doesn-t-history-substitution-etc-work
faq#why-doesn-t-set-ux-exported-universal-variables-seem-to-work
faq#why-won-t-ssh-scp-rsync-connect-properly-when-fish-is-my-login-shell
fish_for_bash_users
fish_for_bash_users#arithmetic-expansion
fish_for_bash_users#blocks-and-loops
fish_for_bash_users#builtins-and-other-commands
fish_for_bash_users#command-substitutions
fish_for_bash_users#heredocs
fish_for_bash_users#other-facilities
fish_for_bash_users#process-substitution
fish_for_bash_users#prompts
fish_for_bash_users#quoting
fish_for_bash_users#special-variables
fish_for_bash_users#string-manipulation
fish_for_bash_users#subshells
fish_for_bash_users#test-test
fish_for_bash_users#variable-defaults-my-variable-default-value
fish_for_bash_users#variables
fish_for_bash_users#wildcards-globs
index
index#configuration
index#default-shell
index#examples
index#installation
index#other-help-pages
index#resources
index#setup
index#shebang-line
index#starting-and-exiting
index#uninstalling
index#where-to-go
interactive
interactive#abbreviations
interactive#autosuggestions
interactive#command-line-editor
interactive#command-mode
interactive#configurable-greeting
interactive#copy-and-paste-kill-ring
interactive#custom-bindings
interactive#directory-stack
interactive#emacs-mode-commands
interactive#help
interactive#insert-mode
interactive#key-sequences
interactive#multiline-editing
interactive#navigating-directories
interactive#pager-color-variables
interactive#private-mode
interactive#programmable-prompt
interactive#programmable-title
interactive#searchable-command-history
interactive#shared-bindings
interactive#syntax-highlighting
interactive#syntax-highlighting-variables
interactive#tab-completion
interactive#vi-mode-commands
interactive#visual-mode
language
language#argument-handling
language#autoloading-functions
language#brace-expansion
language#builtin-commands
language#combiners-and-or
language#combining-different-expansions
language#combining-lists
language#combining-pipes-and-redirections
language#command-lookup
language#command-substitution
language#comments
language#conditions
language#configuration-files
language#debugging-fish-scripts
language#defining-aliases
language#dereferencing-variables
language#escaping-characters
language#event-handlers
language#exporting-variables
language#functions
language#future-feature-flags
language#home-directory-expansion
language#input-output-redirection
language#job-control
language#lists
language#locale-variables
language#loops-and-blocks
language#overriding-variables-for-a-single-command
language#parameter-expansion
language#path-variables
language#piping
language#profiling-fish-scripts
language#querying-for-user-input
language#quotes
language#quoting-variables
language#shell-variable-and-function-names
language#shell-variables
language#slices
language#special-variables
language#syntax-overview
language#table-of-operators
language#terminology
language#the-if-statement
language#the-status-variable
language#the-switch-statement
language#universal-variables
language#variable-expansion
language#variable-scope
language#variables-as-command
language#wildcards-globbing
license
license#gnu-library-general-public-license
license#license-for-fish
license#license-for-the-python-docs-theme
license#mit-license
prompt
prompt#adding-color
prompt#formatting
prompt#our-first-prompt
prompt#save-the-prompt
prompt#shortening-the-working-directory
prompt#status
prompt#transient-prompt
prompt#where-to-go-from-here
relnotes
terminal-compatibility
terminal-compatibility#dcs-commands-and-gnu-screen
terminal-compatibility#optional-commands
terminal-compatibility#required-commands
terminal-compatibility#unicode-codepoints
tutorial
tutorial#autoloading-functions
tutorial#autosuggestions
tutorial#combiners-and-or-not
tutorial#command-substitutions
tutorial#conditionals-if-else-switch
tutorial#exit-status
tutorial#exports-shell-variables
tutorial#functions
tutorial#getting-help
tutorial#getting-started
tutorial#learning-fish
tutorial#lists
tutorial#loops
tutorial#path
tutorial#pipes-and-redirections
tutorial#prompt
tutorial#ready-for-more
tutorial#running-commands
tutorial#separating-commands-semicolon
tutorial#startup-where-s-bashrc
tutorial#syntax-highlighting
tutorial#tab-completions
tutorial#universal-variables
tutorial#variables
tutorial#why-fish
tutorial#wildcards