library("gettext")
knitr::opts_chunk$set(echo = TRUE)

With GNU gettext programmers got a utility to internationalise their applications:

Usually, programs are written and documented in English, and use English at execution time for interacting with users. This is true not only from within GNU, but also in a great deal of proprietary and free software. Using a common language is quite handy for communication between developers, maintainers and users from all countries. On the other hand, most people are less comfortable with English than with their own native language, and would rather be using their mother tongue for day to day's work, as far as possible. Many would simply love seeing their computer screen showing a lot less of English, and far more of their own language.

The package implements gettext, ngettext to use various languages and domains in parallel. xgettext extracts all strings from an R file or an R expression. gettextify takes an existing R code and puts around all text constants a gettext.

Understanding GNU gettext

GNU gettext vocabulary

Domain : GNU gettext distinguishes between domains. Basically a domain is the name for your application. This allows to create different translations for the same messages in dependence of the application in use.

Language : GNU gettext distinguishes between languages ;) It allows translating messages for application in every language.

Plural : In your application you may have messages which depend on numbers, e.g. 1 error made, 2 errors made, ... . In some languages you have more than one plural form, e.g. in Arabic you have up to six plural forms depending on your number.

Context : The translation of a messages may depend on a context, e.g. the translation of I saw her duck depends on the interpretation of duck, it differs if duck is a noun or a verb.

Usually in a PO file the messages and its translation for one language and one domain is stored. Plurals and contexts are coded in the PO file.

PO file structure

For detailed overview, see the GNU gettext manual, section The Format of PO files.

The basic structure is an entry of the form:

msgid untranslated-string
msgstr translated-string

Plurals can be added by:

msgid untranslated-string-singular
msgid_plural untranslated-string-plural
msgstr[0] translated-string-case-0
...
msgstr[N] translated-string-case-n

To use plurals you need to define in your PO file how from the number in the message the correct plural form from 0 till N can be calculated.

Contexts can be given by:

msgctxt context
msgid untranslated-string
msgstr translated-string

ISO 639

ISO 639 is a set of standards by the International Organization for Standardization that is concerned with representation of names for languages and language groups. It uses a two- or three-letter code to identify a language.

library("gettext")
data(iso639)
head(iso639)

An example with Shiny

The package contains two example apps which shows an interactive histogram using Shiny, see shiny/app1/app.R and shiny/app2/app.R.

Note: Before you start to translate the texts of your app to other languages, make sure that the development of your app is finished. Otherwise, you may have to redo translations again and again.

The first step would be to extract all texts from your program (here we take the example app.R which is included in the package)

xgettext(system.file('shiny', 'app1', 'app.R', package='gettext'))

Note: A difference to the standard xgettext is that ALL strings are extracted from the R program. The output consists of four columns: column one tells if it is a single string, or within a gettext, G, ngettext or N command. The second column is the message id and the third column contains the plural form. The fourth column contains the context (default: NA). Therefore, you can check later if all strings which needs to be translated are really translated, see in the appendix.

The next step is to create a template file for the translation, a pot file, from it:

txt <- xgettext(system.file('shiny', 'app1', 'app.R', package='gettext'))
writePotfile(txt, 'myproject.pot')

You may edit the POT file, e.g. with an editor as Poedit, and create a PO file for German and store it in your Shiny app directory.

The final step is to modify your app, replace any "text" by gettext("text"). The observer in the app in at the beginning uses the URL parameter lang to choose the language.

To see a demo of the apps you may run:

library("demoShiny")
demoShiny('app1')   # Shiny app without language support 
demoShiny('app2en') # Shiny app in the default language (English)
demoShiny('app2de') # Shiny app in German

The gettext package

getTranslation, saveTranslation, and readTranslation

The package has an internal environment translation which stores the messages and its translations. With getTranslation() you can get a copy of this environment as a list. Note that changes in this copy will not change anything in the internal environment.

