I18n Support and Shell Scripts

From Docs

Jump to: navigation, search
Unity16pxVert.png
Internationalization and Localization Guides

Contents

How to write a shell script with i18n support

This guide will help you write shell scripts which can be easily translated into multiple languages. This is achieved through the use of a function (denoted simply as '__' in order to be as discrete as possible) that does not have a significant impact on the way shell scripts must be written.

Note: All pieces of code showed in the following examples were tested with bash only. Their effectiveness with other shells is not guaranteed.

The basics – Getting started

Essentially, to obtain a multilingual shell script, we will use the GNU gettext package. This package comprises several tools which allow to:

Let's start with a classic:

echo 'Hello, world!'

gettext can be used to retrieve the translation of the message and pass it to echo, in this way:

echo "$(gettext 'Hello, world!')"

However, gettext needs to know the catalog name from which to extract a possible translation. This can be done by setting the environment variable TEXTDOMAIN. Therefore, add this line to the script (before the first invocation of gettext):

export TEXTDOMAIN=experiment

In this case gettext will try to retrieve any translation from a catalog named experiment.mo located in /usr/share/locale/xx/LC_MESSAGES, where 'xx' represents your locale. You can specify a different location by setting the environment variable TEXTDOMAINDIR:

export TEXTDOMAINDIR=~

In this case gettext will search for the catalog named experiment.mo in ~/xx/LC_MESSAGES, where 'xx' represents your locale.

Let's complicate things a bit now, and consider a situation in which the message we would like to be translatable contains a variable:

echo 'Hello, $NAME!'

