Hacktoberfest with Ghost: Help bring i18n to our app
Tags: hacktoberfest, node, javascript, opensource
Published: 2022-10-17 14:40:05

---
title: Hacktoberfest with Ghost: Help bring i18n to our app
published: true
description: Help bring better translation support into Ghost for Hacktoberfest 2022
tags: hacktoberfest,node,javascript,opensource
# Use a ratio of 100:42 for best results.
---

[Ghost](https://ghost.org/) is an industry-leading, open-source publishing platform built on Node. Creators (Ali Abdaal, Scatchy, DESK), publishers (The Lever, The Atlantic, The Browser), and companies (Unsplash, DuckDuckGo, OpenAI, Mozilla) use our platform to publish, share, and grow a business around their content.

Because our users are global, **better translation support (i18n)** has become our most-requested feature. Hacktoberfest seemed like the perfect opportunity to ask the dev community for help in shipping this much-wanted feature.

While developers have contributed to translating individual components in the past, we believe that i18n needs to be solved with a single solution. Therefore, we put together a plan that outlines the requirements for this solution.

{% embed https://github.com/TryGhost/Ghost/issues/15502 %}

## Problem

At present, only our theme layer is translatable using a custom-built i18n framework (that we do not want to build on). Our native membership (Portal), email, comment, and search systems, however, are not translatable.

Since these systems only have one set of strings that need translating into each language, it makes sense to have these translations be part of Ghost core (that is, built into the product itself).

## Solution
 
We want to be able to serve translated emails and widgets (membership, comments, search), based on the language defined by the user in general settings. Technically, the solution boils down to answering two key questions:

1. Where do the translated strings come from?
2. How do the replacement strings get interpolated?

The answer to (1) is “a JSON file” of some description. We do not want to manage translations in a 3rd party tool like transifex, but we’d like to keep all of the JSON files in GitHub. It needs to be possible to load the strings and pass them to all the places they’re needed.

The answer to (2) is a `{{t}}` helper that needs to exist in both React and email templates. We want to keep our existing pattern in themes of using the default string as the key. This means that when a default string is changed, it needs to be re-translated in all languages, which is desirable as changes are usually more than cosmetic.

We will also need a way to collect all the default strings and compile them into a new JSON file, ready to be translated.

For email templates, it makes sense for the interpolation to be dynamic, done at the time of sending as this is a server-side system. For our React widgets, we would like to create a custom build for each translation, so it becomes possible to load language-specific scripts, e.g. `portal-es.min.js`.

Several aspects of i18n will be made significantly easier by ensuring that all translatable components and the translations themselves live together in the Ghost monorepo. This was the first part of the process, which is already complete 🎉

## Technical plan

Our basic requirements are:

- Load a single set of translations + (maybe) a set of custom strings into React widgets
- Load a single set of translations into Ghost core for emails
- Future: Load translations for Admin
- Interpolation tools for Handlebars and React
- Be able to use the default string as the key
- Be able to generate a new language file with all available keys
- Not have to load all translations, e.g., all of Portal’s translations into comments
- Have a single place, in GitHub, where locales are defined
- Ideally: generate a build per locale for each JS widget, e.g., `portal-es.min.js`

That last requirement is the hardest, as we will need to figure out a build step for our react projects… but this should be a one-off piece.

The [i18next 4](https://www.i18next.com/) framework appears to support every single one of our requirements, including [fallback-based keys](https://www.i18next.com/principles/fallback), [namespacing](https://www.i18next.com/principles/namespaces), and having tools for [extracting translation strings](https://www.i18next.com/how-to/extracting-translations). It’s also extensible with plugins and has links to tooling for managing the translation process—so we could have a `yarn check-translations` command to view what’s not translated.

**Expected structure:**

- A new `ghost/locales` folder in the monorepo that contains namespaced JSON files, e.g., `locales/es/common.json`, `locales/es/portal.json` , `locales/es/email.json`
- For Node.js/email-templates we do one of:
    - (Preferred) The correct path is loaded from config based on environment
    - `ghost/locales` gets copied into `ghost/core` somehow
- Then for React:
    - Use code-splitting to generate individual versions of portal, comments, and search for each supported locale.

Here are [some details](https://levelup.gitconnected.com/code-splitting-react-i18n-locales-using-dynamic-imports-97af247ad6f0) of using i18next with Webpack to create a a build step using “code splitting,” but it doesn’t quite achieve what we want. We are currently investigating switching to Vite for our build tool—and this may end up being an additional prerequisite.

## Milestones

This project has prerequisites, is fairly complex, and has lots of different phases in which different community members may wish to get involved. Therefore, we’ve broken the project down into milestones and will keep this thread up to date with where we are.

**Milestone 1: Monorepo**

- Portal needs to be merged into the monorepo ✅
- Comments and search will follow later, but the project can continue without them.

Who: This milestone has been completed by the Ghost team.

**Milestone 2: Framework and build tools**

- Implement the “expected structure” from above
- Get the basic pieces of i18next in place
- Figure out if we need to switch to Vite in Portal (may need help from Ghost team)
- Have one interpolated string in email and Portal
- Tools for extracting strings & generating files

Who: Looking for interested contributors, Ghost team will help and/or kick this off if needed.

**Milestone 3: Interpolated Strings**

- All strings in Portal and Member emails (signup, signin, subscribe) are interpolated using the `{{t}}` helper and the default, existing US English string as the key.
- comments-ui and SodoSearch should also be in the monorepo by now, and so these strings can also be interpolated.

Who: Looking for interested contributors

**Milestone 4: Translations**

- Tooling for extracting strings into new locale files should be perfected
- Getting all strings translated for additional languages.

Who: Looking for interested contributors

---

Check out the issue on GitHub for more info:

{% embed https://github.com/TryGhost/Ghost/issues/15502 %}

Find more info in [our Forum](https://forum.ghost.org/t/portal-and-email-component-translations/19144/29)

If you’re interested in helping with milestones 2-4 of this project, we’d love to hear from you in the comments below 👇