How to translate your package’s messages with {potools}

[This article was first published on Maëlle's R blog on Maëlle Salmon's personal website, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

In November I’ll give a talk about multilingualism in R at the Spanish R conference in Barcelona (😍). I can’t wait! Until then, I need to prepare my talk. 😅 I plan to present the rOpenSci “multilingual publishing” project but also other related tools, like potools. In this post, I’ll walk you through a minimal example of using potools to translate messages in an R package!

What is potools?

In R, you can provide messages in different languages by using .po, .pot and .mo files. If you provide messages in English and their translation to Spanish, an user who sets the environment variable LANGUAGE to es will get to see Spanish messages rather than the default English messages.

The potools package by Michael Chirico is the roxygen2 of .po, .pot and .mo files: as you could well write Rd files by hand1, you could write the translation files by hand… but things are easier with potools.

Create package

I create a package called pockage, with my usual usethis workflow. It initially has a single function, that tries to extract the user’s name via the whoami package, and then says hello using the cli package:

#' Say hello
#'
#' @return Nothing
#' @export
#'
#' @examples
#' speak()
speak <- function() {
  name <- whoami::fullname(fallback = "user")
  cli::cli_alert_info("Hello {name}!")
}

Let’s try it out:

pockage::speak()
#> ℹ Hello Maëlle Salmon!

Install potools

I install potools from GitHub using pak::pak("MichaelChirico/potools").

Register potools style in DESCRIPTION

I shall use potools explicit style, which means it will recognize strings as translatable only if I mark them as so. Therefore I add these lines to DESCRIPTION:

Config/potools/style: explicit

Create tr_() function and use it

Following potools’ vignette for developers, I run usethis::use_r("utils-potools") and paste the definition an internal function in that new file:

tr_ <- function(...) {
  enc2utf8(gettext(paste0(...), domain = "R-pockage"))
}

I then modify the speak() function:

#' Say hello
#'
#' @return Nothing
#' @export
#'
#' @examples
#' speak()
speak <- function() {
  name <- whoami::fullname(fallback = "user")
  cli::cli_alert_info(tr_("Hello {name}!"))
}

The difference is that the string “Hello {name}!” is now marked as translatable.

Create the general translation file

I run potools::po_extract() to create the po/R-pockage.pot file.

Create the translation file for French and fill it

Then I run potools::po_create("fr") to create the file holding the translation of the string to French.

I obtain this po/R-fr.po file where I edit two lines (Last-Translator, and msgstr at the bottom):

msgid ""
msgstr ""
"Project-Id-Version: pockage 0.0.0.9000\n"
"POT-Creation-Date: 2023-10-06 10:45+0200\n"
"PO-Revision-Date: 2023-10-06 10:33+0200\n"
"Last-Translator: Malle Salmon\n"
"Language-Team: none\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ASCII\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

#: mensaje.R:9
msgid "user"
msgstr "utilisateur·rice"

#: mensaje.R:10
msgid "Hello {name}!"
msgstr "Salut {name} !"

Compile the translation

I compile with potools::po_compile() which creates the .mo binary for the French language in inst/po.

Try out the translation

I load or install&load the package, then I use the following code, magically using withr to set the LANGUAGE environment variable locally:

withr::with_language("fr", pockage::speak())
#> ℹ Salut Maëlle Salmon !
# as opposed to
pockage::speak()
#> ℹ Salut Maëlle Salmon !

Quite neat, right?

Add languages

I then add two other languages, unstoppable as I am now, by running potools::po_create(c("es", "ca")). I add the Spanish and Catalan translations of the string in respectively po/R-es.po and po/R-ca.po. I compile with potools::po_compile() which creates the .mo binary for the Spanish and Catalan languages in inst/po.

After installing the pockage package again, I get:

withr::with_language("fr", pockage::speak())
#> ℹ Salut Maëlle Salmon !
withr::with_language("es", pockage::speak())
#> ℹ Hola Maëlle Salmon!

# I swear it's pronounced differently
withr::with_language("ca", pockage::speak())
#> ℹ Hola Maëlle Salmon!

Translate more strings

Now imagine I also want to translate the fallback of the whoami function call, for the case whoami can’t identify the user.

#' Say hello
#'
#' @return Nothing
#' @export
#'
#' @examples
#' speak()
speak <- function() {
  name <- whoami::fullname(fallback = tr_("user"))
  cli::cli_alert_info(tr_("Hello {name}!"))
}

I run

Conclusion

Find my final pockage package on GitHub.

To translate messages with potools in the explicit style one needs to:

  • register the potools style in DESCRIPTION;
  • create and use a tr_() internal function to wrap strings to be translated;
  • run potools::po_extract() at the beginning of the translation efforts and every time strings wrapped in tr_() are changed, deleted, added;
  • run potools::po_create() once per non-default language to be supported;
  • run potools::po_update() after potools::po_extract() every time strings wrapped in tr_() are changed, deleted, added;
  • run potools::po_compile() every time the translation source files are changed.

potools has one vignette for developers and one for translators that I’d recommend reading, because they provide useful advice beyond the basic workflow that I illustrated here.


  1. which is what I first learnt to do years and years ago. ↩︎

To leave a comment for the author, please follow the link and comment on their blog: Maëlle's R blog on Maëlle Salmon's personal website.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)