--- title: "Translating The Workbench" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Translating The Workbench} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ## Introduction The philosophy of The Carpentries Workbench is one of separation between lesson content and the tooling needed to transform that content into a website. It is possible to write a lesson in any human language that has a syllabary which can be represented on a computer. The only catch is: by default the language of the _website template_---all the navigational elements of the website---is English, so authors need to tell The Workbench what language the website template should use. To write a lesson in a specific language, the lesson author should add `lang: 'xx'` to the `config.yaml` file where `xx` represents the [language code](https://www.gnu.org/software/gettext/manual/html_node/Usual-Language-Codes.html) that matches the language of the lesson content. This defaults to `"en"`, but can be any language code (e.g. "ja" specifying Japanese) or combination language code and [country code](https://www.gnu.org/software/gettext/manual/html_node/Country-Codes.html) (e.g. "pt_BR" specifies Pourtugese used in Brazil). For more information on how this is used, see [the Locale Names section of the gettext manual](https://www.gnu.org/software/gettext/manual/html_node/Locale-Names.html). If there is country-specific variation for a language that needs to be added, it is not necessary to re-translate everything that is identical in the original language file---you only need to translate what is different. Setting the `lang:` keyword will allow the lesson navigational elements of the website template to be presented in the same language as the lesson content _if the language has been added to {sandpaper}_. If not, the menu items will appear in English. This vignette is of interest to those who wish to update translations or add new translations. In this vignette I will provide resources for updating and adding new languages, the process by which translation happens, and I will outline special syntax used in {sandpaper}. ## Resources ### Recommended Tools To provide translations as a non-maintainer, you need only a text editor to add translations to the strings present in the `.po` translation files (see more about these files in the [Translating in {sandpaper} section](#translating-in-sandpaper). If you would like to compile and test these translations locally, you will need the [{potools} package](https://michaelchirico.github.io/potools/), which requires the [GNU gettext system](https://www.gnu.org/software/gettext/). As mentioned in the [{potools} installation documentation](https://michaelchirico.github.io/potools/#installation), if you are on macOS, you will likely need to use `brew` to install `gettext` with `brew install gettext`, otherwise, it is bundled with RTools (needed for package development on Windows) and it is present on most Linux distributions. ### Documentation The documentation for the {potools} package is a wonderful resource. Use `vignette("translators", package = "potools")` to read details about what to consider when translating text in a package. Another really good resource is [a blog post by Maëlle Salmon](https://masalmon.eu/2023/10/06/potools-mwe/) which gives a minimum working example of translating package messages using {potools}. If you are interested in translating _lesson content_, please consult Joel Nitta's [Carpentries translation guide](https://hackmd.io/@joelnitta/SkCSC6ZNT#Guide-for-translators-l10n). You can use tools such as Joel Nitta's [{dovetail}](https://github.com/joelnitta/dovetail#readme) for providing a method for translators to track and deploy translations of Carpentries lessons. You can also use rOpenSci's [{babeldown}](https://docs.ropensci.org/babeldown/), which uses the DeepL API for automated translation that translators can edit afterwards. ## Translating in {sandpaper} The translations from {sandpaper} are mostly shuffled off to {varnish}, where it has template variables written in mustache templating. These variables define visible menu text such as "Key Points" and screen-reader accessible text for buttons such as "close menu". There are `r length(sandpaper::known_languages())` languages that are known to {sandpaper}: ```{r echo = FALSE, results = "asis"} writeLines(paste("-", sandpaper::known_languages())) ``` If a language is not known to {sandpaper} and a lesson attempts to use that language, it will default back to English (the source language). ### The source files of translations When you translate in {sandpaper}, you will be working with `.po` files, which are text files that live in [the po/ folder](https://github.com/carpentries/sandpaper/tree/HEAD/po) in the source of this package. There is one `.po` file per language translated. The syntax looks like this, where the first line shows the file where the translation exists, the second line gives the message in English, and the third line gives the translation: ```po #: build_404.R:57 msgid "Page not found" msgstr "ページが見つかりません" ``` These files are recognised by [several well-established graphical user interfaces](https://michaelchirico.github.io/potools/#alternative-software), but since they are text files, you can edit them in any text editor or on GitHub directly. ### How translations are processed in R These po files are compiled into binary `.mo` files that are carried with the built package on to the user's computer. These files are used by the R function `base::gettext()` to translate messages in a specific context. The context for {sandpaper} is known as `R-sandpaper`. ```{r gettext} library("withr") library("sandpaper") known_languages() with_language("ja", { enc2utf8(gettext("Page not found", domain = "R-sandpaper")) }) with_language("en", { enc2utf8(gettext("Page not found", domain = "R-sandpaper")) }) ``` If a language does not exist, it will revert to English: ```{r gettext-2} with_language("xx", { enc2utf8(gettext("Page not found", domain = "R-sandpaper")) }) ``` To make translation keys easier to detect, an internal convenience function, `tr_()` has been defined, In addition, the source strings and keys for the translations can be found from `tr_src()`, `tr_varnish()` and `tr_computed()`, so if you want to find the context for a given translation key, you can find it by searching the source code for `tr_`. ### Special syntax for translators Some content for translation requires variables or markup to be added after translation. Items in `{curly_braces}` are variables and should remain in English: ```po #: utils-translate.R:52 msgid "Estimated time: {icons$clock} {minutes} minutes" msgstr "所要時間:{icons$clock} {minutes}分" ``` Words in `<(kirby quotes)>` will have HTML markup surrounding them and should be translated: ``` #: utils-translate.R:62 msgid "This lesson is subject to the <(Code of Conduct)>" msgstr "このレッスンは<(行動規範)>の対象となります" ``` ### Updates to translations There may be times in the future where translations will need to be updated because text changes or is added. When this happens, the maintainer of {sandpaper} will run the following commands to extract the new translation strings, update all languages, and recompile the `.mo` files for the built package. ```r potools::po_extract() potools::po_update() potools::po_compile() ``` When the languages are updated, the translation utility will attempt to make fuzzy matches or create new strings. For example, if we update the "Page not found" translation to be title case, add punctuation and a little whimsy to be `"Page? Not Found! -_-;"`, when you go to edit your translation, you might see something like this: ```po #: build_404.R:57 #, fuzzy #| msgid "Page not found" msgid "Page? Not Found! -_-;" msgstr "ページが見つかりません" ``` The old translation will be used until a translator updates it and runs `potools::po_compile()` to update the `.mo` files. When new strings for translations are added, the translation utility does not assume to know anything about translation and the will appear like so: ```po #: build_404.R:57 msgid "A new translation approaches!" msgstr "" ``` If no translation is available for a given string, it will default to the string itself: ```{r default-string} with_language("ja", { enc2utf8(gettext("A new translation approaches!", domain = "R-sandpaper")) }) ``` ## List of Translation Variables ```{r child="../man/children/translation-vars.Rmd"} ```