Nest 实现国际化

1,569 阅读4分钟

如果你的网站需要支持多种语言访问,那么就需要实现国际化功能。
这样,中文用户访问时看到的是中文界面,英文用户访问时看到的是英文界面。
不仅前端需要国际化,后端也需要国际化。否则,当英文用户使用英文界面登录时,突然遇到一个“用户不存在”的错误提示,会感到非常困惑。
本文将介绍如何在 Nest 框架中实现国际化功能。

初始化项目

首先,创建一个新的 Nest 项目:

nest new nest-i18n-test -p pnpm

安装 nestjs-i18n 包:

pnpm install nestjs-i18n

配置 I18nModule:

在 AppModule 中引入 I18nModule:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { I18nModule, QueryResolver } from 'nestjs-i18n';
import * as path from 'path';

@Module({
  // 导入模块
  imports: [
    // 配置国际化模块
    I18nModule.forRoot({
      // 设置默认语言为英语
      fallbackLanguage: 'en',
      // 加载器选项
      loaderOptions: {
        // 指定国际化文件路径
        path: path.join(__dirname, '/i18n/'),
        // 监视文件变化
        watch: true,
      },
      // 配置解析器,支持通过查询参数 'lang' 或 'l' 来指定语言
      resolvers: [new QueryResolver(['lang', 'l'])],
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

添加国际化资源包

在 src 目录创建 i18n 目录,然后创建语言资源文件:
i18n/en/test.json:

{
  "hello": "Hello World"
}

i18n/zh/test.json:

{
  "hello": "你好世界"
}

同时,在 nest-cli.json 中配置资源文件的自动复制:

"assets": [
  { "include": "i18n/**/*", "watchAssets": true }
]

image.png

修改 AppService 以使用国际化

import { Inject, Injectable } from '@nestjs/common';
import { I18nContext, I18nService } from 'nestjs-i18n';

@Injectable()
export class AppService {
  @Inject()
  private readonly i18n: I18nService;

  getHello(): string {
    return this.i18n.t('test.hello', { lang: I18nContext.current().lang });
  }
}

I18nService 从资源文件中获取 test.hello 的值,使用当前语言。

运行项目

pnpm run start:dev

在浏览器中访问,可以看到界面根据语言环境自动切换:
image.png
image.png

使用其他语言解析器

除了 QueryResolver,还可以使用其他解析器,
例如根据自定义 header、cookie 或 accept-language 头来解析语言:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import {
  AcceptLanguageResolver,
  CookieResolver,
  HeaderResolver,
  I18nModule,
  QueryResolver,
} from 'nestjs-i18n';
import * as path from 'path';

@Module({
  imports: [
    // 配置国际化模块
    I18nModule.forRoot({
      // 设置默认语言为英语
      fallbackLanguage: 'en',
      // 配置语言文件加载选项
      loaderOptions: {
        // 设置语言文件的路径
        path: path.join(__dirname, '/i18n/'),
        // 启用文件监视,自动重载语言文件
        watch: true,
      },
      // 配置语言解析器
      resolvers: [
        // 解析 URL 查询参数中的语言设置,支持 "lang" 和 "l" 参数
        new QueryResolver(['lang', 'l']),
        // 解析 HTTP 头部中的自定义语言设置,支持 "x-custom-lang" 头部
        new HeaderResolver(['x-custom-lang']),
        // 解析 Cookie 中的语言设置,支持 "lang" Cookie
        new CookieResolver(['lang']),
        // 解析请求头中的 Accept-Language 设置
        AcceptLanguageResolver,
      ],
    }),
  ],
  // 设置控制器
  controllers: [AppController],
  // 设置服务提供者
  providers: [AppService],
})
export class AppModule {}

可以在 Postman 中测试 cookie 解析器,通过添加 cookie 来切换语言:
image.png
image.png
加一个 lang=zh:
image.png
返回就变成中文了:
image.png

在 DTO 中使用国际化

由于 DTO 不在 IoC 容器中,无法直接注入 I18nService。可以使用 I18nValidationPipe 来实现国际化验证消息。

安装验证相关的包

pnpm install class-validator class-transformer

修改 CreateUserDto 以使用国际化验证消息

加一个模块:

nest g resource user
import { IsNotEmpty, MinLength } from "class-validator";

export class CreateUserDto {
  @IsNotEmpty({
    message: "validate.usernameNotEmpty"
  })
  username: string;
  
  @IsNotEmpty({
    message: 'validate.passwordNotEmpty'
  })
  @MinLength(6, {
    message: 'validate.passwordNotLessThan6'
  })
  password: string;                    
}

全局启用 I18nValidationPipe

在 main.ts 中配置:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { I18nValidationExceptionFilter, I18nValidationPipe } from 'nestjs-i18n';

async function bootstrap() {
  // 创建 Nest 应用实例
  const app = await NestFactory.create(AppModule);

  // 使用全局验证管道,进行国际化验证
  app.useGlobalPipes(new I18nValidationPipe());

  // 使用全局异常过滤器,处理国际化验证异常
  app.useGlobalFilters(
    new I18nValidationExceptionFilter({
      detailedErrors: false, // 设置是否显示详细错误信息
    }),
  );

  // 启动应用,监听 3000 端口
  await app.listen(3000);
}

// 调用 bootstrap 函数启动应用
bootstrap();

添加验证资源包

i18n/zh/validate.json:

{
  "usernameNotEmpty": "用户名不能为空",
  "passwordNotEmpty": "密码不能为空",
  "passwordNotLessThan6": "密码不能少于 6 位"
}

i18n/en/validate.json:

{
  "usernameNotEmpty": "The username cannot be empty",
  "passwordNotEmpty": "Password cannot be empty",
  "passwordNotLessThan6": "The password cannot be less than 6 characters"
}

测试

通过上述配置,可以实现根据语言环境返回不同的验证消息:
image.png
修改 cookie:
image.png
报错信息为英文:
image.png

使用占位符实现动态消息

可以在文案中使用占位符:
image.png
然后传入参数:

@MinLength(6, {
  message: i18nValidationMessage("validate.passwordNotLessThan6", {
    len: 66
  })
})

image.png
在 I18nService 的 API 中同样支持占位符:
image.png

import { Inject, Injectable } from '@nestjs/common';
import { I18nContext, I18nService } from 'nestjs-i18n';

@Injectable()
export class AppService {
  @Inject()
  private readonly i18n: I18nService;

  getHello(): string {
    return this.i18n.t('test.hello', {
      lang: I18nContext.current().lang,
      args: {
        name: '云牧',
      },
    });
  }
}

image.png
通过这些配置,Nest 项目可以灵活支持多种语言,满足国际化需求。