DEV Community

Cover image for Internationalization (i18n) for Deno with i18next
Adriano Raiano
Adriano Raiano

Posted on

Internationalization (i18n) for Deno with i18next

You may already know how to properly internationalize a client side application, like described in this React based tutorial, this Angular based tutorial or this Vue based tutorial.

In this blog post we will shed light on Deno.

Why do I need to handle i18n in Deno?

Think of all user faced content not directly rendered in your browser...

Let's check that out...

We will show some examples that uses i18next as i18n framework. If you're curious to know why we suggest i18next, have a look at this page.

Command line interface (CLI)

Let's start with something simple: a verry small CLI app.
We are defining a sayhi command with optional language and name parameters that should respond with a salutation in the appropriate language.

import { parse } from "https://deno.land/std/flags/mod.ts";

const { args } = Deno;
const parsedArgs = parse(args);

const cmd = parsedArgs._[0];

if (cmd !== "sayhi" && cmd !== "s") {
  throw new Error(`unknown command ${cmd}`);
}

const name = parsedArgs.n || parsedArgs.name;
const language = parsedArgs.l || parsedArgs.language;

console.log({ name, language })
Enter fullscreen mode Exit fullscreen mode

Ok, now let's create a new i18n.ts file and setup i18next accordingly:

import i18next from "https://deno.land/x/i18next/index.js";
import enTranslation from "./locales/en/translation.json" assert {
  type: "json",
};
import deTranslation from "./locales/de/translation.json" assert {
  type: "json",
};

const systemLocale = Intl.DateTimeFormat().resolvedOptions().locale;

i18next
  .use(Backend)
  .init({
    // debug: true,
    fallbackLng: "en",
    resources: {
      en: {
        translation: enTranslation,
      },
      de: {
        translation: deTranslation,
      },
    }
  });

export default (lng: string | undefined | null) =>
  i18next.getFixedT(lng || systemLocale);
Enter fullscreen mode Exit fullscreen mode

And also our translation resources:

// locales/en/translations.json
{
  "salutation": "Hello World!",
  "salutationWithName": "Hello {{name}}!"
}

// locales/de/translations.json
{
  "salutation": "Hallo Welt!",
  "salutationWithName": "Hallo {{name}}!"
}
Enter fullscreen mode Exit fullscreen mode

Now we can use the i18n.ts export like that:

import { parse } from "https://deno.land/std/flags/mod.ts";
import i18n from "./i18n.ts";

const { args } = Deno;
const parsedArgs = parse(args);

const cmd = parsedArgs._[0];

if (cmd !== "sayhi" && cmd !== "s") {
  throw new Error(`unknown command ${cmd}`);
}

const name = parsedArgs.n || parsedArgs.name;
const language = parsedArgs.l || parsedArgs.language;

const t = i18n(language);
if (name) {
  console.log(t("salutationWithName", { name }));
} else {
  console.log(t("salutation"));
}
Enter fullscreen mode Exit fullscreen mode

Ok, what's the result?

# if we execute the cli command without any parameters...
deno run --allow-read mod.ts sayhi
# result: Hello World!

# if we execute the cli command with a language parameter...
deno run --allow-read mod.ts sayhi --language de
# result: Hallo Welt!

# if we execute the cli command with a language parameter and a name parameter...
deno run --allow-read mod.ts sayhi --language de --name John
# result: Hallo John!
Enter fullscreen mode Exit fullscreen mode

Easy, isn't it?

You can also i.e. use the i18next-fs-backend to dynamically load your translations, for example like this:

import i18next from "https://deno.land/x/i18next/index.js";
import Backend from "https://deno.land/x/i18next_fs_backend/index.js";

const systemLocale = Intl.DateTimeFormat().resolvedOptions().locale;

i18next
  .use(Backend)
  .init({
    // debug: true,
    initImmediate: false, // setting initImediate to false, will load the resources synchronously
    fallbackLng: "en",
    preload: ['en', 'de'],
    backend: {
      loadPath: "locales/{{lng}}/{{ns}}.json",
    },
  });

export default (lng: string | undefined | null) =>
  i18next.getFixedT(lng || systemLocale);
Enter fullscreen mode Exit fullscreen mode

๐Ÿง‘โ€๐Ÿ’ป A code example can be found here.

A possible next step...

A possible next step could be to professionalize the translation management.
This means the translations would be "managed" (add new languages, new translations etc...) in a translation management system (TMS), like locize and synchronized with your code. To see how this could look like, check out Step 1 in this tutorial.