The internal environment can be saved to and read from a file with the commands saveTranslation and readTranslation. These commands use internally saveRDS and readRDS. Rather than analysing a POfile when starting your program, as in the example app, you might load all messages and save it. In your app you may load the saved translations via readtranslation.

options, bind and bindtextdomain

In gettext you need to specify a language and a domain. The domain becomes important when you load translations for several apps, because the same text might be translated differently in two apps. Most users will have just one app and will simply use the default domain NA. The current language and domain can be set by:

options(gettext.lang='lang')
options(gettext.domain='mydomain')

The bind command decomposes a base file name into language and domain. For detecting the language at the end of a file name the iso639 data object is used.

bind('myproject_de.po')
bind(de='myproject.po')
bind(de=c('myproject1.po', 'myproject2.po'))
bind(de=c('myproject1_de.po', 'myproject2_de.po'), 
          en=c('myproject1_en.po', 'myproject2_en.po'))
files <- c("myproject_de.po", 
           "intl/for_use_owncloud_impress_cs_CZ.po",
           "intl/ibalsa-master-zh_HK.po",
           "spirit-po/test1.po")
bind(files)

The bindtextdomain looks at all .po files from the directory, calls bind and replaces the detected domains by the given domain.

To use any of the translations you have to set the language, e.g.

options(gettext.lang='zh_hk')

readPofile

Reads one or more PO files into the internal translation environment. If file names are given then the default domain and languages will be used. You may change that by using the parameters lang and domain.

readPofile('myproject_de_DE.po', lang='de')

gettext and ngettext

With gettext and ngettext you can get translations for your text. Since R does not support _ as operator and it might not be a good idea to define a function _ I use G and N as abbreviations.

gettext('No translation found')
ngettext('Found %.0f translation', 'Found %.0f translations', 7)

Both commands use either the currently set language and domain:

getOption('gettext.lang')
getOption('gettext.domain')

Or you can explicitly use the parameters lang and domain.

xgettext and writePotfile

xgettext analyses an expression for text constants and gettext, ngettext, G and N function calls.

file <- system.file('shiny', 'app1', 'app.R', package='gettext')
xgettext(file)
file <- system.file('shiny', 'app2', 'app.R', package='gettext')
xgettext(file)

The result of xgettext can be stored in a POT file to create with external software translations in other languages.

file <- system.file('shiny', 'app2', 'app.R', package='gettext')
txt  <- xgettext(file)
writePotfile(txt, "my.pot")

gettextify

gettextify takes as argument an R expression, a file name, or an R code. It replaces all text constants with gettext("text"). Excluded are only text constants which appear within gettext, G, ngettext, N, require or library.

file <- system.file('shiny', 'app1', 'app.R', package='gettext')
txt  <- gettextify(file)
cat(txt, sep="\n")

The result txt can be saved, e.g. with writeLines, to a file. Note that this is a helper function and still requires that you check/work on the result!

addMsg and addPlural

readPofile uses addMsg to add a message and its translations to the internal environment. With addPlural you can add a plural for a language and domain. Basically you could circumvent the usage of POT and PO files by using them directly.

For example plurals see plural:

data(plural)
plural[[7]]  # Arabic

Appendix

shiny/app1/app.R {#app1}


shiny/app2/app.R {#app2}


shiny/app2/myproject.pot {#pot}


shiny/app2/myproject_de_DE.po {#po}


xgettext on shiny/app2/app.R {#xget2}

xgettext(system.file('shiny', 'app2', 'app.R', package='gettext'))

The NA entries indicate that a gettext has been found in the R code. Below you see the extracted text constant. It there is no text constant to extract than the following entry may not appear, but at least you are hinted to this gettext in your program.

xgettext on shiny/app3/app.R {#xget3}

xgettext(system.file('shiny', 'app3', 'app.R', package='gettext'))


sigbertklinke/gettext documentation built on Feb. 17, 2020, 10:37 a.m.