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
{
"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} }})"
}
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
.
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"assets": [{ "include": "i18n/**/*", "watchAssets": true }]
}
}
When using a monorepo structure don't forget to set the outDir
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"assets": [
{
"include": "i18n/**/*",
"watchAssets": true,
+ "outDir": "dist/apps/api"
}
]
}
}
Module setup
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
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;
}
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.
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 🎉.
@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.
Name | Default value |
---|---|
QueryResolver | ['lang'] |
HeaderResolver | [] |
AcceptLanguageResolver | {matchType: 'strict-loose' |
CookieResolver | lang |
GraphQLWebsocketResolver | N/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.
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
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 {}
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
.
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:
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.