Translations

Overview

In this tutorial, we build a two-panel fiscal chart of government revenue and capital spending. We want to support multiple languages without maintaining separate R scripts for each. A common—but brittle—approach is to copy your R file and replace all hard-coded English titles, subtitles, captions, and legends with translated strings.

A more robust workflow:

  1. Assign all text elements to variables instead of literals, so you can swap languages programmatically.
  2. Collect these variables in a central container (an R environment) for consistency and IDE support.
  3. Provide utilities to export/import this container to/from Excel for official translations.

This tutorial will show you how to make charts in various languages, using either unofficial translation (by ChatGPT) or official translation by language services. We encapsulate all steps—data reading, label assignment, and plotting—inside the a function makeChart() function, which you call with:

makeChart(lang = "en", isOfficial = FALSE, filename = "english.png")
makeChart(lang = "es", isOfficial = FALSE, filename = "spanish-unofficial.png")
makeChart(lang = "es", isOfficial = TRUE,  filename = "spanish-official.png")

Setup

First, load the required packages and helper scripts. These helpers live in your repo under utils/ and define the custom theme_imf_panel() and color palette.

library(readxl)
library(writexl)
library(dplyr)
library(tidyr)
library(ggpubr)
library(patchwork)
library(ragg)
library(cowplot)
library(here)
library(ragg)

# Custom theme and annotation helpers
source(here("utils/theme_and_colors_IMF.R"))      # see Chapter 7
source(here("utils/Add_text_to_figure_panel.R"))  # see Chapter 5

Assign Labels to Variables, Not Literals

Instead of embedding string literals directly in your plotting code, assign all text elements (titles, subtitles, captions, legends) to variables or an environment. This allows you to:

  • Swap languages easily by populating variables from different sources.
  • Maintain consistency across multiple figures.
  • Automate translation workflows via Excel export/import.
# NOT recommended:
ggplot(data) +
  labs(title = "Ingresos del Gobierno Central")

# Recommended:
labels$chart1_title <- "Ingresos del Gobierno Central"
ggplot(data) +
  labs(title = labels$chart1_title)

Centralizing Labels with Environments

An R environment is a mutable container for named objects. Use an environment to collect all your label variables:

labels <- new.env(parent = emptyenv())
labels$chart1_title  <- "Central Government Revenue"
labels$chart1_legend <- c("Tax", "Non-Tax")


# ... more labels ...

Benefits:

  • Encapsulation: Bundles related labels in one place.

  • Performance: Reference semantics avoid copying when modifying.

  • Scalability: Easily add or remove labels without changing function signatures.

  • IDE Autocomplete: Environments enable tab-completion (e.g., labels$<TAB>) in RStudio. If you type in labels$ in RStudio, you get a popup:

Once you have defined all your English labels in labels, export them to Excel for translation:

write_labels_to_excel(labels, "labels.xlsx")

Exporting and Importing Label Environments

To integrate professional translation workflows while keeping rapid prototyping simple, we provide two complementary helpers:

  1. Export your in-code English labels to an Excel template:
    write_labels_to_excel(labels, "labels.xlsx")
This creates an XLSX with columns:

-   `id`: label identifier (e.g. `chart1_title`)
-   `english`: the original English text
-   `translation`: an empty column for translators

  1. Read the translated workbook back into R:

    labels <- read_labels_from_excel("labels_translated.xlsx")

    This recreates a labels environment with all translated strings, preserving vectors versus scalars.


Helper Function Definitions

write_labels_to_excel()

#' Write a labels environment to an Excel file
#'
#' @param labels_env An environment of atomic values (length-1 or character vectors)
#' @param path       File path for the XLSX
write_labels_to_excel <- function(labels_env, path) {
  labels_list <- as.list(labels_env)
  labels_df <- data.frame(
    id          = names(labels_list),
    english     = sapply(labels_list, function(x) paste(x, collapse = ", ")),
    translation = rep(NA_character_, length(labels_list)),
    stringsAsFactors = FALSE
  )
  writexl::write_xlsx(labels_df, path = path)
}

read_labels_from_excel()

#' Read labels from Excel and return an environment
#'
#' @param path Path to translated XLSX
#' @param sep  Regex for splitting multi-element strings (default ",\\s*")
#' @return    A new environment populated with the translated labels
read_labels_from_excel <- function(path, sep = ",\\s*") {
  df  <- readxl::read_excel(here(path))
  env <- new.env(parent = emptyenv())
  for (i in seq_len(nrow(df))) {
    key   <- df$id[i]
    value <- df$translation[i]
    env[[key]] <- if (grepl(sep, value)) strsplit(value, sep)[[1]] else as.character(value)
  }
  env
}

Official vs. Unofficial Translations

This workflow supports two translation modes:

  • Unofficial (rapid) Coders directly translate the hard-coded labels inside the makeChart() function or in your code environment. In makeChart(lang = "es", isOfficial = FALSE), the function adds a diagonal watermark “Unofficial translation”:

    if (lang == "es" && !isOfficial) {
      pw <- ggdraw(pw) +
        draw_label(
          "Unofficial translation",
          x        = 0.5,
          y        = 0.5,
          angle    = 45,
          fontface = "bold",
          colour   = "grey80",
          size     = 60,
          alpha    = 0.30
        )
    }
  • Official (validated)

    1. Export English labels to Excel via write_labels_to_excel(labels, "labels.xlsx").
    2. Send labels.xlsx to professional language services.
    3. When the translated file is ready, call makeChart(lang = "es", isOfficial = TRUE).
    4. makeChart() reads labels_translated.xlsx with read_labels_from_excel(), recreating the labels environment with official translations.

Producing the Fiscal Chart: makeChart()

