All Projects → ToonvanStrijp → Nestjs I18n

ToonvanStrijp / Nestjs I18n

Licence: other
Add i18n support inside your nestjs project

Programming Languages

typescript
32286 projects

Projects that are alternatives of or similar to Nestjs I18n

nestjs-i18n
The i18n module for nestjs.
Stars: ✭ 376 (+92.82%)
Mutual labels:  i18n, nestjs
Crud
NestJs CRUD for RESTful APIs
Stars: ✭ 2,709 (+1289.23%)
Mutual labels:  nestjs
Nestjs Microservice Architecture
A reference architecture for building microservices with Nest
Stars: ✭ 174 (-10.77%)
Mutual labels:  nestjs
Cldr
Elixir implementation of CLDR/ICU
Stars: ✭ 178 (-8.72%)
Mutual labels:  i18n
Node Gettext
A JavaScript implementation of gettext, a localization framework.
Stars: ✭ 175 (-10.26%)
Mutual labels:  i18n
Nestjs Template
Scaffold quickly your next TypeScript API with this opinionated NestJS template crafted for Docker environments
Stars: ✭ 183 (-6.15%)
Mutual labels:  nestjs
Vue I18next
Internationalization for vue using the i18next i18n ecosystem.
Stars: ✭ 172 (-11.79%)
Mutual labels:  i18n
Mongoose
Mongoose module for Nest framework (node.js) 🍸
Stars: ✭ 191 (-2.05%)
Mutual labels:  nestjs
Elefant
Elefant, the refreshingly simple PHP CMS and web framework.
Stars: ✭ 188 (-3.59%)
Mutual labels:  i18n
Localize Router
An implementation of routes localisation for Angular
Stars: ✭ 177 (-9.23%)
Mutual labels:  i18n
Elasticsearch
Elasticsearch module based on the official elasticsearch package 🌿
Stars: ✭ 176 (-9.74%)
Mutual labels:  nestjs
Typeorm Nestjs Migration Example
"Example of how to use migrations feature of TypeORM with NestJS.
Stars: ✭ 176 (-9.74%)
Mutual labels:  nestjs
Blog Service
blog service @nestjs
Stars: ✭ 188 (-3.59%)
Mutual labels:  nestjs
Extract React Intl Messages
extract react intl messages
Stars: ✭ 174 (-10.77%)
Mutual labels:  i18n
Cool Admin Api
cool-admin-api 是基于egg.js、typeorm、jwt等封装的api开发脚手架、快速开发api接口
Stars: ✭ 188 (-3.59%)
Mutual labels:  nestjs
Ngettext
A cross-platform .NET implementation of the GNU/Gettext library.
Stars: ✭ 172 (-11.79%)
Mutual labels:  i18n
Vue Admin Template
Sample Admin Template based on Vuejs & Vuetify.
Stars: ✭ 177 (-9.23%)
Mutual labels:  i18n
Dayperiodformatter
A formatter for localized day periods (morning, afternoon, night, etc.)
Stars: ✭ 181 (-7.18%)
Mutual labels:  i18n
Nestjs Boilerplate
A production-ready 🏭 NestJS boilerplate with batteries 🔋 included. No Kidding!
Stars: ✭ 192 (-1.54%)
Mutual labels:  nestjs
Hugo Theme Bootstrap4 Blog
A blogging-centric Bootstrap v4 theme for the Hugo static site generator.
Stars: ✭ 191 (-2.05%)
Mutual labels:  i18n

Build Status Coverage Status

Description

The i18n module for Nest.

Installation

$ npm i --save nestjs-i18n

Table of contents

Versions

To keep your setup working use the correct version of nestjs-i18n.

nestjs-i18n version nestjs version
v7.0.0 or greather v7.0.0
v6.0.0 or below v6.0.0

Quick Start

Build in we have a JSON parser (I18nJsonParser) this parser handles to following structure

Structure

create a directory and in it define your language keys as directories.

package.json
package-lock.json
...
src
└── i18n
    ├── en
    │   ├── category.json
    │   └── auth.json
    └── nl
        ├── category.json
        └── auth.json

Translation File

The format of a translation file could look like this:

{
  "HELLO_MESSAGE": "Hello {username}",
  "GOODBYE_MESSAGE": "Goodbye {username}",
  "USER_ADDED_PRODUCT": "{0.username} added {1.productName} to cart",
  "SETUP": {
    "WELCOME": "Welcome {0.username}",
    "GOODBYE": "Goodbye {0.username}"
  },
  "ARRAY": ["ONE", "TWO", "THREE"]
}

All the translations are prefixed with the file name (to prevent collisions). Let's say the filename of the translation file is: user.json. To use the HELLO_MESSAGE translation you would use the following key: user.HELLO_MESSAGE.

String formatting is done by: string-format

nest-cli.json copy i18n

If you've created your project using the @nestjs/cli you can edit the nest-cli.json to automatically copy your i18n folder to your output (dist) folder.

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "assets": ["i18n/**/*"]
  }
}

