Appendix I. Localization

Localization is an undocumented Bash feature.

A localized shell script echoes its text output in the language defined as the system's locale. A Linux user in Berlin, Germany, would get script output in German, whereas his cousin in Berlin, Maryland, would get output from the same script in English.

To create a localized script, use the following template to write all messages to the user (error messages, prompts, etc.).

   1 #!/bin/bash
   2 # localized.sh
   3 #  Script by Stéphane Chazelas,
   4 #+ modified by Bruno Haible, bugfixed by Alfredo Pironti.
   5 
   6 . gettext.sh
   7 
   8 E_CDERROR=65
   9 
  10 error()
  11 {
  12   printf "$@" >&2
  13   exit $E_CDERROR
  14 }
  15 
  16 cd $var || error "`eval_gettext \"Can\'t cd to \\\$var.\"`"
  17 #  The triple backslashes (escapes) in front of $var needed
  18 #+ "because eval_gettext expects a string
  19 #+ where the variable values have not yet been substituted."
  20 #    -- per Bruno Haible
  21 read -p "`gettext \"Enter the value: \"`" var
  22 #  ...
  23 
  24 
  25 #  ------------------------------------------------------------------
  26 #  Alfredo Pironti comments:
  27 
  28 #  This script has been modified to not use the $"..." syntax in
  29 #+ favor of the "`gettext \"...\"`" syntax.
  30 #  This is ok, but with the new localized.sh program, the commands
  31 #+ "bash -D filename" and "bash --dump-po-string filename"
  32 #+ will produce no output
  33 #+ (because those command are only searching for the $"..." strings)!
  34 #  The ONLY way to extract strings from the new file is to use the
  35 # 'xgettext' program. However, the xgettext program is buggy.
  36 
  37 # Note that 'xgettext' has another bug.
  38 #
  39 # The shell fragment:
  40 #    gettext -s "I like Bash"
  41 # will be correctly extracted, but . . .
  42 #    xgettext -s "I like Bash"
  43 # . . . fails!
  44 #  'xgettext' will extract "-s" because
  45 #+ the command only extracts the
  46 #+ very first argument after the 'gettext' word.
  47 
  48 
  49 #  Escape characters:
  50 #
  51 #  To localize a sentence like
  52 #     echo -e "Hello\tworld!"
  53 #+ you must use
  54 #     echo -e "`gettext \"Hello\\tworld\"`"
  55 #  The "double escape character" before the `t' is needed because
  56 #+ 'gettext' will search for a string like: 'Hello\tworld'
  57 #  This is because gettext will read one literal `\')
  58 #+ and will output a string like "Bonjour\tmonde",
  59 #+ so the 'echo' command will display the message correctly.
  60 #
  61 #  You may not use
  62 #     echo "`gettext -e \"Hello\tworld\"`"
  63 #+ due to the xgettext bug explained above.
  64 
  65 
  66 
  67 # Let's localize the following shell fragment:
  68 #     echo "-h display help and exit"
  69 #
  70 # First, one could do this:
  71 #     echo "`gettext \"-h display help and exit\"`"
  72 #  This way 'xgettext' will work ok,
  73 #+ but the 'gettext' program will read "-h" as an option!
  74 #
  75 # One solution could be
  76 #     echo "`gettext -- \"-h display help and exit\"`"
  77 #  This way 'gettext' will work,
  78 #+ but 'xgettext' will extract "--", as referred to above.
  79 #
  80 # The workaround you may use to get this string localized is
  81 #     echo -e "`gettext \"\\0-h display help and exit\"`"
  82 #  We have added a \0 (NULL) at the beginning of the sentence.
  83 #  This way 'gettext' works correctly, as does 'xgettext.'
  84 #  Moreover, the NULL character won't change the behavior
  85 #+ of the 'echo' command.
  86 #  ------------------------------------------------------------------

 bash$ bash -D localized.sh
 "Can't cd to %s."
 "Enter the value: "
This lists all the localized text. (The -D option lists double-quoted strings prefixed by a $, without executing the script.)

 bash$ bash --dump-po-strings localized.sh
 #: a:6
 msgid "Can't cd to %s."
 msgstr ""
 #: a:7
 msgid "Enter the value: "
 msgstr ""
The --dump-po-strings option to Bash resembles the -D option, but uses gettext "po" format.

Bruno Haible points out:

Starting with gettext-0.12.2, xgettext -o - localized.sh is recommended instead of bash --dump-po-strings localized.sh, because xgettext . . .

1. understands the gettext and eval_gettext commands (whereas bash --dump-po-strings understands only its deprecated $"..." syntax)

2. can extract comments placed by the programmer, intended to be read by the translator.

This shell code is then not specific to Bash any more; it works the same way with Bash 1.x and other /bin/sh implementations.

Now, build a language.po file for each language that the script will be translated into, specifying the msgstr. Alfredo Pironti gives the following example:

fr.po:
   1 #: a:6
   2 msgid "Can't cd to $var."
   3 msgstr "Impossible de se positionner dans le repertoire $var."
   4 #: a:7
   5 msgid "Enter the value: "
   6 msgstr "Entrez la valeur : "
   7 
   8 #  The string are dumped with the variable names, not with the %s syntax,
   9 #+ similar to C programs.
  10 #+ This is a very cool feature if the programmer uses
  11 #+ variable names that make sense!

Then, run msgfmt.

msgfmt -o localized.sh.mo fr.po

Place the resulting localized.sh.mo file in the /usr/local/share/locale/fr/LC_MESSAGES directory, and at the beginning of the script, insert the lines:
   1 TEXTDOMAINDIR=/usr/local/share/locale
   2 TEXTDOMAIN=localized.sh

If a user on a French system runs the script, she will get French messages.

With older versions of Bash or other shells, localization requires gettext, using the -s option. In this case, the script becomes:

   1 #!/bin/bash
   2 # localized.sh
   3 
   4 E_CDERROR=65
   5 
   6 error() {
   7   local format=$1
   8   shift
   9   printf "$(gettext -s "$format")" "$@" >&2
  10   exit $E_CDERROR
  11 }
  12 cd $var || error "Can't cd to %s." "$var"
  13 read -p "$(gettext -s "Enter the value: ")" var
  14 # ...

The TEXTDOMAIN and TEXTDOMAINDIR variables need to be set and exported to the environment. This should be done within the script itself.

---

This appendix written by Stéphane Chazelas, with modifications suggested by Alfredo Pironti, and by Bruno Haible, maintainer of GNU gettext.