mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-27 08:43:09 -03:00
l10n: implement status language builtin
Based on the discussion in https://github.com/fish-shell/fish-shell/pull/11967 Introduce a `status language` builtin, which has subcommands for controlling and inspecting fish's message localization status. The motivation for this is that using only the established environment variables `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, and `LANG` can cause problems when fish interprets them differently from GNU gettext. In addition, these are not well-suited for users who want to override their normal localization settings only for fish, since fish would propagate the values of these variables to its child processes. Configuration via these variables still works as before, but now there is the `status language set` command, which allows overriding the localization configuration. If `status language set` is used, the language precedence list will be taken from its remaining arguments. Warnings will be shown for invalid arguments. Once this command was used, the localization related environment variables are ignored. To go back to taking the configuration from the environment variables after `status language set` was executed, users can run `status language unset`. Running `status language` without arguments shows information about the current message localization status, allowing users to better understand how their settings are interpreted by fish. The `status language list-available` command shows which languages are available to choose from, which is used for completions. This commit eliminates dependencies from the `gettext_impl` module to code in fish's main crate, allowing for extraction of this module into its own crate in a future commit. Closes #12106
This commit is contained in:
committed by
Johannes Altmanninger
parent
c0b95a0ee1
commit
aa8f5fc77e
@@ -20,7 +20,6 @@ Color and key binding variables are no longer set in universal scope
|
||||
2. universal variables as a source of truth are easy to misunderstand,
|
||||
compared to configuration files like ``config.fish``.
|
||||
|
||||
|
||||
Deprecations and removed features
|
||||
---------------------------------
|
||||
- Erasing a color variable (e.g. by running ``set -e fish_color_command``)
|
||||
@@ -30,6 +29,10 @@ Deprecations and removed features
|
||||
- ``fish_config theme choose`` now clears only color variables that were set by earlier invocations of a ``fish_config theme choose`` command
|
||||
(which includes fish's default theme).
|
||||
|
||||
Scripting improvements
|
||||
----------------------
|
||||
- New :ref:`status language <status-language>` commands allow showing and modifying language settings for fish messages without having to modify environment variables.
|
||||
|
||||
Interactive improvements
|
||||
------------------------
|
||||
- When typing immediately after starting fish, the first prompt is now rendered correctly.
|
||||
|
||||
@@ -23,6 +23,8 @@ If other programs launched via fish should respect these locale variables they h
|
||||
|
||||
For :envvar:`LANGUAGE` you can use a list, or use colons to separate multiple languages.
|
||||
|
||||
If the :ref:`status language set <status-language>` command was used, its arguments specify the language precedence, and the environment variables are ignored.
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ Synopsis
|
||||
status list-files [PATH ...]
|
||||
status terminal
|
||||
status test-terminal-feature FEATURE
|
||||
status language [list-available|set [LANGUAGE ...]|unset]
|
||||
|
||||
Description
|
||||
-----------
|
||||
@@ -146,6 +147,32 @@ The following operations (subcommands) are available:
|
||||
Currently the only available *FEATURE* is :ref:`scroll-content-up <term-compat-indn>`.
|
||||
An error will be printed when passed an unrecognized feature.
|
||||
|
||||
.. _status-language:
|
||||
|
||||
**language**
|
||||
Show or modify message localization settings.
|
||||
When invoked without arguments, the current language settings are shown.
|
||||
|
||||
Available subcommands:
|
||||
|
||||
**list-available**
|
||||
prints the language names for which fish has translations.
|
||||
These names can be used with the **set** subcommand.
|
||||
|
||||
**set**
|
||||
sets the language precedence for fish's messages.
|
||||
Overrides language settings configured via :ref:`environment variables <variables-locale>`, but only applies to fish itself, not to any child processes.
|
||||
Takes a list of language names from the set shown by the **list-available** subcommand.
|
||||
For some languages, fish's translation catalogs are incomplete, meaning not all messages can be shown in these languages.
|
||||
Therefore, we allow specifying a list here, with translations taken from the first specified language which has a translation available for a message.
|
||||
For example, after running ``status language set pt_BR fr``, all messages which have a translation into Brazilian Portuguese will be shown in that language.
|
||||
The remaining messages will be shown in French, if a French translation is available.
|
||||
If none of the specified languages have a translation available for a message, the message will be shown in English.
|
||||
|
||||
**unset**
|
||||
undoes the effects of the **set** subcommand.
|
||||
Language settings will be taken from environment variables again.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
|
||||
@@ -1549,7 +1549,8 @@ You can change the settings of fish by changing the values of certain variables.
|
||||
|
||||
.. describe:: Locale Variables
|
||||
|
||||
Locale variables such as :envvar:`LANG`, :envvar:`LC_ALL`, :envvar:`LC_MESSAGES`, :envvar:`LC_NUMERIC` and :envvar:`LC_TIME` set the language option for the shell and subprograms. See the section :ref:`Locale variables <variables-locale>` for more information.
|
||||
Locale variables such as :envvar:`LANG`, :envvar:`LC_ALL`, :envvar:`LC_MESSAGES`, :envvar:`LC_NUMERIC` and :envvar:`LC_TIME` set the language option for the shell and subprograms.
|
||||
See the section :ref:`Locale variables <variables-locale>` and :ref:`status language <status-language>` for more information.
|
||||
|
||||
.. describe:: Color variables
|
||||
|
||||
|
||||
27
po/de.po
27
po/de.po
@@ -83,6 +83,10 @@ msgstr "$status ist kein gültiger Befehl. Siehe `help %s`"
|
||||
msgid "%s"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: %s: invalid subcommand\n"
|
||||
msgstr "%s %s: %s: ungültiger Unterbefehl\n"
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n"
|
||||
msgstr ""
|
||||
@@ -881,6 +885,10 @@ msgstr "Abbruch"
|
||||
msgid "Abort (Alias for SIGABRT)"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "Active languages (%s):"
|
||||
msgstr ""
|
||||
|
||||
msgid "Address boundary error"
|
||||
msgstr "Adressbereichsfehler"
|
||||
|
||||
@@ -1229,6 +1237,9 @@ msgstr ""
|
||||
msgid "Jobs getting started or continued"
|
||||
msgstr ""
|
||||
|
||||
msgid "Language specifiers appear repeatedly:"
|
||||
msgstr ""
|
||||
|
||||
msgid "List or remove functions"
|
||||
msgstr "Funktionen auflisten oder entfernen"
|
||||
|
||||
@@ -1279,6 +1290,9 @@ msgstr "Nie"
|
||||
msgid "No TTY for interactive shell (tcgetpgrp failed)"
|
||||
msgstr "Kein TTY für interaktive Shell (tcgetpgrp schlug fehl)"
|
||||
|
||||
msgid "No catalogs available for language specifiers:"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "No matches for wildcard '%s'. See `help %s`."
|
||||
msgstr ""
|
||||
@@ -1775,6 +1789,9 @@ msgstr "Befehlsersetzung sind in Befehlsposition nicht erlaubt. Probier stattdes
|
||||
msgid "completion reached maximum recursion depth, possible cycle?"
|
||||
msgstr "Vervollständigung hat die maximale Rekursionstiefe erreicht, möglicher Kreis?"
|
||||
|
||||
msgid "default"
|
||||
msgstr ""
|
||||
|
||||
msgid "dir symlink"
|
||||
msgstr "Verweis auf ein Verzeichnis"
|
||||
|
||||
@@ -1800,10 +1817,17 @@ msgid ""
|
||||
"`help %s` will show an online version\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "from command `status language set`"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "from sourcing file %s\n"
|
||||
msgstr "aus der Quelldatei %s\n"
|
||||
|
||||
#, c-format
|
||||
msgid "from variable %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "in command substitution\n"
|
||||
msgstr "in der Befehlsersetzung\n"
|
||||
|
||||
@@ -3386,6 +3410,9 @@ msgstr ""
|
||||
msgid "Show modification time"
|
||||
msgstr "Änderungsdatum anzeigen"
|
||||
|
||||
msgid "Show or change fish's language settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show seconds since the modification time"
|
||||
msgstr ""
|
||||
|
||||
|
||||
27
po/en.po
27
po/en.po
@@ -81,6 +81,10 @@ msgstr ""
|
||||
msgid "%s"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: %s: invalid subcommand\n"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n"
|
||||
msgstr ""
|
||||
@@ -879,6 +883,10 @@ msgstr "Abort"
|
||||
msgid "Abort (Alias for SIGABRT)"
|
||||
msgstr "Abort (Alias for SIGABRT)"
|
||||
|
||||
#, c-format
|
||||
msgid "Active languages (%s):"
|
||||
msgstr ""
|
||||
|
||||
msgid "Address boundary error"
|
||||
msgstr "Address boundary error"
|
||||
|
||||
@@ -1227,6 +1235,9 @@ msgstr ""
|
||||
msgid "Jobs getting started or continued"
|
||||
msgstr ""
|
||||
|
||||
msgid "Language specifiers appear repeatedly:"
|
||||
msgstr ""
|
||||
|
||||
msgid "List or remove functions"
|
||||
msgstr "List or remove functions"
|
||||
|
||||
@@ -1277,6 +1288,9 @@ msgstr "Never"
|
||||
msgid "No TTY for interactive shell (tcgetpgrp failed)"
|
||||
msgstr "No TTY for interactive shell (tcgetpgrp failed)"
|
||||
|
||||
msgid "No catalogs available for language specifiers:"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "No matches for wildcard '%s'. See `help %s`."
|
||||
msgstr ""
|
||||
@@ -1773,6 +1787,9 @@ msgstr ""
|
||||
msgid "completion reached maximum recursion depth, possible cycle?"
|
||||
msgstr ""
|
||||
|
||||
msgid "default"
|
||||
msgstr ""
|
||||
|
||||
msgid "dir symlink"
|
||||
msgstr ""
|
||||
|
||||
@@ -1798,10 +1815,17 @@ msgid ""
|
||||
"`help %s` will show an online version\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "from command `status language set`"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "from sourcing file %s\n"
|
||||
msgstr "from sourcing file %s\n"
|
||||
|
||||
#, c-format
|
||||
msgid "from variable %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "in command substitution\n"
|
||||
msgstr "in command substitution\n"
|
||||
|
||||
@@ -3384,6 +3408,9 @@ msgstr ""
|
||||
msgid "Show modification time"
|
||||
msgstr "Show modification time"
|
||||
|
||||
msgid "Show or change fish's language settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show seconds since the modification time"
|
||||
msgstr ""
|
||||
|
||||
|
||||
27
po/fr.po
27
po/fr.po
@@ -212,6 +212,10 @@ msgstr "$status n’est pas une commande valide. Voir « help %s »"
|
||||
msgid "%s"
|
||||
msgstr "%s"
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: %s: invalid subcommand\n"
|
||||
msgstr "%s %s : %s : sous-commande invalide\n"
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n"
|
||||
msgstr ""
|
||||
@@ -1010,6 +1014,10 @@ msgstr "Abandon"
|
||||
msgid "Abort (Alias for SIGABRT)"
|
||||
msgstr "Abandon (Alias pour SIGABRT)"
|
||||
|
||||
#, c-format
|
||||
msgid "Active languages (%s):"
|
||||
msgstr ""
|
||||
|
||||
msgid "Address boundary error"
|
||||
msgstr "Erreur de frontière d’adresse"
|
||||
|
||||
@@ -1358,6 +1366,9 @@ msgstr ""
|
||||
msgid "Jobs getting started or continued"
|
||||
msgstr ""
|
||||
|
||||
msgid "Language specifiers appear repeatedly:"
|
||||
msgstr ""
|
||||
|
||||
msgid "List or remove functions"
|
||||
msgstr "Lister ou supprimer des fonctions"
|
||||
|
||||
@@ -1408,6 +1419,9 @@ msgstr "Jamais"
|
||||
msgid "No TTY for interactive shell (tcgetpgrp failed)"
|
||||
msgstr "Pas de TTY pour shell interactif (tcgetpgrp a échoué)"
|
||||
|
||||
msgid "No catalogs available for language specifiers:"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "No matches for wildcard '%s'. See `help %s`."
|
||||
msgstr ""
|
||||
@@ -1904,6 +1918,9 @@ msgstr ""
|
||||
msgid "completion reached maximum recursion depth, possible cycle?"
|
||||
msgstr ""
|
||||
|
||||
msgid "default"
|
||||
msgstr ""
|
||||
|
||||
msgid "dir symlink"
|
||||
msgstr ""
|
||||
|
||||
@@ -1929,10 +1946,17 @@ msgid ""
|
||||
"`help %s` will show an online version\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "from command `status language set`"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "from sourcing file %s\n"
|
||||
msgstr "du fichier source %s\n"
|
||||
|
||||
#, c-format
|
||||
msgid "from variable %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "in command substitution\n"
|
||||
msgstr "dans la substitution de commande\n"
|
||||
|
||||
@@ -3515,6 +3539,9 @@ msgstr ""
|
||||
msgid "Show modification time"
|
||||
msgstr "Afficher l’horodatage de modification"
|
||||
|
||||
msgid "Show or change fish's language settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show seconds since the modification time"
|
||||
msgstr ""
|
||||
|
||||
|
||||
27
po/pl.po
27
po/pl.po
@@ -77,6 +77,10 @@ msgstr ""
|
||||
msgid "%s"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: %s: invalid subcommand\n"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n"
|
||||
msgstr ""
|
||||
@@ -875,6 +879,10 @@ msgstr "Przerwij"
|
||||
msgid "Abort (Alias for SIGABRT)"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "Active languages (%s):"
|
||||
msgstr ""
|
||||
|
||||
msgid "Address boundary error"
|
||||
msgstr ""
|
||||
|
||||
@@ -1223,6 +1231,9 @@ msgstr ""
|
||||
msgid "Jobs getting started or continued"
|
||||
msgstr ""
|
||||
|
||||
msgid "Language specifiers appear repeatedly:"
|
||||
msgstr ""
|
||||
|
||||
msgid "List or remove functions"
|
||||
msgstr "Wypisz lub usuń funkcje"
|
||||
|
||||
@@ -1273,6 +1284,9 @@ msgstr "Nigdy"
|
||||
msgid "No TTY for interactive shell (tcgetpgrp failed)"
|
||||
msgstr ""
|
||||
|
||||
msgid "No catalogs available for language specifiers:"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "No matches for wildcard '%s'. See `help %s`."
|
||||
msgstr ""
|
||||
@@ -1769,6 +1783,9 @@ msgstr ""
|
||||
msgid "completion reached maximum recursion depth, possible cycle?"
|
||||
msgstr ""
|
||||
|
||||
msgid "default"
|
||||
msgstr ""
|
||||
|
||||
msgid "dir symlink"
|
||||
msgstr ""
|
||||
|
||||
@@ -1794,10 +1811,17 @@ msgid ""
|
||||
"`help %s` will show an online version\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "from command `status language set`"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "from sourcing file %s\n"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "from variable %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "in command substitution\n"
|
||||
msgstr "w zastępstwie polecenia \n"
|
||||
|
||||
@@ -3380,6 +3404,9 @@ msgstr ""
|
||||
msgid "Show modification time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show or change fish's language settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show seconds since the modification time"
|
||||
msgstr ""
|
||||
|
||||
|
||||
27
po/pt_BR.po
27
po/pt_BR.po
@@ -82,6 +82,10 @@ msgstr ""
|
||||
msgid "%s"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: %s: invalid subcommand\n"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n"
|
||||
msgstr ""
|
||||
@@ -880,6 +884,10 @@ msgstr "Abortado"
|
||||
msgid "Abort (Alias for SIGABRT)"
|
||||
msgstr "Abortado (Outro nome para SIGABRT)"
|
||||
|
||||
#, c-format
|
||||
msgid "Active languages (%s):"
|
||||
msgstr ""
|
||||
|
||||
msgid "Address boundary error"
|
||||
msgstr "Erro de fronteira de endereço (Falha de segmentação)"
|
||||
|
||||
@@ -1228,6 +1236,9 @@ msgstr ""
|
||||
msgid "Jobs getting started or continued"
|
||||
msgstr ""
|
||||
|
||||
msgid "Language specifiers appear repeatedly:"
|
||||
msgstr ""
|
||||
|
||||
msgid "List or remove functions"
|
||||
msgstr "Lista ou remove funções"
|
||||
|
||||
@@ -1278,6 +1289,9 @@ msgstr "Nunca"
|
||||
msgid "No TTY for interactive shell (tcgetpgrp failed)"
|
||||
msgstr "Não há TTY para shell interativo (falha em tcgetpgrp)"
|
||||
|
||||
msgid "No catalogs available for language specifiers:"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "No matches for wildcard '%s'. See `help %s`."
|
||||
msgstr ""
|
||||
@@ -1774,6 +1788,9 @@ msgstr ""
|
||||
msgid "completion reached maximum recursion depth, possible cycle?"
|
||||
msgstr ""
|
||||
|
||||
msgid "default"
|
||||
msgstr ""
|
||||
|
||||
msgid "dir symlink"
|
||||
msgstr ""
|
||||
|
||||
@@ -1799,10 +1816,17 @@ msgid ""
|
||||
"`help %s` will show an online version\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "from command `status language set`"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "from sourcing file %s\n"
|
||||
msgstr "do arquivo %s\n"
|
||||
|
||||
#, c-format
|
||||
msgid "from variable %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "in command substitution\n"
|
||||
msgstr "na substituição de comando\n"
|
||||
|
||||
@@ -3385,6 +3409,9 @@ msgstr ""
|
||||
msgid "Show modification time"
|
||||
msgstr "Mostra horário de modificação"
|
||||
|
||||
msgid "Show or change fish's language settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show seconds since the modification time"
|
||||
msgstr ""
|
||||
|
||||
|
||||
27
po/sv.po
27
po/sv.po
@@ -78,6 +78,10 @@ msgstr ""
|
||||
msgid "%s"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: %s: invalid subcommand\n"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n"
|
||||
msgstr ""
|
||||
@@ -876,6 +880,10 @@ msgstr "Avbrott"
|
||||
msgid "Abort (Alias for SIGABRT)"
|
||||
msgstr "Avbrott (Alias för SIGABRT)"
|
||||
|
||||
#, c-format
|
||||
msgid "Active languages (%s):"
|
||||
msgstr ""
|
||||
|
||||
msgid "Address boundary error"
|
||||
msgstr "Minnesadress korsar segmentgräns"
|
||||
|
||||
@@ -1224,6 +1232,9 @@ msgstr ""
|
||||
msgid "Jobs getting started or continued"
|
||||
msgstr ""
|
||||
|
||||
msgid "Language specifiers appear repeatedly:"
|
||||
msgstr ""
|
||||
|
||||
msgid "List or remove functions"
|
||||
msgstr "Visa eller ta bort funktioner"
|
||||
|
||||
@@ -1274,6 +1285,9 @@ msgstr "Aldrig"
|
||||
msgid "No TTY for interactive shell (tcgetpgrp failed)"
|
||||
msgstr ""
|
||||
|
||||
msgid "No catalogs available for language specifiers:"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "No matches for wildcard '%s'. See `help %s`."
|
||||
msgstr ""
|
||||
@@ -1770,6 +1784,9 @@ msgstr ""
|
||||
msgid "completion reached maximum recursion depth, possible cycle?"
|
||||
msgstr ""
|
||||
|
||||
msgid "default"
|
||||
msgstr ""
|
||||
|
||||
msgid "dir symlink"
|
||||
msgstr ""
|
||||
|
||||
@@ -1795,10 +1812,17 @@ msgid ""
|
||||
"`help %s` will show an online version\n"
|
||||
msgstr ""
|
||||
|
||||
msgid "from command `status language set`"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "from sourcing file %s\n"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "from variable %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "in command substitution\n"
|
||||
msgstr "i kommandosubstitution\n"
|
||||
|
||||
@@ -3381,6 +3405,9 @@ msgstr ""
|
||||
msgid "Show modification time"
|
||||
msgstr "Visa ändringstid"
|
||||
|
||||
msgid "Show or change fish's language settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show seconds since the modification time"
|
||||
msgstr ""
|
||||
|
||||
|
||||
27
po/zh_CN.po
27
po/zh_CN.po
@@ -104,6 +104,10 @@ msgstr "$status 不是有效的命令。参见 `help %s`"
|
||||
msgid "%s"
|
||||
msgstr "%s"
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: %s: invalid subcommand\n"
|
||||
msgstr "%s %s: %s: 无效的子命令\n"
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n"
|
||||
msgstr "%s %s: 名为 %s 且适用于命令 %s 的缩写已存在,无法重命名 %s\n"
|
||||
@@ -902,6 +906,10 @@ msgstr "中止"
|
||||
msgid "Abort (Alias for SIGABRT)"
|
||||
msgstr "中止 (SIGABRT 的别名)"
|
||||
|
||||
#, c-format
|
||||
msgid "Active languages (%s):"
|
||||
msgstr ""
|
||||
|
||||
msgid "Address boundary error"
|
||||
msgstr "地址边界错误"
|
||||
|
||||
@@ -1253,6 +1261,9 @@ msgstr "正在改变状况的作业"
|
||||
msgid "Jobs getting started or continued"
|
||||
msgstr "正在启动或继续的作业"
|
||||
|
||||
msgid "Language specifiers appear repeatedly:"
|
||||
msgstr ""
|
||||
|
||||
msgid "List or remove functions"
|
||||
msgstr "列出或移除函数"
|
||||
|
||||
@@ -1303,6 +1314,9 @@ msgstr "从不"
|
||||
msgid "No TTY for interactive shell (tcgetpgrp failed)"
|
||||
msgstr "交互 shell 没有 TTY (tcgetpgrp 失败)"
|
||||
|
||||
msgid "No catalogs available for language specifiers:"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "No matches for wildcard '%s'. See `help %s`."
|
||||
msgstr "未找到通配符 '%s' 的匹配项。参见 `help %s`。"
|
||||
@@ -1799,6 +1813,9 @@ msgstr "在命令位置不允许替换命令。试使用 var=(你的命令) $var
|
||||
msgid "completion reached maximum recursion depth, possible cycle?"
|
||||
msgstr "补全达到了最大的递归深度,可能存在循环?"
|
||||
|
||||
msgid "default"
|
||||
msgstr ""
|
||||
|
||||
msgid "dir symlink"
|
||||
msgstr "目录符号链接"
|
||||
|
||||
@@ -1827,10 +1844,17 @@ msgstr ""
|
||||
"文档可能未安装。\n"
|
||||
"`help %s` 将显示在线版本\n"
|
||||
|
||||
msgid "from command `status language set`"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "from sourcing file %s\n"
|
||||
msgstr "从源文件 %s\n"
|
||||
|
||||
#, c-format
|
||||
msgid "from variable %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "in command substitution\n"
|
||||
msgstr "在命令替换中\n"
|
||||
|
||||
@@ -3413,6 +3437,9 @@ msgstr "显示与光标下记号相关的 man 页面条目或函数描述"
|
||||
msgid "Show modification time"
|
||||
msgstr "显示修改时间"
|
||||
|
||||
msgid "Show or change fish's language settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show seconds since the modification time"
|
||||
msgstr "显示自修改时间以来的秒数"
|
||||
|
||||
|
||||
27
po/zh_TW.po
27
po/zh_TW.po
@@ -77,6 +77,10 @@ msgstr "$status 不是有效的命令。參見「help %s」"
|
||||
msgid "%s"
|
||||
msgstr "%s"
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: %s: invalid subcommand\n"
|
||||
msgstr "%s %s:%s:無效的子命令\n"
|
||||
|
||||
#, c-format
|
||||
msgid "%s %s: Abbreviation %s already exists for commands %s, cannot rename %s\n"
|
||||
msgstr "%s %s:名為 %s 且適用於命令 %s 的縮寫已存在,無法重新命名 %s\n"
|
||||
@@ -876,6 +880,10 @@ msgstr "中止"
|
||||
msgid "Abort (Alias for SIGABRT)"
|
||||
msgstr "中止(SIGABRT 的別名)"
|
||||
|
||||
#, c-format
|
||||
msgid "Active languages (%s):"
|
||||
msgstr ""
|
||||
|
||||
msgid "Address boundary error"
|
||||
msgstr "位址邊界錯誤"
|
||||
|
||||
@@ -1227,6 +1235,9 @@ msgstr "作業變更狀態"
|
||||
msgid "Jobs getting started or continued"
|
||||
msgstr "作業開始或繼續"
|
||||
|
||||
msgid "Language specifiers appear repeatedly:"
|
||||
msgstr ""
|
||||
|
||||
msgid "List or remove functions"
|
||||
msgstr "列出或移除函式"
|
||||
|
||||
@@ -1277,6 +1288,9 @@ msgstr "永不"
|
||||
msgid "No TTY for interactive shell (tcgetpgrp failed)"
|
||||
msgstr "沒有互動式 shell 所需的 TTY(tcgetpgrp 失敗了)"
|
||||
|
||||
msgid "No catalogs available for language specifiers:"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "No matches for wildcard '%s'. See `help %s`."
|
||||
msgstr "wildcard「%s」無匹配項目。參見「help %s」。"
|
||||
@@ -1774,6 +1788,9 @@ msgstr "不允許在命令處進行命令替換。試試 var=(命令) $var ..."
|
||||
msgid "completion reached maximum recursion depth, possible cycle?"
|
||||
msgstr "補全達到最大遞迴深度,可能是循環?"
|
||||
|
||||
msgid "default"
|
||||
msgstr ""
|
||||
|
||||
msgid "dir symlink"
|
||||
msgstr "目錄象徵式連結"
|
||||
|
||||
@@ -1802,10 +1819,17 @@ msgstr ""
|
||||
"可能未安裝文件。\n"
|
||||
"「help %s」將顯示線上版本\n"
|
||||
|
||||
msgid "from command `status language set`"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "from sourcing file %s\n"
|
||||
msgstr "在載入的檔案 %s\n"
|
||||
|
||||
#, c-format
|
||||
msgid "from variable %s"
|
||||
msgstr ""
|
||||
|
||||
msgid "in command substitution\n"
|
||||
msgstr "在命令替換\n"
|
||||
|
||||
@@ -3388,6 +3412,9 @@ msgstr "顯示和游標處的詞元有關的手冊頁或函式說明"
|
||||
msgid "Show modification time"
|
||||
msgstr "顯示修改時間"
|
||||
|
||||
msgid "Show or change fish's language settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show seconds since the modification time"
|
||||
msgstr "顯示自修改時間以來的秒數"
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ set -l __fish_status_all_commands \
|
||||
is-login \
|
||||
is-no-job-control \
|
||||
job-control \
|
||||
language \
|
||||
line-number \
|
||||
list-files \
|
||||
print-stack-trace \
|
||||
@@ -80,3 +81,19 @@ complete -f -c status -n "__fish_seen_subcommand_from job-control" -a none -d "S
|
||||
|
||||
complete -f -c status -n "__fish_seen_subcommand_from get-file" -a '(status list-files 2>/dev/null)'
|
||||
complete -f -c status -n "__fish_seen_subcommand_from list-files" -a '(status list-files 2>/dev/null)'
|
||||
|
||||
# Tests equality between the command line with the first item removed
|
||||
# and the function's arguments.
|
||||
function __fish_status_is_exact_subcommand
|
||||
set -l line (commandline -pxc)[2..]
|
||||
test "$line" = "$argv"
|
||||
end
|
||||
# Tests if the command line with the first item removed starts with the provided arguments.
|
||||
function __fish_status_is_subcommand_prefix
|
||||
set -l prefix (string escape --style=regex -- (string join -- ' ' $argv))
|
||||
set -l line (string join -- ' ' (commandline -pxc)[2..])
|
||||
string match -rq -- "^$prefix" $line
|
||||
end
|
||||
complete -f -c status -n "not __fish_seen_subcommand_from $__fish_status_all_commands" -a language -d "Show or change fish's language settings"
|
||||
complete -f -c status -n "__fish_status_is_exact_subcommand language" -a "(echo list-available\tShow languages usable with \'status language set\'\nset\tSet the language\(s\) used for fish\'s messages\nunset\tUndo effects of \'status language set\'\n)"
|
||||
complete -f -c status -n "__fish_status_is_subcommand_prefix language set" -a "(status language list-available)"
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
pub BUILTIN_ERR_INVALID_SUBCMD
|
||||
"%s: %s: invalid subcommand\n"
|
||||
|
||||
pub BUILTIN_ERR_INVALID_SUBSUBCMD
|
||||
"%s %s: %s: invalid subcommand\n"
|
||||
|
||||
/// Error messages for unexpected args.
|
||||
pub BUILTIN_ERR_ARG_COUNT0
|
||||
"%s: missing argument\n"
|
||||
|
||||
@@ -75,6 +75,7 @@ fn to_wstr(self) -> &'static wstr {
|
||||
(STATUS_IS_NO_JOB_CTRL, "is-no-job-control"),
|
||||
(STATUS_LINE_NUMBER, "line-number", "current-line-number"),
|
||||
(STATUS_LIST_FILES, "list-files"),
|
||||
(STATUS_LANGUAGE, "language"),
|
||||
(STATUS_SET_JOB_CONTROL, "job-control"),
|
||||
(STATUS_STACK_TRACE, "stack-trace", "print-stack-trace"),
|
||||
(STATUS_TERMINAL, "terminal"),
|
||||
@@ -303,7 +304,7 @@ fn parse_cmd_opts(
|
||||
|
||||
*optind = w.wopt_index;
|
||||
|
||||
return Ok(SUCCESS);
|
||||
Ok(SUCCESS)
|
||||
}
|
||||
|
||||
struct EmptyEmbed;
|
||||
@@ -489,6 +490,47 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
streams.out.append(src);
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
STATUS_LANGUAGE => {
|
||||
cfg_if! {
|
||||
if #[cfg(not(feature = "localize-messages"))] {
|
||||
streams.err.append(L!("fish was built with the `localize-messages` feature disabled. The `status language` command is unavailable.\n"));
|
||||
return Err(STATUS_CMD_ERROR);
|
||||
} else {
|
||||
if args.is_empty() {
|
||||
streams.out.append(crate::wutil::gettext::status_language());
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
match args[0].to_string().as_str() {
|
||||
"list-available" => {
|
||||
streams.out.append(crate::wutil::gettext::list_available_languages());
|
||||
return Ok(SUCCESS);
|
||||
},
|
||||
"set" => {
|
||||
let langs = args[1..]
|
||||
.iter()
|
||||
.map(|lang| lang.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let lints = crate::wutil::gettext::update_from_status_language_builtin(&langs);
|
||||
let formatted_lints = lints.display_all();
|
||||
if !formatted_lints.is_empty() {
|
||||
streams.err.append(&formatted_lints);
|
||||
}
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
"unset" => {
|
||||
crate::wutil::gettext::unset_from_status_language_builtin(parser.vars());
|
||||
return Ok(SUCCESS);
|
||||
}
|
||||
invalid => {
|
||||
streams
|
||||
.err
|
||||
.append(wgettext_fmt!(BUILTIN_ERR_INVALID_SUBSUBCMD, cmd, subcmd.to_wstr(), invalid));
|
||||
return Err(STATUS_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
STATUS_LIST_FILES => {
|
||||
use crate::util::wcsfilecmp_glob;
|
||||
let mut paths = vec![];
|
||||
@@ -733,6 +775,7 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
|
||||
| STATUS_TEST_FEATURE
|
||||
| STATUS_GET_FILE
|
||||
| STATUS_LIST_FILES
|
||||
| STATUS_LANGUAGE
|
||||
| STATUS_TEST_TERMINAL_FEATURE => {
|
||||
unreachable!("")
|
||||
}
|
||||
|
||||
@@ -540,7 +540,7 @@ fn init_locale(vars: &EnvStack) {
|
||||
invalidate_numeric_locale();
|
||||
|
||||
#[cfg(feature = "localize-messages")]
|
||||
crate::wutil::gettext::update_locale_from_env(vars);
|
||||
crate::wutil::gettext::update_from_env(vars);
|
||||
}
|
||||
|
||||
pub fn use_posix_spawn() -> bool {
|
||||
|
||||
@@ -1,19 +1,195 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[cfg(feature = "localize-messages")]
|
||||
use crate::env::EnvStack;
|
||||
use crate::env::{EnvStack, Environment};
|
||||
use crate::wchar::prelude::*;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
#[cfg(feature = "localize-messages")]
|
||||
mod gettext_impl {
|
||||
use crate::env::{EnvStack, Environment};
|
||||
use fish_gettext_maps::CATALOGS;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{collections::HashSet, sync::Mutex};
|
||||
|
||||
type Catalog = &'static phf::Map<&'static str, &'static str>;
|
||||
|
||||
pub struct SetLanguageLints<'a> {
|
||||
pub duplicates: Vec<&'a str>,
|
||||
pub non_existing: Vec<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum LanguagePrecedenceOrigin {
|
||||
Default,
|
||||
LocaleVariable(LocaleVariable),
|
||||
LanguageEnvVar,
|
||||
StatusLanguage,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum LocaleVariable {
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
LANG,
|
||||
LC_MESSAGES,
|
||||
LC_ALL,
|
||||
}
|
||||
|
||||
impl LocaleVariable {
|
||||
fn as_language_precedence_origin(&self) -> LanguagePrecedenceOrigin {
|
||||
LanguagePrecedenceOrigin::LocaleVariable(*self)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::LANG => "LANG",
|
||||
Self::LC_MESSAGES => "LC_MESSAGES",
|
||||
Self::LC_ALL => "LC_ALL",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LocaleVariable {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
struct InternalLocalizationState {
|
||||
precedence_origin: LanguagePrecedenceOrigin,
|
||||
language_precedence: Vec<(String, Catalog)>,
|
||||
}
|
||||
|
||||
pub struct PublicLocalizationState {
|
||||
pub precedence_origin: LanguagePrecedenceOrigin,
|
||||
pub language_precedence: Vec<String>,
|
||||
}
|
||||
|
||||
/// Stores the current localization status.
|
||||
/// `is_active` indicates whether localization is currently active, and the reason if it is
|
||||
/// not.
|
||||
/// The `origin` indicates where the values in `language_precedence` were taken from.
|
||||
/// `language_precedence` stores the catalogs in the order they should be used.
|
||||
///
|
||||
/// This struct should be updated when the relevant variables change or `status language` is used
|
||||
/// to modify the localization state.
|
||||
static LOCALIZATION_STATE: Lazy<Mutex<InternalLocalizationState>> =
|
||||
Lazy::new(|| Mutex::new(InternalLocalizationState::new()));
|
||||
|
||||
impl InternalLocalizationState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
precedence_origin: LanguagePrecedenceOrigin::Default,
|
||||
language_precedence: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn to_public(&self) -> PublicLocalizationState {
|
||||
PublicLocalizationState {
|
||||
precedence_origin: self.precedence_origin,
|
||||
language_precedence: self
|
||||
.language_precedence
|
||||
.iter()
|
||||
.map(|(lang, _)| lang.to_owned())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_from_env(
|
||||
&mut self,
|
||||
message_locale: Option<(LocaleVariable, String)>,
|
||||
language_var: Option<Vec<String>>,
|
||||
) {
|
||||
// Do not override values set via `status language`.
|
||||
if self.precedence_origin == LanguagePrecedenceOrigin::StatusLanguage {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((precedence_origin, locale)) = &message_locale {
|
||||
// Regular locale names start with lowercase letters (`ll_CC`, followed by some suffix).
|
||||
// The C or POSIX locale is special, and often used to disable localization.
|
||||
// Their names are upper-case, but variants with suffixes (`C.UTF-8`) exist.
|
||||
// To ensure that such variants are accounted for, we match on prefixes of the
|
||||
// locale name.
|
||||
// https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_02
|
||||
fn is_c_locale(locale: &str) -> bool {
|
||||
locale.starts_with('C') || locale.starts_with("POSIX")
|
||||
}
|
||||
if is_c_locale(locale) {
|
||||
self.precedence_origin =
|
||||
LanguagePrecedenceOrigin::LocaleVariable(*precedence_origin);
|
||||
self.language_precedence.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let (precedence_origin, language_list) = if let Some(list) = language_var {
|
||||
(LanguagePrecedenceOrigin::LanguageEnvVar, list)
|
||||
} else if let Some((precedence_origin, locale)) = message_locale {
|
||||
let mut normalized_name = String::new();
|
||||
// Strip off encoding and modifier. (We always expect UTF-8 and don't support modifiers.)
|
||||
for c in locale.chars() {
|
||||
if c.is_alphabetic() || c == '_' {
|
||||
normalized_name.push(c);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// At this point, the normalized_name should have the shape `ll` or `ll_CC`.
|
||||
(
|
||||
precedence_origin.as_language_precedence_origin(),
|
||||
vec![normalized_name],
|
||||
)
|
||||
} else {
|
||||
(LanguagePrecedenceOrigin::Default, vec![])
|
||||
};
|
||||
|
||||
let mut seen_languages = HashSet::new();
|
||||
self.language_precedence = language_list
|
||||
.into_iter()
|
||||
.flat_map(|lang| find_existing_catalogs(&lang))
|
||||
.filter(|(lang, _)| seen_languages.insert(lang.to_owned()))
|
||||
.collect();
|
||||
self.precedence_origin = precedence_origin;
|
||||
}
|
||||
|
||||
fn update_from_status_language_builtin<'a, 'b: 'a, S: AsRef<str> + 'a>(
|
||||
&mut self,
|
||||
langs: &'b [S],
|
||||
) -> SetLanguageLints<'a> {
|
||||
let mut seen = HashSet::new();
|
||||
let mut duplicates = vec![];
|
||||
for lang in langs {
|
||||
let lang = lang.as_ref();
|
||||
if !seen.insert(lang) {
|
||||
duplicates.push(lang)
|
||||
}
|
||||
}
|
||||
let mut existing_langs = vec![];
|
||||
let mut non_existing = vec![];
|
||||
for lang in langs {
|
||||
let lang = lang.as_ref();
|
||||
if let Some(catalog) = CATALOGS.get(lang) {
|
||||
existing_langs.push((lang.to_owned(), *catalog));
|
||||
} else {
|
||||
non_existing.push(lang);
|
||||
}
|
||||
}
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
let unique_langs = existing_langs
|
||||
.into_iter()
|
||||
.filter(|(lang, _)| seen.insert(lang.to_owned()))
|
||||
.collect();
|
||||
self.language_precedence = unique_langs;
|
||||
self.precedence_origin = LanguagePrecedenceOrigin::StatusLanguage;
|
||||
|
||||
SetLanguageLints {
|
||||
duplicates,
|
||||
non_existing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to find catalogs for `language`.
|
||||
/// `language` must be an ISO 639 language code, optionally followed by an underscore and an ISO
|
||||
/// 3166 country/territory code.
|
||||
@@ -52,125 +228,220 @@ fn find_existing_catalogs(language: &str) -> Vec<(String, Catalog)> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The precedence list of user-preferred languages, obtained from the relevant environment
|
||||
/// variables.
|
||||
/// This should be updated when the relevant variables change.
|
||||
static LANGUAGE_PRECEDENCE: Lazy<Mutex<Vec<Catalog>>> = Lazy::new(|| Mutex::new(Vec::new()));
|
||||
|
||||
/// Four environment variables can be used to select languages.
|
||||
/// A detailed description is available at
|
||||
/// <https://www.gnu.org/software/gettext/manual/html_node/Setting-the-POSIX-Locale.html>
|
||||
/// Our does not replicate the behavior exactly.
|
||||
/// See the following description.
|
||||
///
|
||||
/// There are three variables which can be used for setting the locale for messages:
|
||||
/// 1. `LC_ALL`
|
||||
/// 2. `LC_MESSAGES`
|
||||
/// 3. `LANG`
|
||||
///
|
||||
/// The value of the first one set to a non-zero value will be considered.
|
||||
/// If it is set to the `C` locale (we consider any value starting with `C` as the `C` locale),
|
||||
/// localization will be disabled.
|
||||
/// Otherwise, the variable `LANGUAGE` is checked. If it is non-empty, it is considered a
|
||||
/// colon-separated list of languages. Languages are listed with descending priority, meaning
|
||||
/// we will localize each message into the first language with a localization available.
|
||||
/// Each language is specified by a 2 or 3 letter ISO 639 language code, optionally followed by
|
||||
/// an underscore and an ISO 3166 country/territory code. If the second part is omitted, some
|
||||
/// variant of the language will be used if localizations exist for one. We make no guarantees
|
||||
/// about which variant that will be.
|
||||
/// In addition to the colon-separated format, using a list with one language per element is
|
||||
/// also supported.
|
||||
///
|
||||
/// Returns the (possibly empty) preference list of languages.
|
||||
fn get_language_preferences_from_env(vars: &EnvStack) -> Vec<String> {
|
||||
use crate::wchar::L;
|
||||
|
||||
fn normalize_locale_name(locale: &str) -> String {
|
||||
// Strips off the encoding and modifier parts.
|
||||
let mut normalized_name = String::new();
|
||||
// Strip off encoding and modifier. (We always expect UTF-8 and don't support modifiers.)
|
||||
for c in locale.chars() {
|
||||
if c.is_alphabetic() || c == '_' {
|
||||
normalized_name.push(c);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// At this point, the normalized_name should have the shape `ll` or `ll_CC`.
|
||||
normalized_name
|
||||
}
|
||||
|
||||
fn check_language_var(vars: &EnvStack) -> Option<Vec<String>> {
|
||||
let langs = vars.get(L!("LANGUAGE"))?;
|
||||
let langs = langs.as_list();
|
||||
let filtered_langs: Vec<String> = langs
|
||||
.iter()
|
||||
.filter(|lang| !lang.is_empty())
|
||||
.map(|lang| normalize_locale_name(&lang.to_string()))
|
||||
.collect();
|
||||
if filtered_langs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(filtered_langs)
|
||||
}
|
||||
|
||||
// Locale value is determined by the first of these three variables set to a non-zero
|
||||
// value.
|
||||
if let Some(locale) = vars
|
||||
.get(L!("LC_ALL"))
|
||||
.or_else(|| vars.get(L!("LC_MESSAGES")).or_else(|| vars.get(L!("LANG"))))
|
||||
{
|
||||
let locale = locale.as_string().to_string();
|
||||
if locale.starts_with('C') {
|
||||
// Do not localize in C locale.
|
||||
return vec![];
|
||||
}
|
||||
// `LANGUAGE` has higher precedence than the locale value.
|
||||
if let Some(precedence_list) = check_language_var(vars) {
|
||||
return precedence_list;
|
||||
}
|
||||
// Use the locale value if `LANGUAGE` is not set.
|
||||
vec![normalize_locale_name(&locale)]
|
||||
} else if let Some(precedence_list) = check_language_var(vars) {
|
||||
// Use the `LANGUAGE` value if locale is not set.
|
||||
return precedence_list;
|
||||
} else {
|
||||
// None of the relevant variables are set, so we will not localize.
|
||||
vec![]
|
||||
}
|
||||
pub(super) fn update_from_env(
|
||||
locale: Option<(LocaleVariable, String)>,
|
||||
language_var: Option<Vec<String>>,
|
||||
) {
|
||||
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
localization_state.update_from_env(locale, language_var);
|
||||
}
|
||||
|
||||
/// Implementation of the function with the same name in super.
|
||||
pub(super) fn update_locale_from_env(vars: &EnvStack) {
|
||||
let mut seen_languages = HashSet::new();
|
||||
let mut language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();
|
||||
*language_precedence = get_language_preferences_from_env(vars)
|
||||
.into_iter()
|
||||
.flat_map(|lang| find_existing_catalogs(&lang))
|
||||
.filter(|(lang, _)| seen_languages.insert(lang.to_owned()))
|
||||
.map(|(_, catalog)| catalog)
|
||||
.collect();
|
||||
pub(super) fn update_from_status_language_builtin<'a, 'b: 'a, S: AsRef<str> + 'a>(
|
||||
langs: &'b [S],
|
||||
) -> SetLanguageLints<'a> {
|
||||
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
localization_state.update_from_status_language_builtin(langs)
|
||||
}
|
||||
|
||||
pub(super) fn unset_from_status_language_builtin(
|
||||
locale: Option<(LocaleVariable, String)>,
|
||||
language_var: Option<Vec<String>>,
|
||||
) {
|
||||
let mut localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
localization_state.precedence_origin = LanguagePrecedenceOrigin::Default;
|
||||
localization_state.update_from_env(locale, language_var);
|
||||
}
|
||||
|
||||
pub(super) fn status_language() -> PublicLocalizationState {
|
||||
let localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
localization_state.to_public()
|
||||
}
|
||||
|
||||
pub(super) fn gettext(message_str: &'static str) -> Option<&'static str> {
|
||||
let language_precedence = LANGUAGE_PRECEDENCE.lock().unwrap();
|
||||
let localization_state = LOCALIZATION_STATE.lock().unwrap();
|
||||
|
||||
// Use the localization from the highest-precedence language that has one available.
|
||||
for catalog in language_precedence.iter() {
|
||||
for (_, catalog) in localization_state.language_precedence.iter() {
|
||||
if let Some(localized_str) = catalog.get(message_str) {
|
||||
return Some(localized_str);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn list_available_languages() -> Vec<&'static str> {
|
||||
let mut langs: Vec<_> = CATALOGS.entries().map(|(&lang, _)| lang).collect();
|
||||
langs.sort();
|
||||
langs
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "localize-messages")]
|
||||
fn get_message_locale(vars: &EnvStack) -> Option<(gettext_impl::LocaleVariable, String)> {
|
||||
use gettext_impl::LocaleVariable;
|
||||
let get = |var_str: &wstr, var: LocaleVariable| {
|
||||
vars.get_unless_empty(var_str)
|
||||
.map(|val| (var, val.as_string().to_string()))
|
||||
};
|
||||
get(L!("LC_ALL"), LocaleVariable::LC_ALL)
|
||||
.or_else(|| get(L!("LC_MESSAGES"), LocaleVariable::LC_MESSAGES))
|
||||
.or_else(|| get(L!("LANG"), LocaleVariable::LANG))
|
||||
}
|
||||
|
||||
#[cfg(feature = "localize-messages")]
|
||||
fn get_language_var(vars: &EnvStack) -> Option<Vec<String>> {
|
||||
let langs = vars.get_unless_empty(L!("LANGUAGE"))?;
|
||||
let langs = langs.as_list();
|
||||
let filtered_langs: Vec<String> = langs
|
||||
.iter()
|
||||
.filter(|lang| !lang.is_empty())
|
||||
.map(|lang| lang.to_string())
|
||||
.collect();
|
||||
if filtered_langs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(filtered_langs)
|
||||
}
|
||||
|
||||
/// Call this when one of `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, `LANG` changes.
|
||||
/// Updates internal state such that the correct localizations will be used in subsequent
|
||||
/// localization requests.
|
||||
///
|
||||
/// For deciding how to localize, the following is done:
|
||||
///
|
||||
/// 1. If the language precedence was set via `status language`, env vars are ignored.
|
||||
/// 2. Check the first non-empty value of the env vars `LC_ALL`, `LC_MESSAGES`, `LANG`. If it
|
||||
/// starts with `C` we consider this a C locale and disable localization.
|
||||
/// 3. Otherwise, the value of the `LANGUAGE` env var is used, if non-empty. This allows specifying
|
||||
/// multiple languages, with languages specified first taking precedence, e.g.
|
||||
/// `LANGUAGE=zh_TW:zh_CN:pt_BR`
|
||||
/// 4. Otherwise, the first non-empty value of the env vars `LC_ALL`, `LC_MESSAGES`, `LANG` is
|
||||
/// used. This can only specify a single language, e.g. `LANG=de_AT.UTF-8`.
|
||||
/// There, we normalize locale names by stripping off the suffix, leaving only the `ll_CC` part.
|
||||
/// 5. Otherwise, localization will not happen.
|
||||
///
|
||||
/// If users specify `ll_CC` as a language and we don't have a catalog for this language, but we
|
||||
/// have one for `ll`, that will be used instead. If users specify `ll` (without specifying a
|
||||
/// language variant), which we discourage, and we don't have a catalog for `ll`, but we do have
|
||||
/// one for `ll_CC`, that will be used as a fallback. If we have multiple `ll_*` catalogs, all of
|
||||
/// them will be used, in arbitrary order.
|
||||
#[cfg(feature = "localize-messages")]
|
||||
pub fn update_locale_from_env(vars: &EnvStack) {
|
||||
gettext_impl::update_locale_from_env(vars);
|
||||
pub fn update_from_env(vars: &EnvStack) {
|
||||
gettext_impl::update_from_env(get_message_locale(vars), get_language_var(vars));
|
||||
}
|
||||
|
||||
#[cfg(feature = "localize-messages")]
|
||||
fn append_space_separated_list<S: AsRef<str>>(
|
||||
string: &mut WString,
|
||||
list: impl IntoIterator<Item = S>,
|
||||
) {
|
||||
for lang in list.into_iter() {
|
||||
string.push(' ');
|
||||
string.push_utfstr(&crate::common::escape(
|
||||
WString::from_str(lang.as_ref()).as_utfstr(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "localize-messages")]
|
||||
pub struct SetLanguageLints<'a> {
|
||||
duplicates: Vec<&'a str>,
|
||||
non_existing: Vec<&'a str>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "localize-messages")]
|
||||
impl<'a> From<gettext_impl::SetLanguageLints<'a>> for SetLanguageLints<'a> {
|
||||
fn from(lints: gettext_impl::SetLanguageLints<'a>) -> Self {
|
||||
Self {
|
||||
duplicates: lints.duplicates,
|
||||
non_existing: lints.non_existing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "localize-messages")]
|
||||
impl<'a> SetLanguageLints<'a> {
|
||||
pub fn display_duplicates(&self) -> WString {
|
||||
let mut result = WString::new();
|
||||
if self.duplicates.is_empty() {
|
||||
return result;
|
||||
}
|
||||
result.push_utfstr(wgettext!("Language specifiers appear repeatedly:"));
|
||||
append_space_separated_list(&mut result, &self.duplicates);
|
||||
result.push('\n');
|
||||
result
|
||||
}
|
||||
|
||||
pub fn display_non_existing(&self) -> WString {
|
||||
let mut result = WString::new();
|
||||
if self.non_existing.is_empty() {
|
||||
return result;
|
||||
}
|
||||
result.push_utfstr(wgettext!("No catalogs available for language specifiers:"));
|
||||
append_space_separated_list(&mut result, &self.non_existing);
|
||||
result.push('\n');
|
||||
result
|
||||
}
|
||||
|
||||
pub fn display_all(&self) -> WString {
|
||||
let mut result = WString::new();
|
||||
result.push_utfstr(&self.display_duplicates());
|
||||
result.push_utfstr(&self.display_non_existing());
|
||||
result
|
||||
}
|
||||
}
|
||||
/// Call this when the `status language` builtin should update the language precedence.
|
||||
/// `langs` should be the list of languages the precedence should be set to.
|
||||
#[cfg(feature = "localize-messages")]
|
||||
pub fn update_from_status_language_builtin<'a, 'b: 'a, S: AsRef<str> + 'a>(
|
||||
langs: &'b [S],
|
||||
) -> SetLanguageLints<'a> {
|
||||
gettext_impl::update_from_status_language_builtin(langs).into()
|
||||
}
|
||||
|
||||
#[cfg(feature = "localize-messages")]
|
||||
pub fn unset_from_status_language_builtin(vars: &EnvStack) {
|
||||
gettext_impl::unset_from_status_language_builtin(
|
||||
get_message_locale(vars),
|
||||
get_language_var(vars),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "localize-messages")]
|
||||
pub fn status_language() -> WString {
|
||||
use gettext_impl::LanguagePrecedenceOrigin;
|
||||
let localization_state = gettext_impl::status_language();
|
||||
let mut result = WString::new();
|
||||
localizable_consts!(
|
||||
LANGUAGE_LIST_VARIABLE_ORIGIN "from variable %s"
|
||||
);
|
||||
let origin_string = match localization_state.precedence_origin {
|
||||
LanguagePrecedenceOrigin::Default => wgettext!("default").to_owned(),
|
||||
LanguagePrecedenceOrigin::LocaleVariable(var) => {
|
||||
wgettext_fmt!(LANGUAGE_LIST_VARIABLE_ORIGIN, var.as_str())
|
||||
}
|
||||
LanguagePrecedenceOrigin::LanguageEnvVar => {
|
||||
wgettext_fmt!(LANGUAGE_LIST_VARIABLE_ORIGIN, "LANGUAGE")
|
||||
}
|
||||
LanguagePrecedenceOrigin::StatusLanguage => {
|
||||
wgettext!("from command `status language set`").to_owned()
|
||||
}
|
||||
};
|
||||
result.push_utfstr(&wgettext_fmt!("Active languages (%s):", origin_string));
|
||||
append_space_separated_list(&mut result, &localization_state.language_precedence);
|
||||
result.push('\n');
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(feature = "localize-messages")]
|
||||
pub fn list_available_languages() -> WString {
|
||||
let mut languages = WString::new();
|
||||
for lang in gettext_impl::list_available_languages() {
|
||||
languages.push_str(lang);
|
||||
languages.push('\n');
|
||||
}
|
||||
languages
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "localize-messages"))]
|
||||
@@ -180,13 +451,13 @@ pub fn initialize_gettext() {}
|
||||
/// available. Without this, early error messages cannot be localized.
|
||||
#[cfg(feature = "localize-messages")]
|
||||
pub fn initialize_gettext() {
|
||||
let locale_vars = EnvStack::new();
|
||||
env_stack_set_from_env!(locale_vars, "LANGUAGE");
|
||||
env_stack_set_from_env!(locale_vars, "LC_ALL");
|
||||
env_stack_set_from_env!(locale_vars, "LC_MESSAGES");
|
||||
env_stack_set_from_env!(locale_vars, "LANG");
|
||||
let vars = EnvStack::new();
|
||||
env_stack_set_from_env!(vars, "LANGUAGE");
|
||||
env_stack_set_from_env!(vars, "LC_ALL");
|
||||
env_stack_set_from_env!(vars, "LC_MESSAGES");
|
||||
env_stack_set_from_env!(vars, "LANG");
|
||||
|
||||
gettext_impl::update_locale_from_env(&locale_vars);
|
||||
gettext_impl::update_from_env(get_message_locale(&vars), get_language_var(&vars));
|
||||
}
|
||||
|
||||
/// Use this function to localize a message.
|
||||
|
||||
@@ -55,6 +55,12 @@ begin
|
||||
set -l LANG C
|
||||
echo (_ file)
|
||||
# CHECK: file
|
||||
set -l LANG POSIX
|
||||
echo (_ file)
|
||||
# CHECK: file
|
||||
set -l LANG C.UTF-8
|
||||
echo (_ file)
|
||||
# CHECK: file
|
||||
end
|
||||
echo (_ file)
|
||||
# CHECK: arquivo
|
||||
@@ -73,6 +79,13 @@ end
|
||||
echo (_ file)
|
||||
# CHECK: arquivo
|
||||
|
||||
# Check that empty vars are ignored
|
||||
begin
|
||||
set -l LC_ALL
|
||||
echo (_ file)
|
||||
# CHECK: arquivo
|
||||
end
|
||||
|
||||
# Check that all relevant locale variables are respected.
|
||||
set --erase LANG
|
||||
set --erase LC_MESSAGES
|
||||
@@ -104,3 +117,71 @@ echo (_ file)
|
||||
set -l LC_ALL de_DE.utf8
|
||||
echo (_ file)
|
||||
# CHECK: Datei
|
||||
|
||||
# Check `status language` builtin
|
||||
set --erase LANG
|
||||
set --erase LC_MESSAGES
|
||||
set --erase LC_ALL
|
||||
set --erase LANGUAGE
|
||||
status language
|
||||
# CHECK: Active languages (default):
|
||||
echo (_ file)
|
||||
# CHECK: file
|
||||
|
||||
set -l LANGUAGE pt_BR de_DE
|
||||
status language
|
||||
# CHECK: Active languages (from variable LANGUAGE): pt_BR de
|
||||
echo (_ file)
|
||||
# CHECK: arquivo
|
||||
|
||||
# We have fr but not fr_FR. For the builtin command, only exact matches are allowed.
|
||||
status language set fr_FR de pt_BR
|
||||
# CHECKERR: No catalogs available for language specifiers: fr_FR
|
||||
status language
|
||||
# CHECK: Active languages (from command `status language set`): de pt_BR
|
||||
echo (_ file)
|
||||
# CHECK: Datei
|
||||
|
||||
set -l LANGUAGE zh_TW
|
||||
status language
|
||||
# CHECK: Active languages (from command `status language set`): de pt_BR
|
||||
echo (_ file)
|
||||
# CHECK: Datei
|
||||
|
||||
set -l LC_MESSAGES C
|
||||
status language
|
||||
# CHECK: Active languages (from command `status language set`): de pt_BR
|
||||
echo (_ file)
|
||||
# CHECK: Datei
|
||||
|
||||
status language unset
|
||||
status language
|
||||
# CHECK: Active languages (from variable LC_MESSAGES):
|
||||
echo (_ file)
|
||||
# CHECK: file
|
||||
|
||||
set --erase LC_MESSAGES
|
||||
status language
|
||||
# CHECK: Active languages (from variable LANGUAGE): zh_TW
|
||||
echo (_ file)
|
||||
# CHECK: 檔案
|
||||
|
||||
set --erase LANGUAGE
|
||||
status language
|
||||
# CHECK: Active languages (default):
|
||||
echo (_ file)
|
||||
# CHECK: file
|
||||
|
||||
# Check `status language set` warnings
|
||||
status language set asdf
|
||||
# CHECKERR: No catalogs available for language specifiers: asdf
|
||||
|
||||
# This will have to be changed if we add catalogs for languages used here.
|
||||
status language set zh_HK it_IT
|
||||
# CHECKERR: No catalogs available for language specifiers: zh_HK it_IT
|
||||
|
||||
status language set de de
|
||||
# CHECKERR: Language specifiers appear repeatedly: de
|
||||
|
||||
status language set \xff quote\"
|
||||
# CHECKERR: No catalogs available for language specifiers: \Xff 'quote"'
|
||||
|
||||
Reference in New Issue
Block a user