Translating The Workbench

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 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 (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. 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

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 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.

You can use tools such as Joel Nitta’s {dovetail} for providing a method for translators to track and deploy translations of Carpentries lessons. You can also use rOpenSci’s {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 6 languages that are known to {sandpaper}:

  • en
  • de
  • es
  • fr
  • ja
  • uk

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 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:

#: build_404.R:57
msgid "Page not found"
msgstr "ページが見つかりません"

These files are recognised by several well-established graphical user interfaces, 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.

library("withr")
library("sandpaper")
known_languages()
#> [1] "en" "de" "es" "fr" "ja" "uk"
with_language("ja", {
  enc2utf8(gettext("Page not found", domain = "R-sandpaper"))
})
#> [1] "ページが見つかりません"
with_language("en", {
  enc2utf8(gettext("Page not found", domain = "R-sandpaper"))
})
#> [1] "Page not found"

If a language does not exist, it will revert to English:

with_language("xx", {
  enc2utf8(gettext("Page not found", domain = "R-sandpaper"))
})
#> [1] "Page not found"

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:

#: 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.

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:

#: 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:

#: 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:

with_language("ja", {
  enc2utf8(gettext("A new translation approaches!", domain = "R-sandpaper"))
})
#> [1] "A new translation approaches!"

List of Translation Variables

There are 62 translations generated by set_language() that correspond to the following variables in {varnish}:

variable string
translate.SkipToMain 'Skip to main content'
translate.iPreAlpha 'Pre-Alpha'
translate.PreAlphaNote 'This lesson is in the pre-alpha phase, which means that it is in early development, but has not yet been taught.'
translate.AlphaNote 'This lesson is in the alpha phase, which means that it has been taught once and lesson authors are iterating on feedback.'
translate.iAlpha 'Alpha'
translate.BetaNote 'This lesson is in the beta phase, which means that it is ready for teaching by instructors outside of the original author team.'
translate.iBeta 'Beta'
translate.PeerReview 'This lesson has passed peer review.'
translate.InstructorView 'Instructor View'
translate.LearnerView 'Learner View'
translate.MainNavigation 'Main Navigation'
translate.ToggleNavigation 'Toggle Navigation'
translate.ToggleDarkMode 'Toggle theme (auto)'
translate.Menu 'Menu'
translate.SearchButton 'Search the All In One page'
translate.Setup 'Setup'
translate.KeyPoints 'Key Points'
translate.InstructorNotes 'Instructor Notes'
translate.Glossary 'Glossary'
translate.LearnerProfiles 'Learner Profiles'
translate.More 'More'
translate.LessonProgress 'Lesson Progress'
translate.CloseMenu 'close menu'
translate.EPISODES 'EPISODES'
translate.Home 'Home'
translate.HomePageNav 'Home Page Navigation'
translate.RESOURCES 'RESOURCES'
translate.ExtractAllImages 'Extract All Images'
translate.AIO 'See all in one page'
translate.DownloadHandout 'Download Lesson Handout'
translate.ExportSlides 'Export Chapter Slides'
translate.PreviousAndNext 'Previous and Next Chapter'
translate.Previous 'Previous'
translate.EstimatedTime 'Estimated time: {icons$clock} {minutes} minutes'
translate.Next 'Next'
translate.NextChapter 'Next Chapter'
translate.LastUpdate 'Last updated on {updated}'
translate.EditThisPage 'Edit this page'
translate.ExpandAllSolutions 'Expand All Solutions'
translate.SetupInstructions 'Setup Instructions'
translate.DownloadFiles 'Download files required for the lesson'
translate.ActualScheduleNote 'The actual schedule may vary slightly depending on the topics and exercises chosen by the instructor.'
translate.BackToTop 'Back To Top'
translate.SpanToTop '<(Back)> To Top'
translate.ThisLessonCoC 'This lesson is subject to the <(Code of Conduct)>'
translate.CoC 'Code of Conduct'
translate.EditOnGH 'Edit on GitHub'
translate.Contributing 'Contributing'
translate.Source 'Source'
translate.Cite 'Cite'
translate.Contact 'Contact'
translate.About 'About'
translate.MaterialsLicensedUnder 'Materials licensed under {license} by the authors'
translate.TemplateLicense 'Template licensed under <(CC-BY 4.0)> by {template_authors}'
translate.Carpentries 'The Carpentries'
translate.BuiltWith 'Built with {sandpaper_link}, {pegboard_link}, and {varnish_link}'
translate.ExpandAllSolutions 'Expand All Solutions'
translate.CollapseAllSolutions 'Collapse All Solutions'
translate.Collapse 'Collapse'
translate.Episodes 'Episodes'
translate.GiveFeedback 'Give Feedback'
translate.LearnMore 'Learn More'

In addition, there are 27 translations that are inserted before they get to {varnish}:

variable string
OUTPUT 'OUTPUT'
WARNING 'WARNING'
ERROR 'ERROR'
Overview 'Overview'
Questions 'Questions'
Objectives 'Objectives'
Callout 'Callout'
Challenge 'Challenge'
Prereq 'Prerequisite'
Checklist 'Checklist'
Discussion 'Discussion'
Testimonial 'Testimonial'
Caution 'Caution'
Keypoints 'Key Points'
Show me the solution 'Show me the solution'
Give me a hint 'Give me a hint'
Show details 'Show details'
Instructor Note 'Instructor Note'
SummaryAndSetup 'Summary and Setup'
SummaryAndSchedule 'Summary and Schedule'
AllInOneView 'All in One View'
PageNotFound 'Page not found'
AllImages 'All Images'
Anchor 'anchor'
Figure 'Figure {element}'
ImageOf 'Image {i} of {n}: {sQuote(txt)}'
Finish 'Finish'