#' Build and save the two-panel fiscal chart (Figure 7)
#'
#' @param lang       "en" or "es"
#' @param isOfficial TRUE to use translated XLSX, FALSE for hard-coded Spanish
#' @param filename   Output PNG file path
makeChart <- function(lang = "en", isOfficial = FALSE, filename) {
  # 1. Read & pivot data
  chart_1_fiscal <- read_excel(
    here("databases/SR charts v1.xlsx"),
    sheet = "Chart 1 Data"
  )
  chart_2_fiscal <- read_excel(
    here("databases/SR charts v1.xlsx"),
    sheet = "Chart 5 Data"
  )
  
  chart_long_fiscal <- function(data) {
    data %>%
      rename(label = Year) %>%                       # keep original “Year” col
      pivot_longer(-label,
                   names_to  = "year",
                   values_to = "value") %>%
      mutate(year = as.numeric(year))
  }
  
  chart_1_fiscal_long <- chart_long_fiscal(chart_1_fiscal)
  chart_2_fiscal_long <- chart_long_fiscal(chart_2_fiscal)

  # 2. Build labels env
  if (lang == "en") {
    labels <- new.env(parent = emptyenv())
    labels$chart1_title    <- "Central Government Revenue"
    labels$chart1_subtitle <- "(In percent of GDP)"
    labels$chart1_caption  <- "Source: Ministry of Finance and IMF staff estimates."
    labels$chart1_legend   <- c("Tax", "Non-Tax")
    labels$chart2_title    <- "Central Government Capital Spending"
    labels$chart2_subtitle <- "(In percent of GDP)"
    labels$chart2_legend   <- c("Domestic", "External")
    labels$main_title      <- "Figure 7. Fiscal Sector Developments"
    labels$main_caption    <- "Sources: Bank of Jamaica and IMF staff estimates and projections."
    
    # To export English labels for translation uncomment the next line
    #  write_labels_to_excel(labels, "labels.xlsx")
    
    
  } else if (lang == "es") {
    if (isOfficial) {
      labels <- read_labels_from_excel("labels_translated.xlsx")
    } else {
      labels <- new.env(parent = emptyenv())
      labels$chart1_title    <- "Ingresos del Gobierno Central"
      labels$chart1_subtitle <- "(En porcentaje del PIB)"
      labels$chart1_caption  <- "Fuente: Ministerio de Hacienda y estimaciones del personal del FMI."
      labels$chart1_legend   <- c("Tributarios", "No tributarios")
      labels$chart2_title    <- "Gasto de Capital del Gobierno Central"
      labels$chart2_subtitle <- "(En porcentaje del PIB)"
      labels$chart2_legend   <- c("Interna", "Externa")
      labels$main_title      <- "Figura 7. Evolución del Sector Fiscal"
      labels$main_caption    <- "Fuentes: Banco de Jamaica y estimaciones y proyecciones del personal del FMI.\nNote: unofficial translation"
    }
  }


  # ---------------------------------------------------------------------------#
  # 3.  First panel: Government revenue 
  # ---------------------------------------------------------------------------#
  fig1 <-
    chart_1_fiscal_long %>%
    ggplot(aes(x = as.character(year), y = value)) +
    geom_bar(stat      = "identity",
             fill      = blue,
             position  = position_dodge(0.73),
             color     = "black",
             width     = 0.42) +
    geom_hline(aes(yintercept = 0),
               color    = light_grey,
               linetype = "solid") +
    scale_x_discrete(expand = c(0.05, 0)) +
    scale_y_continuous(limits = c(-8, 2),
                       breaks = seq(-8, 2, 2),
                       expand = c(0, 0)) +
    labs(title    = labels$chart1_title,
         subtitle = labels$chart1_subtitle,
         x = "", y = "") +
    theme_imf_panel()
  
  # ---------------------------------------------------------------------------#
  # 4.  Second panel: Capital spending ----------------------------------------
  # ---------------------------------------------------------------------------#
  fig2 <-
    chart_2_fiscal_long %>%
    ggplot(aes(x = as.character(year), y = value)) +
    geom_bar(stat      = "identity",
             fill      = blue,
             position  = position_dodge(0.73),
             color     = "black",
             width     = 0.42) +
    geom_hline(aes(yintercept = 0),
               color    = light_grey,
               linetype = "solid") +
    scale_x_discrete(expand = c(0.05, 0)) +
    scale_y_continuous(limits = c(0, 5),
                       breaks = seq(0, 5, 1),
                       expand = c(0, 0)) +
    labs(title    = labels$chart2_title,
         subtitle = labels$chart2_subtitle,
         x = "", y = "") +
    theme_imf_panel()
  
  # 5. Combine & annotate
  pw <- (fig1 | fig2) +
    plot_annotation(
      title   = labels$main_title,
      caption = labels$main_caption,
      theme   = theme(
        plot.title   = element_text(color  = blue, family = primary_font, face = "bold", size = 20, hjust = 0.5),
        plot.caption = element_text(hjust = 0, family = primary_font, size = 12)
      )
    )

  # 6. Watermark for unofficial Spanish
  if (lang == "es" && !isOfficial) {
    pw <- ggdraw(pw) +
      draw_label("Unofficial translation", x = 0.5, y = 0.5, angle = 45,
                 fontface = "bold", colour = "grey80", size = 40, alpha = 0.3)
  }

  pw
  # 7. Save file
 # ggsave(filename = filename, plot = pw, dpi = 600,
  #       height = 5.5 * 1.25, width = 9.5 * 1.25, units = "in")
}

Usage Examples

# Build English chart

fig1<-makeChart("en", FALSE, "english.png")
fig1

# Spanish unofficial translation

fig2<-makeChart("es", FALSE,  "spanish_unofficial.png")
fig2

# Spanish official

fig3<-makeChart("es", TRUE,  "spanish_official.png")
fig3