Skip to content

Commit

Permalink
feat: add i18n (#30)
Browse files Browse the repository at this point in the history
Co-authored-by: Lam Ngoc Khuong <me@ngockhuong.com>
  • Loading branch information
khuongln-1346 and lamngockhuong authored Jun 18, 2024
1 parent 8352162 commit 521083b
Show file tree
Hide file tree
Showing 14 changed files with 107 additions and 13 deletions.
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ APP_PORT=3000
APP_NAME="NestJS API"
API_PREFIX=api
APP_FALLBACK_LANGUAGE=en
APP_HEADER_LANGUAGE=en

DATABASE_TYPE=postgres
DATABASE_HOST=localhost
Expand Down
3 changes: 2 additions & 1 deletion nest-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
"deleteOutDir": true,
"assets": [{ "include": "i18n/**/*", "watchAssets": true }]
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"argon2": "0.40.3",
"class-transformer": "0.5.1",
"class-validator": "0.14.1",
"nestjs-i18n": "10.4.5",
"pg": "8.12.0",
"reflect-metadata": "0.2.2",
"rxjs": "7.8.1",
Expand Down
41 changes: 41 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/api/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { UserEntity } from './entities/user.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { plainToInstance } from 'class-transformer';
import { UserDto } from './dto/user.dto';
import { I18nContext } from 'nestjs-i18n';

@Injectable()
export class UserService {
Expand All @@ -14,6 +15,8 @@ export class UserService {
private readonly userRepository: Repository<UserEntity>,
) {}
async create(dto: CreateUserDto): Promise<UserDto> {
const i18n = I18nContext.current();

const { username, email, password } = dto;

// check uniqueness of username/email
Expand All @@ -29,7 +32,7 @@ export class UserService {
});

if (user) {
throw new Error('User already exists');
throw new Error(i18n.t('validation.user.errors.userAlreadyExists'));
}

const newUser = new UserEntity({
Expand Down
1 change: 0 additions & 1 deletion src/config/app-config.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ export type AppConfig = {
port: number;
apiPrefix: string;
fallbackLanguage: string;
headerLanguage: string;
};
7 changes: 1 addition & 6 deletions src/config/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ class EnvironmentVariablesValidator {
@IsString()
@IsOptional()
APP_FALLBACK_LANGUAGE: string;

@IsString()
@IsOptional()
APP_HEADER_LANGUAGE: string;
}

export default registerAs<AppConfig>('app', () => {
Expand All @@ -47,7 +43,6 @@ export default registerAs<AppConfig>('app', () => {
? parseInt(process.env.PORT, 10)
: 3000,
apiPrefix: process.env.API_PREFIX || 'api',
fallbackLanguage: process.env.FALLBACK_LANGUAGE || 'en',
headerLanguage: process.env.HEADER_LANGUAGE || 'x-lang',
fallbackLanguage: process.env.APP_FALLBACK_LANGUAGE || 'en',
};
});
1 change: 1 addition & 0 deletions src/i18n/en/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
7 changes: 7 additions & 0 deletions src/i18n/en/validation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"user": {
"errors": {
"userAlreadyExists": "User already exists"
}
}
}
1 change: 1 addition & 0 deletions src/i18n/jp/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
7 changes: 7 additions & 0 deletions src/i18n/jp/validation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"user": {
"errors": {
"userAlreadyExists": "このユーザーは既に存在します"
}
}
}
1 change: 1 addition & 0 deletions src/i18n/vi/common.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
7 changes: 7 additions & 0 deletions src/i18n/vi/validation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"user": {
"errors": {
"userAlreadyExists": "Người dùng đã tồn tại"
}
}
}
37 changes: 34 additions & 3 deletions src/utils/modules-set.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { ModuleMetadata } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ApiModule } from '../api/api.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import appConfig from '../config/app.config';
import { TypeOrmConfigService } from '../database/typeorm-config.service';
import { DataSource, DataSourceOptions } from 'typeorm';
import databaseConfig from '../database/config/database.config';
import {
AcceptLanguageResolver,
HeaderResolver,
I18nModule,
QueryResolver,
} from 'nestjs-i18n';
import { AllConfigType } from '@/config/config.type';
import * as path from 'path';

function generateModulesSet() {
const imports: ModuleMetadata['imports'] = [
Expand All @@ -28,14 +36,37 @@ function generateModulesSet() {
},
});

const i18nModule = I18nModule.forRootAsync({
resolvers: [
{ use: QueryResolver, options: ['lang'] },
AcceptLanguageResolver,
new HeaderResolver(['x-lang']),
],
useFactory: (configService: ConfigService<AllConfigType>) => {
const isDevelopment =
configService.get('app.nodeEnv', { infer: true }) === 'development';
return {
fallbackLanguage: configService.getOrThrow('app.fallbackLanguage', {
infer: true,
}),
loaderOptions: {
path: path.join(__dirname, '/../i18n/'),
watch: isDevelopment,
},
logging: isDevelopment,
};
},
inject: [ConfigService],
});

const modulesSet = process.env.MODULES_SET || 'monolith';

switch (modulesSet) {
case 'monolith':
customModules = [ApiModule, dbModule];
customModules = [ApiModule, dbModule, i18nModule];
break;
case 'api':
customModules = [ApiModule, dbModule];
customModules = [ApiModule, dbModule, i18nModule];
break;
default:
console.error(`Unsupported modules set: ${modulesSet}`);
Expand Down

0 comments on commit 521083b

Please sign in to comment.