Internationalization of Shiny Apps - Never Easier Than With shiny.i18n
<em><strong>Updated</strong>: January 24, 2023.</em> Have you ever created a multilingual Shiny app? It is very likely that the answer is <strong>no</strong> because Shiny just doesn’t have any good tools for that. At <a href="https://appsilon.com/" target="_blank" rel="noopener noreferrer">Appsilon</a>, we came across the internationalization problem many times, so we decided to make a tool that makes life easier when it comes to multilingualism. <code class="highlighter-rouge">shiny.i18n</code> is the new kid on the block and still under rapid development, but the <code class="highlighter-rouge">0.1.0</code> version is already ready to go. Our shiny.i18n usage is not limited to Shiny apps. You can use it as a standalone R package for generating multilingual reports or visualizations. We decided on this name because Shiny is the most common and obvious use-case scenario. <blockquote>Did you know Shiny is now available for Python? <a href="https://appsilon.com/shiny-for-python-introduction/">Here's our official get-started guide</a>.</blockquote> Table of contents: <ul><li><a href="#install">How to Install shiny.i18n</a></li><li><a href="#file-formats">File Formats for Internationalization of Shiny Apps</a></li><li><a href="#shiny">Hands-on: Translating a Shiny App with shiny.i18n</a></li><li><a href="#summary">Summing up Internationalization of Shiny Apps</a></li></ul> <hr /> <h2 id="install">How to Install shiny.i18n</h2> The latest version of the package (0.3.0) is released on <a href="https://cran.r-project.org/web/packages/shiny.i18n/index.html" target="_blank" rel="noopener noreferrer">CRAN</a>, so you can simply get it like this: <pre><code class="language-r" data-lang="r">install.packages<span class="p">("</span><span class="n">shiny.i18n"</span><span class="p">)</span></code></pre> To install the development version always check the <a href="https://github.com/Appsilon/shiny.i18n" target="_blank" rel="noopener noreferrer">GitHub project</a> first. <pre><code class="language-r" data-lang="r">devtools::install_github("Appsilon/shiny.i18n")</code></pre> On the GitHub page, you can also find more examples, read the documentation, and post some issues or your own contributions. <h2 id="file-formats">File Formats for Internationalization of Shiny Apps</h2> Now it’s time to learn more about how to use shiny.i18n. The package utilizes specific translation file data formats. Currently, two approaches are supported. <h3><strong>JSON</strong></h3> Example of a JSON translation file for English and Polish language you can find below: <pre><code class="language-json">{ "languages":[ "en", "pl" ], "translation":[ { "en":"Hello Shiny!", "pl":"Witaj Shiny!" }, { "en":"Number of bins:", "pl":"Liczba podziałek" }, { "en":"This is description of the plot.", "pl":"To jest opis obrazka." }, { "en":"Histogram of x", "pl":"Histogram x" }, { "en":"Frequency", "pl":"Częstotliwość" } ] }</code></pre> It consists of a single file <code>translation.json</code> with two mandatory fields: <ul><li><code>"languages"</code>: with a list of all language codes;</li><li><code>"translation"</code>: with a list of dictionaries assigning translation to a language code.</li></ul> Other fields (such as <code>cultural_date_format</code>) are optional and if missing will be read from the <a href="https://github.com/Appsilon/shiny.i18n/blob/master/inst/config.yaml" target="_blank" rel="noopener noreferrer">default config</a> YAML file. <h3><strong>CSV</strong></h3> Another approach is to use a CSV format. The main idea behind it is to support distributed translation tasks among many translators. Instead of having to concatenate the results of work from many translators together, we can store them in a common folder with the specific name of the file: <code>translation_<LANGUAGE-CODE>.csv</code>. You can imagine a situation with the following folder structure: <pre><code class="language-csv">translations/ translation_pl.csv translation_it.csv translation_kl.csv</code></pre> which have translations for Polish (pl), Italian (it), and - as the language code is completely arbitrary - kl for the Klingon language. Let’s have a look at what a typical CSV translation file should look like: <pre><code class="language-csv">en, it Hello Shiny!, Ciao Shiny! Histogram of x, Istogramma di x This is description of the plot., Questa è la descrizione della trama. Frequency, Frequenza Number of bins:, Numero di scomparti: Change language, Cambia lingua</code></pre> This time we need to remember that all CSV files from one dictionary must share a common reference language in the left column - which is English (en) in the above case. <h2 id="shiny">Hands-on: Translating a Shiny App with shiny.i18n</h2> To integrate our translations with Shiny we start by loading packages and an example JSON file. <pre><code class="language-r">library(shiny) library(shiny.i18n) <br>i18n <- Translator$new(translation_json_path = "translations/translation.json")</code></pre> Having that, we can check in the RStudio console all languages stored in the i18n object. <pre><code class="language-r">i18n$get_languages()</code></pre> Here's the output: <img class="size-full wp-image-17580" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b270749551644329c1d4df_1-3.webp" alt="Image 1 - Available languages" width="347" height="73" /> Image 1 - Available languages Now within the Shiny app, we need to surround all text elements within <code>i18n$translate</code> or in short <code>i18n$t</code> method. For instance: <pre><code class="language-r">sliderInput("bins", i18n$t("Number of bins:"), min = 1, max = 50, value = 30)</code></pre> to translate a message displayed by <code class="highlighter-rouge">sliderInput</code> element, or: <pre><code class="language-r" data-lang="r"><span class="n">titlePanel</span><span class="p">(</span><span class="n">i</span><span class="m">18</span><span class="n">n</span><span class="o">$</span><span class="n">t</span><span class="p">(</span><span class="s2">"Hello Shiny!"</span><span class="p">))</span></code></pre> to translate a <code>titlePanel</code> content. If we decide to run an instance of the app with a specific language (let's say Klingon) we should call: <pre><code class="language-r" data-lang="r"><span class="n">i</span><span class="m">18</span><span class="n">n</span><span class="o">$</span><span class="n">set_translation_language</span><span class="p">(</span><span class="s2">"kl"</span><span class="p">)</span></code></pre> right after defining <code class="highlighter-rouge">Translator</code> object. Below you can see the full example: <pre><code class="language-r">library(shiny) library(shiny.i18n) <br>i18n <- Translator$new(translation_json_path = "translations/translation.json") i18n$set_translation_language("pl") <br> ui <- fluidPage( titlePanel(i18n$t("Hello Shiny!")), sidebarLayout( sidebarPanel( sliderInput("bins", i18n$t("Number of bins:"), min = 1, max = 50, value = 30) ), mainPanel( plotOutput("distPlot"), p(i18n$t("This is description of the plot.")) ) ) ) <br>server <- function(input, output) { output$distPlot <- renderPlot({ x <- faithful[, 2] bins <- seq(min(x), max(x), length.out = input$bins + 1) hist(x, breaks = bins, col = "darkgray", border = "white", main = i18n$t("Histogram of x"), ylab = i18n$t("Frequency") ) }) } <br>shinyApp(ui = ui, server = server)</code></pre> And here's the dashboard: <img class="size-full wp-image-17578" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b271f26fad9810a9529961_2.gif" alt="Image 2 - Shiny app translated to Polish" width="1628" height="1060" /> Image 2 - Shiny app translated to Polish Above code should convince you about how easy it is to start using shiny.i18n. For more examples once again I refer you to the GitHub page. We hope that shiny.i18n will help you to forget about problems with internationalization. <hr /> <h2 id="summary">Summing up Internationalization of Shiny Apps</h2> And there you have it - concrete proof that translating R Shiny apps was never easier. Just pick a file format you're more comfortable with, either JSON or CSV, and don't forget to wrap the text elements with the package functions we showed you. For more advanced guides and usage examples, check out the link below and stay tuned to the Appsilon blog. <blockquote>Want to see what's new in the latest release of shiny.i18n? <a href="https://appsilon.com/rapid-internationalization-of-shiny-apps-shiny-i18n-version-0-2/">Read our new article to find out</a>.</blockquote>