Search code examples
rshinyinternationalizationshinydashboard

Items inside header and sidebar of {shinydashboard} are not translated with {shiny.i18n}


The problem

When using {shiny.i18n} for live language translations in an {shinydashboard} app, the contents inside dashboardHeader() and dashboardSidebar() are not translated. Contents inside dashboardBody() are translated though.

Sample application

In the app below, the title of the Dashboard ("Basic dashboard") and the two menu items ("Dashboard Tab" and "Widgets Tab") are all wrapped in the i18n$t() function, with traditional Chinese translation (zh) provided in translation_zh.csv.

When the user changes the language from en to zh, the text of the menu items and dashboard title do not change. Meanwhile, other items inside dashboardBody() (e.g. "Number of observations:") are successfully translated.

How could I make the components inside dashboardHeader() and dashboardSidebar() also work with the i18n$t() function?

Default view

enter image description here

After selecting zh as the language

enter image description here

enter image description here

Sample code

The app code is merged from the sample app of {shinydashboard} and sample app of {shiny.i18n}.

app.R

library(shiny)
library(shinydashboard)
library(shiny.i18n)

# File with translations
i18n <- Translator$new(translation_csvs_path = "data/")
i18n$set_translation_language("en") # here you select the default translation to display

ui <- dashboardPage(
  dashboardHeader(title = i18n$t("Basic dashboard")),
  dashboardSidebar(
    sidebarMenu(
      menuItem(i18n$t("Dashboard Tab"), tabName = "dashboard", icon = icon("dashboard")),
      menuItem(i18n$t("Widgets Tab"), tabName = "widgets", icon = icon("th"))
    )
  ),
  ## Body content
  dashboardBody(
    tabItems(
      # First tab content
      tabItem(tabName = "dashboard",

              shiny.i18n::usei18n(i18n),
              div(style = "float: right;",
                  selectInput('selected_language',
                              i18n$t("Change language"),
                              choices = i18n$get_languages(),
                              selected = i18n$get_key_translation())
              ),

              fluidRow(
                box(plotOutput("plot1", height = 250)),

                box(
                  title = i18n$t("Controls"),
                  sliderInput("slider", i18n$t("Number of observations:"), 1, 100, 50)
                )
              )
      ),

      # Second tab content
      tabItem(tabName = "widgets",
              h2(i18n$t("Widgets tab content"))
      )
    )
  )
)

server <- function(input, output, session) {

  observeEvent(input$selected_language, {
    # This print is just for demonstration
    print(paste("Language change!", input$selected_language))
    # Here is where we update language in session
    shiny.i18n::update_lang(session, input$selected_language)
  })

  set.seed(122)
  histdata <- rnorm(500)

  output$plot1 <- renderPlot({
    data <- histdata[seq_len(input$slider)]
    hist(data)
  })
}

shinyApp(ui, server)

data/translation_zh.csv

en,zh
Widgets tab content,內容
Change language,更換語言
Controls,控制台
Number of observations:,觀察數量
Basic dashboard,基本儀表版
Dashboard Tab,儀表版
Widgets Tab,部件

Solution

  • Original Answer

    A tricky one, but after some debugging I found the reason. Without going into the technical details of the Translator implementation, we need to make sure that shiny.i18n::usei18n is called before any call to i18n$t. In your example usei18n is called in the body that is after all the rendering of the parts in the header and the sidebar.

    The solution is to move usei18n to the header, which poses another small issue, as we cannot simply add it in the ... part of it, as these items are expected to be dropdownMenus.

    The solution is to add it to the title in form of a tagList:

    ui <- dashboardPage(
       dashboardHeader(title = tagList(shiny.i18n::usei18n(i18n), i18n$t("Basic dashboard"))),
    # ...
    )
    

    Dashboard with translation now working in all parts of the app

    This is not the most beautiful solution, but it works. Background of this hack is that not only does usei18n add the resources to the HTML document, but also sets an internal flag (private$js) which enables live translation in the first place. Only after this flag is set a call to i18n$t adds the necessary span tags around the elements, which in turn allow for the live translation. Without this flag, i18n$t simply returns the static translation.


    Update

    The relevant part in usei18n is translator$use_js(). Thus the cleanest approach would be to add the following line to your original code:

    i18n$set_translation_language("en")
    i18n$use_js() ### <<-- Add this