Skip to main content

Quickstart


In this quick start you'll learn how to do a basic i18n setup.

Installation

yarn add @softkit/i18n

Setup translation files

By default @softkit/i18n uses the I18nJsonLoader loader class. This loader reads translations from json files. Create a folder named i18n in the src folder of your project.

package.json
package-lock.json
...
src
└── i18n
├── en
│   ├── events.json
│   └── test.json
└── nl
   ├── events.json
   └── test.json
src/i18n/en/test.json
{
"HELLO": "Hello",
"PRODUCT": {
"NEW": "New Product: {name}"
},
"ENGLISH": "English",
"ARRAY": ["ONE", "TWO", "THREE"],
"cat": "Cat",
"cat_name": "Cat: {name}",
"set-up-password": {
"heading": "Hello, {username}",
"title": "Forgot password",
"followLink": "Please follow the link to set up your password"
},
"day_interval": {
"one": "Every day",
"other": "Every {count} days",
"zero": "Never"
},
"nested": "We go shopping: $t(test.day_interval, {{\"count\": {count} }})"
}
caution

The i18n folder isn't automatically copied to your dist folder during the build process. To enable nestjs to do this modify the compilerOptions inside nest-cli.json.

nest-cli.json
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"assets": [{ "include": "i18n/**/*", "watchAssets": true }]
}
}
caution

When using a monorepo structure don't forget to set the outDir

nest-cli.json
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"assets": [
{
"include": "i18n/**/*",
"watchAssets": true,
+ "outDir": "dist/apps/api"
}
]
}
}

Module setup

src/app.module.ts
import { Module } from '@nestjs/common';
import * as path from 'path';
import { I18nModule } from '@softkit/i18n';

@Module({
imports: [
I18nModule.forRoot({
fallbackLanguage: 'en',
loaders: [
new I18nJsonLoader({
path: path.join(__dirname, '/i18n/'),
}),
],
}),
],
controllers: [],
})
export class AppModule {}

The async way @softkit/i18n is to use I18nModule.forRootAsync

src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import * as path from 'path';
import {
I18nModule,
AcceptLanguageResolver,
QueryResolver,
HeaderResolver,
} from '@softkit/i18n';

@Module({
imports: [
I18nModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
fallbackLanguage: configService.getOrThrow('FALLBACK_LANGUAGE'),
loaders: [
new I18nJsonLoader({
path: path.join(__dirname, '/i18n/'),
}),
],
}),
resolvers: [
{ use: QueryResolver, options: ['lang'] },
AcceptLanguageResolver,
new HeaderResolver(['x-lang']),
],
inject: [ConfigService],
}),
],
controllers: [],
})
export class AppModule {}

I18nOptions

export interface I18nOptions {
// The default language to use as a fallback if a translation is not available in the requested language.
fallbackLanguage: string;

// An optional dictionary of fallback languages for specific keys or phrases.
fallbacks?: { [key: string]: string };

// An array of resolvers used to resolve the requested translation.
resolvers?: I18nOptionResolver[];

// The loader type to use for loading translation data.
loader?: Type<I18nLoader>;

// Multiple loaders.
loaders: I18nLoader<unknown>[];

// A formatter for formatting translations (e.g., for date or number formatting).
formatter?: Formatter;

// Whether or not to enable logging for i18n operations.
logging?: boolean;

// The view engine to use for rendering templates (if applicable).
viewEngine?: 'hbs' | 'pug' | 'ejs';

// Whether to disable any middleware related to i18n.
disableMiddleware?: boolean;

// Whether to skip asynchronous hooks related to i18n.
skipAsyncHook?: boolean;

// Configuration options for the i18n validator.
validatorOptions?: I18nValidatorOptions;

// Whether to throw an error when a translation key is missing.
throwOnMissingKey?: boolean;

// The output path for generated types (if any).
typesOutputPath?: string;
}
caution

