Rapid Internationalization of Shiny Apps: shiny.i18n Version 0.2
<h2><span data-preserver-spaces="true">Rapid Multi-Language Support for Shiny Apps</span></h2> <span data-preserver-spaces="true">Have you ever created a multilingual Shiny application? Chances are the answer is </span><strong><span data-preserver-spaces="true">yes. </span></strong>Especially <span data-preserver-spaces="true">if you are a big fan of <a href="https://appsilon.com/shiny" target="_blank" rel="noopener noreferrer">Appsilon</a>'s blog and have read our </span><a class="editor-rtfLink" href="https://wordpress.appsilon.com/internationalization-of-shiny-apps-i18n/" target="_blank" rel="noopener noreferrer"><span data-preserver-spaces="true">first article</span></a><span data-preserver-spaces="true"> on the topic. If that's not the case - fear not - we'll cover everything you need to know today about how to rapidly add multi-language support to Shiny apps. If you already know a lot about <code class="language-r">shiny.i18n</code>, feel free to jump to any section:</span> <ul><li><a href="#introduction"><span data-preserver-spaces="true">Introduction to shiny.i18n</span></a></li><li><a href="#whats-new"><span data-preserver-spaces="true">What's New in Version 0.2.0</span></a></li><li><a href="#example"><span data-preserver-spaces="true">Shiny Dashboard Example</span></a></li><li><a href="#conclusion"><span data-preserver-spaces="true">Conclusion</span></a></li><li><a href="#learn-more">Learn More</a></li></ul> To learn more about Appsilon's open-source packages, see our new <a href="http://shiny.tools" target="_blank" rel="noopener noreferrer">Open Source Landing Page:</a> <a href="http://shiny.tools"><img class="size-large wp-image-5332" src="https://wordpress.appsilon.com/wp-content/uploads/2020/09/Screen-Shot-2020-09-23-at-5.02.14-PM-1024x504.png" alt="Appsilon's shiny.tools landing page for our open source packages." width="1024" height="504" /></a> <em>Appsilon's shiny.tools landing page for our open source packages.</em> <h2 id="introduction"><span data-preserver-spaces="true">Introduction to shiny.i18n</span></h2> <span data-preserver-spaces="true">At </span><span data-preserver-spaces="true">Appsilon we routinely build <a href="http://demo.appsilon.ai" target="_blank" rel="noopener noreferrer">Shiny applications</a> for Global 2000 companies</span><span data-preserver-spaces="true">, and we've come across the internationalization problem multiple times. It made sense to develop an open-source package that handles multilanguage options with ease. Version 0.1.0 has been out for quite some time now, and we are proud to announce a new, upgraded version - 0.2.0.</span> <blockquote><strong><em><span data-preserver-spaces="true">NOTE: </span></em></strong><em><span data-preserver-spaces="true">shiny.i18n usage is not limited to Shiny apps. <strong>You can use it as a standalone R package for generating multilingual reports or visualizations without Shiny.</strong> We decided on the name "shiny.i18n" because Shiny is the most common and obvious use-case scenario for the package.</span></em></blockquote> <span data-preserver-spaces="true">The latest version of the package is available on </span><a class="editor-rtfLink" href="https://cran.r-project.org/web/packages/shiny.i18n/index.html" target="_blank" rel="noopener noreferrer"><span data-preserver-spaces="true">CRAN</span></a><span data-preserver-spaces="true">, so installation is extremely easy:</span> <pre><code class="language-r">install.packages('shiny.i18n')</code></pre> <span data-preserver-spaces="true">The package utilizes specific translation file data formats - currently in JSON and CSV.</span> <h3><span data-preserver-spaces="true">JSON Format</span></h3> <span data-preserver-spaces="true">Here's an example of a JSON translation file for English, Polish, and Croatian languages:</span> <pre><code class="language-r">{ "languages": ["en", "pl", "hr"], "translation": [ { "en": "MtCars Dataset Explorer", "pl": "Eksplorator danych: MtCars", "hr": "Istraživanje MtCars Dataseta" }, { "en": "Change language", "pl": "Wybierz język", "hr": "Promijeni jezik" }, { "en": "Select", "pl": "Wybierz", "hr": "Odaberi" }, { "en": "This is description of the plot.", "pl": "To jest opis obrazka.", "hr": "Ovo je opis grafikona." }, { "en": "Scatter plot", "pl": "Wykres punktowy", "hr": "Dijagram raspršenja" }, { "en": "X-Axis", "pl": "Oś X", "hr": "X os" }, { "en": "Y-Axis", "pl": "Oś Y", "hr": "Y os" } ] }</code></pre> <span data-preserver-spaces="true">As you can see, only two fields are required:</span> <ul><li><span data-preserver-spaces="true">languages - list of language codes</span></li><li><span data-preserver-spaces="true">translation - JSON objects containing translation to all languages</span></li></ul> <h3><span data-preserver-spaces="true">CSV Format</span></h3> <span data-preserver-spaces="true">The main idea behind this format is to support distributed translation tasks among many translators. It's not convenient for them to work together on a single file, but instead, they can store individual files in the same folder. Here's the example folder structure:</span> <pre><code class="language-r">translations/ translation_pl.csv translation_hr.csv</code></pre> <span data-preserver-spaces="true">Every CSV file in the translations folder should look like this:</span> <pre><code class="language-r">en, hr Hello Shiny!, Pozdrav, Shiny! Histogram of x, Histogram od x This is a description of the plot., Ovo je opis grafikona. Frequency, Frekvencija Number of bins:, Broj binova: Change language, Promijeni jezik</code></pre> <span data-preserver-spaces="true">Awesome! Let's now take a look at the version 0.2.0 changelog, and then we'll jump to a dashboard example.</span> <h2 id="whats-new"><span data-preserver-spaces="true">What New in Version 0.2.0</span></h2> <span data-preserver-spaces="true">The complete changelog list for every version is located at </span><a class="editor-rtfLink" href="https://github.com/Appsilon/shiny.i18n/blob/master/CHANGELOG.md" target="_blank" rel="noopener noreferrer"><span data-preserver-spaces="true">GitHub.com</span></a><span data-preserver-spaces="true">, but here's what new in the most recent version:</span> <strong><span data-preserver-spaces="true">Added:</span></strong> <ul><li><span data-preserver-spaces="true">Auxiliary preprocessing functions - they create example translation CSV or JSON files</span></li><li><span data-preserver-spaces="true">RStudio add-in that searches for all i18n tags</span></li><li><span data-preserver-spaces="true">Browser-side translations to dramatically improve performance</span></li><li><span data-preserver-spaces="true">automatic translations via API (currently only google cloud supported)</span></li><li><span data-preserver-spaces="true">2 vignettes with tutorials</span></li><li><span data-preserver-spaces="true">examples with live language translation on the UI side and with automatic translation via API</span></li><li><code class="language-r">pkgdown</code><span data-preserver-spaces="true"> documentation</span></li></ul> <strong><span data-preserver-spaces="true">Changed:</span></strong> <ul><li><code class="language-r">translate</code><span data-preserver-spaces="true"> method now automatically detects whether the object is in UI or server and applies reactive or js translation accordingly</span></li></ul> <h2 id="example"><span data-preserver-spaces="true">Dashboard Example</span></h2> <span data-preserver-spaces="true">To demonstrate how </span><code class="language-r">shiny.i18n</code> works in practice, we'll develop a simple dashboard application based on the <code class="language-r">mtcars</code> dataset. This dashboard can be used to make scatter plots of the two columns specified by the user. <span data-preserver-spaces="true">To start, we'll import a couple of required libraries and initialize the translator. We've stored the translations file in <code class="language-r">translations/translation.json</code>, so keep that in mind if yours is stored elsewhere:</span> <pre><code class="language-r">library(shiny) library(shiny.i18n) library(ggplot2) <br>i18n <- Translator$new(translation_json_path='translations/translation.json') i18n$set_translation_language('en')</code></pre> <span data-preserver-spaces="true">Next, we can define the UI. There are a couple of things we want to have:</span> <ul><li><span data-preserver-spaces="true">Dropdown menu for language - somewhere in the top right is fine</span></li><li><span data-preserver-spaces="true">Title panel - to inform the user what our app is all about</span></li><li><span data-preserver-spaces="true">Sidebar - contains two dropdown menu for X and Y axes, respectively</span></li><li><span data-preserver-spaces="true">Main panel - contains a chart and the chart description</span></li></ul> <span data-preserver-spaces="true">Here's the code for the UI:</span> <pre><code class="language-r">ui <- fluidPage( shiny.i18n::usei18n(i18n), tags$div( style='float: right;', selectInput( inputId='selected_language', label=i18n$t('Change language'), choices = i18n$get_languages(), selected = i18n$get_key_translation() ) ), titlePanel(i18n$t('MtCars Dataset Explorer'), windowTitle=NULL), sidebarLayout( sidebarPanel( width=3, tags$h4(i18n$t('Select')), varSelectInput( inputId='x_select', label=i18n$t('X-Axis'), data=mtcars ), varSelectInput( inputId='y_select', label=i18n$t('Y-Axis'), data=mtcars ) ), mainPanel( plotOutput(outputId='scatter'), tags$p(i18n$t('This is description of the plot.')) ) ) )</code></pre> <span data-preserver-spaces="true">The last step remaining is to build the server. The <code class="language-r">observeEvent</code> function will help us to detect language change and to make translations accordingly. As a final step, we can use the <code class="language-r">renderPlot</code> reactive function to display our scatter plot:</span> <pre><code class="language-r">server <- function(input, output, session) { observeEvent(input$selected_language, { update_lang(session, input$selected_language) }) output$scatter <- renderPlot({ col1 <- sym(input$x_select) col2 <- sym(input$y_select) ggplot(mtcars, aes(x= !! col1, y= !! col2)) + geom_point(size=6) + ggtitle(i18n$t('Scatter plot')) }) } <br>shinyApp(ui=ui, server=server)</code></pre> <span data-preserver-spaces="true">And that's all we have to do. Our final application is simple but perfectly captures how easy it is to implement multilanguage functionality.</span> <img class="aligncenter wp-image-5740 size-full" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b021d3a20f7e6672b82c7e_FinalApp.gif" alt="" width="1252" height="574" /> <h2 id="conclusion"><span data-preserver-spaces="true">Conclusion</span></h2> <span data-preserver-spaces="true">In this relatively short hands-on guide, we've demonstrated what </span><code class="language-r">shiny.i18n</code> is and how it can be used to develop multilingual dashboards. Version 0.2 brought some exciting new features. The most important feature to us is the ability to not have to render everything in the <code class="language-r">server</code> function, which was the case with the earlier version. <span data-preserver-spaces="true">The other new feature is automatic translation, powered by Google Cloud. It requires a bit of setup, and you can learn more about it </span><a class="editor-rtfLink" href="https://github.com/ropensci/googleLanguageR/" target="_blank" rel="noopener noreferrer"><span data-preserver-spaces="true">here</span></a><span data-preserver-spaces="true">.</span> <blockquote><span data-preserver-spaces="true">Curious about automatic translation? </span><a class="editor-rtfLink" href="https://github.com/Appsilon/shiny.i18n/blob/master/examples/automatic_translations/app.R" target="_blank" rel="noopener noreferrer"><span data-preserver-spaces="true">Here's how to implement it with Shiny and i18n.</span></a></blockquote> <!--more--> <h2 id="learn-more">Learn More</h2><ul><li><a href="https://appsilon.com/shiny-worker-package/" target="_blank" rel="noopener noreferrer">shiny.worker: Speed Up Shiny Apps by Offloading Heavy Calculations</a></li><li><a href="https://appsilon.com/how-to-scale-a-shiny-dashboard/" target="_blank" rel="noopener noreferrer">Video: How to Scale Shiny Dashboards</a></li><li>Appsilon is hiring talented R Shiny Developers. Check out our open positions <a href="https://appsilon.com/careers/" target="_blank" rel="noopener noreferrer">here</a>.</li></ul>