Server Side Rendering (SSR)

For this example we will use the http framework abc (created by ๆœจๆ‰, but any other framework will also work.

This time we will use a different i18next module, i18next-http-middleware.
It can be used for all Deno web frameworks, like abc or ServestJS, but also for Node.js web frameworks, like express or Fastify.

As already said, here we will use abc.

Let's again start with the i18n.js file:

import i18next from 'https://deno.land/x/i18next/index.js'
import Backend from 'https://deno.land/x/i18next_fs_backend/index.js'
import i18nextMiddleware from 'https://deno.land/x/i18next_http_middleware/index.js'

i18next
  .use(Backend)
  .use(i18nextMiddleware.LanguageDetector)
  .init({
    // debug: true,
    initImmediate: false, // setting initImediate to false, will load the resources synchronously
    backend: {
      loadPath: 'locales/{{lng}}/{{ns}}.json'
    },
    fallbackLng: 'en',
    preload: ['en', 'de', 'it']
  })

export const i18n = i18next
export const middleware = i18nextMiddleware
Enter fullscreen mode Exit fullscreen mode

And our translation resources...

// locales/en/translations.json
{
  "home": {
    "title": "Hello World!"
  },
  "server": {
    "started": "Server is listening on port {{port}}."
  }
}

// locales/de/translations.json
{
  "home": {
    "title": "Hallo Welt!"
  },
  "server": {
    "started": "Der server lauscht auf dem Port {{port}}."
  }
}

// locales/it/translations.json
{
  "home": {
    "title": "Ciao Mondo!"
  },
  "server": {
    "started": "Il server sta aspettando sul port {{port}}."
  }
}
Enter fullscreen mode Exit fullscreen mode

A simple ejs template:

<html>

  <head>
      <title>i18next - abc with dejs</title>
  </head>

  <body>
      <h1><%= t('home.title') %></h1>
      <div><a href="/?lng=en">english</a>&nbsp; | &nbsp;<a href="/?lng=de">deutsch</a> | &nbsp;<a href="/?lng=it">italiano</a></div>
      <hr />
      <div><a href=<%= "/raw?lng=" + i18n.resolvedLanguage %>>raw test</a></div>
  </body>

</html>
Enter fullscreen mode Exit fullscreen mode

Our "main" file index.js:

// deno run --allow-net --allow-read index.js
import { Application } from 'https://deno.land/x/abc/mod.ts'
import { config } from "https://deno.land/x/dotenv/mod.ts"
import { i18n, middleware } from './i18n.js'
import { renderFile } from 'https://deno.land/x/dejs/mod.ts'

const port = config.PORT || 8080
const app = new Application()

app.renderer = {
  render(name, data) {
    return renderFile(`./views/${name}.html`, data)
  }
}

const handle = middleware.handle(i18n)

app.use((next) =>
  (c) => {
    handle(c)
    return next(c)
  }
)

app.get('/', (c) => c.render('index', { t: c.request.t, i18n: c.request.i18n }))
app.get('/raw', (c) => c.request.t('home.title'))

app.start({ port })

console.log(i18n.t('server.started', { port }))
console.log(i18n.t('server.started', { port, lng: 'de' }))
console.log(i18n.t('server.started', { port, lng: 'it' }))
Enter fullscreen mode Exit fullscreen mode

Now start the app and check what language you're seeing...
dejs abc

If you check the console output you'll also see something like this:

node app.js
# Server is listening on port 8080.
# Der server lauscht auf dem Port 8080.
# Il server sta aspettando sul port 8080.
Enter fullscreen mode Exit fullscreen mode

Yes, if you like, you can also internationalize your log statements ๐Ÿ˜

๐Ÿง‘โ€๐Ÿ’ป A code example can be found here.

A possible next step...

Do you wish to manage your translations in a translation management system (TMS), like locize?

Just use this cli to synchronize the translations with your code. To see how this could look like check out Step 1 in this tutorial.

Alternatively, use i18next-locize-backend instead of the i18next-fs-backend.
If you're running your code in a serverless environment, make sure you read this advice first!

๐ŸŽ‰๐Ÿฅณ Conclusion ๐ŸŽŠ๐ŸŽ

As you see i18n is also important for Deno.

I hope youโ€™ve learned a few new things about Deno server side internationalization and modern localization workflows.

So if you want to take your i18n topic to the next level, it's worth to try i18next and also locize.

๐Ÿ‘

Top comments (0)