The I18nModule is a global module. This means you'll only need to register the module once (in the root module). After that it's accessible throughout the whole application.

Live reloading 🎉

To enable live reloading, use the -w flag in your scripts.

For instance, in your package.json, you might have scripts like these:

"scripts": {
"generate-types": "ts-node --project ./tsconfig.app.json ../../node_modules/@softkit/i18n/src/lib/cli.js generate-types -w",
"generate": "i18n generate-types -w -t json -p ./src/app/i18n/"
}

The -w flag activates the watch mode, which watches for file changes and automatically reloads the relevant parts of your application 🎉.

Type safety 🎉

@softkit/i18n now comes with type safety as well! Click here to see how 🎉.

Add resolvers

Resolvers are used for getting the current language of our request. In basic web applications this is done via the Accept-Language header. But in many cases you want to override this language by your logged in user settings, or some header you define yourself.

@softkit/i18n comes with a set of built-in resolvers.

NameDefault value
QueryResolver['lang']
HeaderResolver[]
AcceptLanguageResolver{matchType: 'strict-loose'
CookieResolverlang
GraphQLWebsocketResolverN/A
GrpcMetadataResolver['lang']

To add resolvers add them to the resolvers array in your I18nModule options. The way @softkit/i18n works it's going to resolve the language in order. So in this case it tries the QueryResolver first, if it can't resolve a language it'll jump to the next one.

src/app.module.ts
import { Module } from '@nestjs/common';
import * as path from 'path';
import {
AcceptLanguageResolver,
I18nJsonLoader,
I18nModule,
QueryResolver,
} from '@softkit/i18n';

@Module({
imports: [
I18nModule.forRoot({
fallbackLanguage: 'en',
loaders: [
new I18nJsonLoader({
path: path.join(__dirname, '/i18n/'),
}),
],
resolvers: [
{ use: QueryResolver, options: ['lang'] },
AcceptLanguageResolver,
],
}),
],
controllers: [],
})
export class AppModule {}

or in forRootAsync

src/app.module.ts
import { Module } from '@nestjs/common';
import * as path from 'path';
import {
AcceptLanguageResolver,
QueryResolver,
HeaderResolver,
CookieResolver,
I18nJsonLoader,
I18nModule,
} from '@softkit/i18n';

@Module({
imports: [
I18nModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
fallbackLanguage: 'en',
loaders: [
new I18nJsonLoader({
path: path.join(__dirname, '/i18n/'),
}),
],
}),
resolvers: [
new QueryResolver(['lang', 'l']),
new HeaderResolver(['x-custom-lang']),
new CookieResolver(),
AcceptLanguageResolver,
],
inject: [ConfigService],
}),
],
controllers: [],
})
export class AppModule {}
tip

It's possible to create your own resolvers! For example if you want to resolve the language from your logged-in user's settings. Please see the resolvers page for instructions.

Translate stuff 🎉

Now that we've setup everything we can start to do translations! The easiest way to do this is in your controller.

src/app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { I18n, I18nContext } from '@softkit/i18n';

@Controller()
export class AppController {
@Get()
async getHello(@I18n() i18n: I18nContext) {
return await i18n.t('test.HELLO');
}
}

You can also do translation on your service as:

src/app.service.ts
import { Injectable } from '@nestjs/common';
import { I18nContext, I18nService } from '@softkit/i18n';

@Injectable()
export class AppService {
constructor(private readonly i18n: I18nService) {}
getHello(): string {
return this.i18n.t('test.HELLO', { lang: I18nContext.current().lang });
}
}

Translate options

export type TranslateOptions = {
/**
* Language to translate to
*/

lang?: string;

/**
* Arguments to pass to the translation
*/

args?: ({ [k: string]: any } | string)[] | { [k: string]: any };

/**
* Default value to return when no translation is found
*/

defaultValue?: string;

/**
* Debug mode
*/

debug?: boolean;
};

Example

A working example is available here.