Translation Module

To use the translation service we first add the module. The I18nModule has a @Global() attribute so you should only import it once.

import { Module } from '@nestjs/common';
import * as path from 'path';
import { I18nModule, I18nJsonParser } from 'nestjs-i18n';

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

Using forRootAsync()

import { Module } from '@nestjs/common';
import * as path from 'path';
import { I18nModule, I18nJsonParser } from 'nestjs-i18n';

@Module({
  imports: [
    I18nModule.forRootAsync({
      useFactory: (configService: ConfigurationService) => ({
        fallbackLanguage: configService.fallbackLanguage, // e.g., 'en'
        parserOptions: {
          path: path.join(__dirname, '/i18n/'),
        },
      }),
      parser: I18nJsonParser,
      inject: [ConfigurationService],
    }),
  ],
  controllers: [],
})
export class AppModule {}

Live reloading / Refreshing translations

To use live reloading use the watch option in the I18nJsonParser. The I18nJsonParser watches the i18n folder for changes and when needed updates the translations or languages.

I18nModule.forRoot({
  fallbackLanguage: 'en',
  parser: I18nJsonParser,
  parserOptions: {
    path: path.join(__dirname, '/i18n/'),
    // add this to enable live translations
    watch: true,
  },
});

To refresh your translations and languages manually:

await this.i18nService.refresh();

GraphQL usage

When using GraphQL it is required to provide the right context. You can do this by importing the GraphQLModule like this:

import { Module } from '@nestjs/common';
import * as path from 'path';
import { I18nModule, I18nJsonParser } from 'nestjs-i18n';

@Module({
  imports: [
    I18nModule.forRoot({
      fallbackLanguage: 'en',
      parser: I18nJsonParser,
      parserOptions: {
        path: path.join(__dirname, '/i18n/'),
      },
    }),
    GraphQLModule.forRoot({
      // USE THIS TO PROVIDE THE RIGHT CONTEXT FOR I18N
      context: ({ req, connection }) => connection ? { req: connection.context } : { req },
      ...
    }),
  ],
  controllers: [],
})
export class AppModule {}

Parser

A default JSON parser (I18nJsonParser) is included.

To implement your own I18nParser take a look at this example i18n.json.parser.ts.

Live translations / languages

To provide live translations you can return an observable within the extended I18nParser class. For and implementation example you can take a look at the i18n.json.parser.ts.

export class I18nMysqlParser extends I18nParser {
  constructor(
    @Inject(I18N_PARSER_OPTIONS)
    private options: I18nJsonParserOptions,
  ) {
    super();
  }

  async languages(): Promise<string[] | Observable<string[]>> {
    // for example do a database call here
    return observableOf(['nl', 'en']);
  }

  async parse(): Promise<I18nTranslation | Observable<I18nTranslation>> {
    // for example do a database call here
    return observableOf({
      nl: {
        HELLO: 'Hallo',
      },
      en: {
        HELLO: 'Hello',
      },
    });
  }
}

Language Resolvers

To make it easier to manage in what language to respond you can make use of resolvers

(note: When using forRootAsync you don't return the resolvers with the rest of the config. You'll need to provide the resolvers like this: example)

@Module({
  imports: [
    I18nModule.forRoot({
      fallbackLanguage: 'en',
      parser: I18nJsonParser,
      parserOptions: {
        path: path.join(__dirname, '/i18n/'),
      },
      resolvers: [
        { use: QueryResolver, options: ['lang', 'locale', 'l'] },
        new HeaderResolver(['x-custom-lang']),
        AcceptLanguageResolver,
        new CookieResolver(['lang', 'locale', 'l']),
      ],
    }),
  ],
  controllers: [HelloController],
})
export class AppModule {}

Currently, there are four built-in resolvers

Resolver Default value
QueryResolver none
HeaderResolver none
AcceptLanguageResolver N/A
CookieResolver lang

Custom resolver

To implement your own resolver (or custom logic) use the I18nResolver interface. The resolvers are provided via the nestjs dependency injection, this way you can inject your own services if needed.

@Injectable()
export class QueryResolver implements I18nResolver {
  constructor(@I18nResolverOptions() private keys: string[]) {}

  resolve(context: ExecutionContext) {
    let req: any;

    switch (context.getType() as string) {
      case 'http':
        req = context.switchToHttp().getRequest();
        break;
      case 'graphql':
        [, , { req }] = context.getArgs();
        break;
    }

    let lang: string;

    if (req) {
      for (const key of this.keys) {
        if (req.query != undefined && req.query[key] !== undefined) {
          lang = req.query[key];
          break;
        }
      }
    }

    return lang;
  }
}

To provide initial options to your custom resolver use the @I18nResolverOptions() decorator, also provide the resolver as followed:

I18nModule.forRoot({
  fallbackLanguage: 'en',
  parser: I18nJsonParser,
  parserOptions: {
    path: path.join(__dirname, '/i18n/'),
  },
  resolvers: [{ use: QueryResolver, options: ['lang', 'locale', 'l'] }],
});