In this case the gettext manual suggests to include in the script a shell function library provided by the gettext package, named gettext.sh (located in /usr/bin). This library contains the eval_gettext function which outputs the native language translation of a textual message, performing dollar-substitution on the result (see http://www.gnu.org/software/autoconf/manual/gettext/sh.html#sh).

We will not take this route, and instead we will use a cleaner and more powerful method to deal with variables inside messages.

The '__' function

General description


To make messages within shell scripts translatable, we will add the '__' function as defined below:

moname="experiment"

__ () {
      local gettextopts="-d $moname -e --"
      local TEXT=`gettext $gettextopts "$1"`
      [ "$(echo $1|grep "\\\n$")" ] && TEXT="$TEXT\n"
      [[ $1 = *$'\n' ]] && TEXT="$TEXT\n"
      shift
      printf -- "$TEXT" "$@"
}

This means that the former echo "$(gettext 'Hello, world!')" becomes:

echo "$(__ "Hello, world!")"

How do we transform echo "Hello, $NAME!", instead?

echo "$(__ 'Hello, %s!' "$NAME")

Why? Let's see how the '__' function works! The first line

local gettextopts="-d $moname -e --"

assigns to the local variable gettextopts the options used with the gettext command:

$ gettext '\tHello, world!'
\tHello, world!

$ gettext -e '\tHello, world!'
        Hello, world!
$ gettext "--option1"
gettext: unrecognized option '--option1'
Try `gettext --help' for more information.

$ gettext -- "--option1"
--option1


The following line

local TEXT=`gettext $gettextopts "$1"`

uses gettext (with the options specified by the variable gettextopts) to retrieve the translation of the first argument of the '__' function, that is $1. The string value is assigned to the local variable TEXT.


The next two lines

[ "$(echo $1|grep "\\\n$")" ] && TEXT="$TEXT\n"
[[ $1 = *$'\n' ]] && TEXT="$TEXT\n"

are aimed at restoring the trailing new line whenever $1 contains one. Indeed, the use of either `` or $() removes any trailing new lines, so, if one is present, it must be restored. The drawback is that if more trailing new lines are present, they will be reduced to one, but we will see methods to overcome this limitation. '/n' is appended to the value of TEXT if either $1 terminates with '\n' (case 1) or it terminates with a new line (case 2).


Once these checks are performed, the positional parameters are shifted to the left by 1:

shift


Finally,

printf -- "$TEXT" "$@"

the content of TEXT is printed using the shell built-in command printf. Moreover, any further arguments passed to the '__' function ( "$@" ) are passed to printf as well.

Usage


Being printf used for the final output of '__', the usage of '__' will reflect that of printf. Like echo, printf is a command to print output on the screen, but it is so much more powerful!

Its syntax is:

printf <FORMAT> <ARGUMENTS...>

and a typical printf call looks like:

printf 'Hello, %s %s!' "$FIRSTNAME" "$LASTNAME"

where 'Hello, %s %s!' is the format specification, and the two variables are passed as arguments. Special format specifiers are introduced by the symbol '%'. In this case '%s' stands for a string.

Please, note: if more arguments than format specifiers are present, then the format string is re-used until the last argument is interpreted. If less format specifiers than arguments are present, then number-formats are set to zero, while string-formats are set to null (empty).

Also note that printf automatically interprets the escape sequences (echo needs the '-e' option), and prints a new line only when specified (echo always prints a new line, unless the '-n' option is specified).

Examples:

$ printf 'Hello, %s %s!' "Alessio" "Adamo"
Hello, Alessio Adamo!$ printf 'Hello, %s %s!\n\n' "Alessio" "Adamo"
Hello, Alessio Adamo!

$ printf 'Hello, %s %s!\n\n' "Alessio"
Hello, Alessio !

 $ printf 'Hello, %s %s!\n\n' "Alessio" "Adamo" "Gianvacca"
Hello, Alessio Adamo!

Hello, Gianvacca !

[ If you want to know more about printf, follow this link: http://wiki.bash-hackers.org/commands/builtin/printf ]

What is the typical usage of '__', then?

__ <MSG_TO_BE_TRANSLATED> <ARGUMENTS>

Let's see now few examples to make it clear.

How to handle simple messages


echo "This is a simple message"

Becomes:

echo "$(__ "This is a simple message")"   # or: __ "This is a simple message\n"

If we don't use the output of '__' inside echo, we must happend '\n', otherwise no new line is printed.


cat<<-EOF
This is another
simple message
EOF

Becomes:

cat<<-EOF
$(__ "This is another
simple message")
EOF

zenity --info --text="This message is extremely important!!!" --title="Information"

Becomes:

zenity --info --text="$(__ "This message is extremely important!!!")" --title="$(__ "Information")"

Bear always in mind that any trailing new line after the first will be ignored by '__', but there is an easy workaroud you can put in place:

echo -e "I want to keep more distance from the following sentence\n\n\n"

Becomes:

echo -e "$(__ "I want to keep more distance from the following sentence")\n\n\n"

How to handle messages with variables


In general, when a message contains some variables, we will substitute each variable with '%s' and feed them to '__' as arguments. See this simple example:

echo -e "Your username is $(whoami)\nand your home directory is $HOME"

Becomes:

echo "$(__ "Your username is %s\nand your home directory is %s" "$(whoami)" "$HOME")"
# Or just:   __ "Your username is %s\nand your home directory is %s\n" "$(whoami)" "$HOME"

Few comments:

  1. The '-e' option of echo can be dropped since the '__' function itself will interpret the escape sequences.
  2. Not necessarily the variables have to be enclosed by double quotes. In this case, for instance, it isn't necessary, since we are sure that their values won't contain blank spaces, but why bother? My suggestion is: always double quote variables and forget about it!
  3. Remember to add a trailing '\n' if you use the second form, since by default '__' doesn't add a trailing new line.

If you want to be more specific on the type of variable, instead of '%s' you might use the proper format specifier as in the following examples.

OBJECT=( circle star square )
for i in 1 2 3; do echo "The shape No $i is: ${OBJECT[$i-1]}"; done

Might be transformed into:

OBJECT=( $(__ "circle") $(__ "star") $(__ "square") )
for i in 1 2 3; do __ "The shape No %s is: %s\n" "$i" "${OBJECT[$i-1]}"; done

Or, more specifically into:

OBJECT=( $(__ "circle") $(__ "star") $(__ "square") )
for i in 1 2 3; do __ "The shape No %d is: %s\n" "$i" "${OBJECT[$i-1]}"; done

Substitution of constants


Sometimes it might be useful to substitute pieces of a message which should not be translated with '%s' or other format specifiers.

cat<<-EOF
Command options:

--version     Print version and quit
EOF

If you want to be sure that the first 'version', being an option, is not translated, you can substitute it with '%s':

cat<<-EOF
$(__ "Command options:

%s     Print version and quit" "--version")
EOF

Another example:

echo "The log is stored in /var/log/$COMMAND_NAME.log"

It's definitely better:

echo "$(__ "The log is stored in %s" "/var/log/$COMMAND_NAME.log")"

Instead of:

echo "$(__ "The log is stored in /var/log/%s.log" "$COMMAND_NAME")"

Special cases


Sometimes, you might want to substitute some 'esoteric' escape sequences with a format specifier. If you substituted them with '%s', they would not be interpreted. In such cases, use '%b'. For example:

$ echo -e "\xA9 is the copyright symbol"
© is the copyright symbol

Becomes:

$ __ "%b is the copyright symbol\n" "\xA9"
© is the copyright symbol

The usual method does not work:

$ __ "%s is the copyright symbol\n" "\xA9"
\xA9 is the copyright symbol

An alternative is:

$ echo -e "$(__ "%s is the copyright symbol" "\xA9")"
© is the copyright symbol

In the example above, the escape sequence '\xA9' is not interpreted by '__' (and specifically printf), but is subsequently interpreted by 'echo -e'. Remember that usually '-e' can be omitted, since '__' interprets the escape sequences before passing the massage to echo.


Beware of the percentage '%' sign!

echo "In 2015 90% of world population will be using Unity Linux"

Cannot be turned into:

__ "In 2015 90% of world population will be using Unity Linux\n"

printf would interpret '% o' as '%o', a format specifier for octal numbers. Yet, since no argument is specified, '% o' will be replaced by '0', thus obtaining 'In 2015 900f world population will be using Unity Linux". In order to avoid it, use two percentage signs '%%':

__ "In 2015 90%% of world population will be using Unity Linux\n"

Such situations can occur more often than you think, when you are dealing with rpm macros.


Next steps

Now that we know how to prepare a shell script for internationalization, we need to know how to extract the relevant messages into a catalog for translation.

Unity.png
Return to Unity Homepage
Personal tools
Namespaces
Variants
Actions
Getting Help
Development
Toolbox
More