How to Localise Your Website With DeepL Translate

How to Localise Your Website With DeepL Translate

In this article, we will translate localisation messages with DeepL.

DeepL is a much more accurate translation method than Google Translate and others. With DeepL, you can avoid manual translation or even hiring translation professionals.

This script can work for virtually all applications that use the following convention:

[directory]/
    en.json
    es.json
    fr.json

And if en.json was the file containing the source language messages:

{
    "8HJxXG": "Sign up",
    "Kvo3A0": "Copyright",
    "QhHk4l": "Home | {name}",
    "U8QBHO": "Powered by",
    "cXBJ7U": "Privacy",
    "g5pX+a": "About",
    "AyGauy": "Login",
    "xkr+zo": "Terms",
    "zFegDD": "Contact"
}

For example, this could be the localisation messages for a simple front-end application.

Now we know what the data looks like, let's get started.

Setting up DeepL Translate

DeepL offers a free translation API. It grants access to all its available features. Go to their API page and click Sign up for free under Deepl API Free. Fill in your details, and you'll receive a free API key.

Using the API

Run the following command to install the DeepL API wrapper into your NodeJS project.

npm i deepl-node

Using CommonJS, you can initialise the wrapper like so:

const authKey = "*******";

const translator = new deepl.Translator(authKey, {
    serverUrl: "https://api-free.deepl.com",
});

Reading all available locales

If you extract your messages with a tool like FormatJS, it will only create the base file. The script needs to know to which other languages to translate the messages. Do this by keeping your message files in a separate directory. Then create empty files for the target locales.

Now, we need a function that gets the names of all the message files in that directory.

const getDirectoryLocales = async (directory) => {
    let filenames = await fs.readdir(directory);

    // Only gets filenames with .json extension
    filenames = filenames.filter((filename) =>
        filename.endsWith(FILE_EXTENSION)
    );

    // Removes the extension from the filenames
    const locales = filenames.map((filename) =>
        filename.slice(0, filename.lastIndexOf(FILE_EXTENSION))
    );
    return locales;
};

Getting the Source Language Messages

Define a function to read the contents of a JSON file and parse it as an object.

const getLocaleMessages = async (filepath) => {
    let messages = {};

    try {
        messages = JSON.parse(await fs.readFile(filepath));
    } catch (e) {
        throw new Error("Couldn't parse source file:", filepath);
    }

    return messages;
};

Translating Messages

Now we need to define a function which translates only the values of an object. It will follow the file convention stated previously.

Once DeepL translates all the values into a target language, we write the object to a new JSON file.

const translateMessages = async (messages, source, locales) => {
    // Used to recreate object with translated values
    const keys = Object.keys(messages);

    const values = Object.values(messages);

    let translatedMessages = {};
    let translatedValues = [];

    for (let locale of locales) {
        // Skip overwriting source language
        if (locale === source) continue;

        translatedMessages = {};

        translatedValues = await translator.translateText(
            values,
            sourceLanguage,
            locale
        );

        translatedValues.forEach((translatedValue, i) => {
            translatedMessages[keys[i]] = translatedValue;
        });

        await fs.writeFile(
            path.join(targetDirectory, locale + FILE_EXTENSION),
            JSON.stringify(translatedMessages)
        );
    }
};

Wrapping up

Here is the code for the entire file. We export a function that will follow all the steps we created in order.

const deepl = require("deepl-node");
const fs = require("fs/promises");
const path = require("path");

const FILE_EXTENSION = ".json";

const authKey = "*******";

const translator = new deepl.Translator(authKey, {
    serverUrl: "https://api-free.deepl.com",
});

const getDirectoryLocales = async (directory) => {
    let filenames = await fs.readdir(directory);

    // Only gets filenames with .json extension
    filenames = filenames.filter((filename) =>
        filename.endsWith(FILE_EXTENSION)
    );

    // Removes the extension from the filenames
    const locales = filenames.map((filename) =>
        filename.slice(0, filename.lastIndexOf(FILE_EXTENSION))
    );
    return locales;
};

const getLocaleMessages = async (filepath) => {
    let messages = {};

    try {
        messages = JSON.parse(await fs.readFile(filepath));
    } catch (e) {
        throw new Error("Couldn't parse source file:", filepath);
    }

    return messages;
};

const translateMessages = async (messages, source, locales) => {
    // Used to recreate object with translated values
    const keys = Object.keys(messages);

    const values = Object.values(messages);

    let translatedMessages = {};
    let translatedValues = [];

    for (let locale of locales) {
        // Skip overwriting source language
        if (locale === source) continue;

        translatedMessages = {};

        translatedValues = await translator.translateText(
            values,
            sourceLanguage,
            locale
        );

        translatedValues.forEach((translatedValue, i) => {
            translatedMessages[keys[i]] = translatedValue;
        });

        await fs.writeFile(
            path.join(targetDirectory, locale + FILE_EXTENSION),
            JSON.stringify(translatedMessages)
        );
    }
};

exports.localise = async (targetDirectory, sourceLanguage) => {
    const locales = await getDirectoryLocales(targetDirectory);

    if (!locales.includes(sourceLanguage)) {
        throw new Error("Source language file not found: " + sourceLanguage);
    }

    const messages = await getLocaleMessages(
        path.join(targetDirectory, sourceLanguage + FILE_EXTENSION)
    );

    await translateMessages(messages, source, locales);
};

Great! Now you know how to add a script to localise the messages for your web page.

For a more functional example of this feature, check out SimpleLocalise. They offer free hosting of your translation files and a nice UI to manage translations. They also provide integrations that work with popular i18n libraries like FormatJS. On top of that, there are many different guides for usage with any type of application, via their REST API.

Stay tuned for more if you enjoyed it. Thank you and goodbye.

Did you find this article valuable?

Support Wool Doughnut by becoming a sponsor. Any amount is appreciated!