П о д а т о ц и

Data are fun

Using {shiny.i18n} with {golem} for server-side translation

In this post we share a useful tidbit we learned while developing the demo for the OpenBudgetMKD application.

Like with most projects at Discindo, the OpenBudgetMKD application is based on R and {shiny}. It uses the {bs4Dash} and shinyWidgets for their excellent UI components, is packaged with{golem}, and will be available in Macedonian, Albanian, and English. From a programming perspective, the most interesting aspect for us was implementing internationalization using the {shiny.i18n} package and integrating this into a {golem}ized {shiny} application. These tools integrate seamlessly and make creating rather powerful and great looking applications easy. Many thanks to the developers for creating these awesome packages!

The one small glitch we encountered had to do with dynamically updating the language of choices of shinyWidgets::radioGroupButtons. Client-side translation using the usual {shiny.i18n} workflow was not working, and the proposed solution to translate the choices within an update function on the server did not work out of the box in {golem} because the i18n$translator was not in the server’s function scope. Specifically, because the ui and server of {golem}ized {shiny} applications exist as separate functions in separate scripts, they do not share the scope (like they would in single-file {shiny} applications). Normally, we overcome this by using global.R to load objects in the environment that are shared by both ui and server. But in {golem}, there is no global.R. So what to do?

Fortunately, we are again spoiled by the versatility of {golem}. The solution is to pass the i18n translator object as golem.option when running the app, in golem::run_app(). Then we can use golem::get_golem_options to access the translator wherever we need it, e.g., at the beginning of both app_ui and app_server to make the translator available in both functions.

We include a minimal example of below. See also the repository.


  1. Customize golem::run_app to load the translator
# R/run_app

run_app <- function(
  onStart = NULL,
  options = list(), 
  enableBookmarking = NULL,
  uiPattern = "/",
  ...
) {
  with_golem_options(
    app = shinyApp(
      ui = app_ui,
      server = app_server,
      onStart = onStart,
      options = options, 
      enableBookmarking = enableBookmarking, 
      uiPattern = uiPattern
    ), 
    # Initiate the translator as a golem.option
    golem_opts = list(translator = shiny.i18n::Translator$new(translation_csvs_path = "inst/app/www/translations/"))
  )
}
  1. Get the translator from golem’s options
  • UI
# R/app_ui.R

app_ui <- function(request) {
  # calling the translator sent as a golem option
  i18n <- golem::get_golem_options(which = "translator")
  i18n$set_translation_language("en")
  
  tagList(# Leave this function for adding external resources
    golem_add_external_resources(),
    # Your application UI logic
    fluidPage(
      h3("{golem} app and {shiny.i18n} internationalization"),
      h5("(With server-side translation)"),
      br(),
      column(
        width = 4,
        # select language
        radioButtons(
          inputId = "lang",
          label = "Select language",
          inline = TRUE,
          choices = i18n$get_languages()
        ),
        # UI that we'll translate on the server size
        uiOutput("welcome")
      )
    ))
}
  • Server
app_server <- function( input, output, session ) {
  
  # calling the translator sent as a golem option
  i18n <- golem::get_golem_options(which = "translator")
  i18n$set_translation_language("en")
  
  # keep track of language object as a reactive
  i18n_r <- reactive({
    i18n
  })
  
  # change language
  observeEvent(input[["lang"]], {
    shiny.i18n::update_lang(session, input[["lang"]])
    i18n_r()$set_translation_language(input[["lang"]])
  })
  
  output[["welcome"]] <- renderUI({
    
    bg <- switch(input[["lang"]], 
                 "en" = "white",
                 "es" = "yellow",
                 "fr" = "steelblue",
                 "de" = "lightgrey")
    
    div(style = paste("padding: 10px; border-radius: 10px; background:", bg), h3(i18n$t("Welcome")))
  }) 
}