i18n

Internationalization (short: i18n) involves the translation of Jovo app content into different locales. It also comes with the benefit of separating content from logic.

Introduction

In a Jovo app, i18n means that content is stored either in distinct language files (e.g. en.json or en-US.json) or a CMS instead of inside handlers or output classes. The app then retrieves the right content depending on the current request's language/locale.

There are multiple benefits that come with this approach:

  • It makes an app work across languages (e.g. English en and German de) without having to touch the core logic.
  • It offers the possibility to create localized content, even in the same language (e.g US en-US and UK en-UK English).
  • Even for apps that only support one language, it separates content from logic by storing all strings in a separate, isolated file.

Jovo uses a package called i18next to support internationalization. You can find its documentation here..

With Jovo and i18next, output that is dependent on a locale can get outsourced into language resource files:

// src/i18n/en.json
{
  "translation": {
    "hello": "Hello World!"
  }
}

In the handlers, the hard-coded string would then be replaced by the $t() method:

// Without i18n
return this.$send('Hello World!');

// With i18n
return this.$send(this.$t('hello'));

The integration can also be used in output classes:

// Without i18n
build() {
  return {
    message: 'Hello World!',
  }
}

// With i18n
build() {
  return {
    message: this.$t('hello'),
  }
}

Learn more about the different ways of returning output in the output docs.

Configuration

Add an i18n object to your app configuration, for example in app.ts:

import { App } from '@jovotech/framework';
// ...

const app = new App({
  // ...

  i18n: {
    // Configuration
  },
});

You can add any configuration that is available for i18next. You can find all configuration options here. The default is:

interpolation: { // https://www.i18next.com/translation-function/interpolation
  escapeValue: false,
},
returnObjects: true, // https://www.i18next.com/translation-function/objects-and-arrays

The most relevant option is the resources object that contains references to all language files. The example below imports a file for the en locale and adds it as resource:

import { App } from '@jovotech/framework';
import en from './i18n/en.json';

// ...

const app = new App({
  // ...

  i18n: {
    resources: {
      en,
    },
  },
});

Import all language resource files as shown in the example above and add them to the resources object. Any locale key like en, en-US, de-DE can be used:

{
  i18n: {
    resources: {
        localeKey: localeResources, // localeKey: en, 'en-US', 'de-DE', ...
    },
  },
  // ...
}

Content Structure

i18n content can usually be found in a folder called i18n or languageResources in your app's src folder. Each locale has its own file (e.g. en.json) that includes all the content wrapped in a translation object:

{
  "translation": {
    "hello": "Hello World!",
    "goodbye": "See you soon."
  }
}

i18next provides many features to structure your content. You can find more information in their official documentation about interpolation, formatting, plurals, and more.

In the next sections, we'll look specifically into parameters, randomization, nested objects, and platform specific translations.

Parameters

You can pass parameters to the $t() method and reference them like this:

{
  "translation": {
    "hello": "Hello {{name}}!"
  }
}

For example, the following method call would return Hello Sam!:

this.$t('hello', { name: 'Sam' });

See content access for more information.

Arrays and Randomization

The $t() method can also return arrays and objects if the returnObjects configuration is set to true, which is the default in the Jovo i18next integration.

{
  "translation": {
    "hello": ["Hello!", "Hi", "Howdy!"]
  }
}

Arrays of strings can be used to randomize content for message and reprompt elements of an output template. Only one of the strings gets randomly selected. This allows for variation in your app's content without making any code modifications.

By default, $t() is expected to return a string. This is why you need to typecast to a different type if you're returning something else, for example:

this.$t<string[]>('hello');

// Alternative
this.$t('hello') as string[];

Nested Objects

i18n also supports nested objects:

{
  "translation": {
    "hello": {
      "message": "Hello World! What's your name?"
    }
  }
}

The message above could be referenced like this:

this.$t('hello.message');

The $t() method can also return full objects. This allows for some interesting and flexible ways how to structure your content. For example, i18n resources could be structured in the same way as Jovo output templates:

{
  "translation": {
    "hello": {
      "message": "Hello World! What's your name?",
      "reprompt": "Could you tell me your name?"
    }
  }
}

In an output class, the object could then be returned like this:

build() {
  return this.$t('hello');
}

By default, $t() is expected to return a string. This is why you need to typecast to a different type if you're returning something else, for example:

this.$t<{ message: string; reprompt: string }>('hello');

This feature requires the returnObjects configuration to be set to true, which is the default in the Jovo i18next integration.

Platform Specific Translations

For platform specific translations, you can add an object with the platform name (in camel case, for example alexa, googleAssistant) that includes translations that override the default translations:

{
  "translation": {
    "hello": {
      "message": "Hello World! What's your name?"
    }
  },
  "alexa": {
    "translation": {
      "hello": {
        "message": "Hello World on Alexa! What's your name?"
      }
    }
  }
}

Jovo automatically returns the right string depending on the current platform.

Content Access

You can access the content both using this.$t(). Learn more about all options in the official i18next documentation.

this.$t(key, options);

// Example without parameters
this.$t('hello');

// Example with parameters
this.$t('hello', { name: 'Sam' });

// Example with nested object
this.$t('hello.message');

Usually this is done either inside a handler:

// Without i18n
return this.$send({ message: 'Hello World!' });

// With i18n
return this.$send({ message: this.$t('hello') });

Or inside an output class:

// Without i18n
build() {
  return {
    message: 'Hello World!',
  }
}

// With i18n
build() {
  return {
    message: this.$t('hello'),
  }
}

By default, $t() is expected to return a string. This is why you need to typecast to a different type if you're returning something else, for example:

this.$t<string[]>('hello');

// Alternative
this.$t('hello') as string[];