Using forRootAsync()

I18nModule.forRootAsync({
  useFactory: () => {
    return {
      fallbackLanguage: 'en',
      parserOptions: {
        path: path.join(__dirname, '/i18n'),
      },
    };
  },
  parser: I18nJsonParser,
  resolvers: [{ use: QueryResolver, options: ['lang', 'locale', 'l'] }],
});

Using Fallbacks

To configure multiple fallbacks use fallbacks option. You could handle a single language or multiple ones.

(note: In this example, the translations en fr and pt-BR are needed to work correctly.)

I18nModule.forRoot({
  fallbackLanguage: 'en',
  fallbacks: {
    'en-CA': 'fr',
    'en-*': 'en',
    'fr-*': 'fr',
    pt: 'pt-BR',
  },
  parser: I18nJsonParser,
  parserOptions: {
    path: path.join(__dirname, '/i18n/'),
  },
});

Translating with i18n module

I18nLang decorator and I18nService

@Controller()
export class SampleController {
  constructor(private readonly i18n: I18nService) {}

  @Get()
  async sample(@I18nLang() lang: string) {
    await this.i18n.translate('HELLO_MESSAGE', {
      lang: lang,
      args: { id: 1, username: 'Toon' },
    });
    await this.i18n.translate('SETUP.WELCOME', {
      lang: 'en',
      args: { id: 1, username: 'Toon' },
    });
    await this.i18n.translate('ARRAY.0', { lang: 'en' });
  }
}

I18n decorator

@Controller()
export class SampleController {
  @Get()
  async sample(@I18n() i18n: I18nContext) {
    await i18n.translate('HELLO_MESSAGE', {
      args: { id: 1, username: 'Toon' },
    });
    await i18n.translate('SETUP.WELCOME', {
      args: { id: 1, username: 'Toon' },
    });
    await i18n.translate('ARRAY.0');
  }
}

No need to handle lang manually.

I18nRequestScopeService within a custom service using request scoped translation service

@Injectable()
export class SampleService {
  constructor(private readonly i18n: I18nRequestScopeService) {}

  async doFancyStuff() {
    await this.i18n.translate('HELLO_MESSAGE', {
      args: { id: 1, username: 'Toon' },
    });
    await this.i18n.translate('SETUP.WELCOME', {
      args: { id: 1, username: 'Toon' },
    });
    await this.i18n.translate('ARRAY.0');
  }
}

To be used within other services like sending E-mails. The advantage is that you don't have to worry about transporting lang from the Request to your service.

Use with caution! The I18nRequestScopeService uses the REQUEST scope and is no singleton. This will be inherited to all consumers of I18nRequestScopeService! Read Nest Docs for more information.

Dont use I18nRequestScopeService within controllers. The I18n decorator is a much better solution.

Translating HttpExceptions

To translate httpexceptions, we need to create a filter and translate the exception message.

import { I18nService } from 'nestjs-i18n';
import {
	ExceptionFilter,
	Catch,
	ArgumentsHost,
	HttpException,
} from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class AllExceptionsFilter implements ExceptionFilter {
	constructor(private readonly i18n: I18nService) {}

	async catch(exception: HttpException, host: ArgumentsHost) {
		const ctx = host.switchToHttp();
		const response = ctx.getResponse<Response>();
		const statusCode = exception.getStatus();

		let message = exception.getResponse() as {
			key: string;
			args: Record<string, any>;
		};

		message = await this.i18n.translate(message.key, {
			lang: ctx.getRequest().i18nLang,
			args: message.args,
		});

		response.status(statusCode).json({ statusCode, message });
	}
}

Now we need to use this filter as global filter. Since we are using dependency injection, you cannot have dependency injection if you register your filter with useGlobalFilters(). From the docs, register the filter as global filter as :

import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: AllExceptionsFilter,
    },
  ],
})
export class AppModule {}

Now to use the filter , simply throw exceptions like:

  throw new HttpException(
    { key: 'operations.HELLO', args: { username: 'rubin' } },
	HttpStatus.FORBIDDEN,
     );
    

Breaking changes:

  • from V8.0.0 on we changed the internal 18n-middleware for an interceptor this way we can provide the ExecutionContext so that nestjs-i18n works on diffrent protocols was well for example (grpc or websockets). This contains one breaking change. It only applies to your code if you've made a custom resolver. To resolve this breaking change take look at this example. Instead of providing the req in the resolve method, change this to take the ExecutionContext as argument.

  • from V6.0.0 on we implemented the I18nParser, by using this we can easily support different formats other than JSON. To migrate to this change look at the Quick start above. There are some changes in the declaration of the I18nModule. Note: the translate function returns a Promise. So you need to call it using await i18n.translate('HELLO');

  • from V4.0.0 on we changed the signature of the translate method, the language is now optional, if no language is given it'll fallback to the fallbackLanguage

  • from V3.0.0 on we load translations based on their directory name instead of file name. Change your translations files to the structure above: info

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].