NestJSで多言語化対応を行う方法
トラストハブが運営しているCloveオリパは海外からアクセスするユーザーが増加しており、より積極的なサポートのために海外ユーザー向けに多言語化対応を行っています。
トラストハブではほぼ全てのサービスのバックエンドAPIにNestJSを使っており、nestjs-i18nというパッケージを導入することで多言語化対応を実現しています。
nestjs-i18nの導入方法
まずは、nestjs-i18nをインストールします
pnpm install nestjs-i18n
最初に翻訳ファイルを作成します。
トラストハブでは、src/libディレクトリ配下にライブラリ関連のファイルをまとめているので、src/lib/i18nディレクトリ配下に翻訳ファイルを作成することにしました。
src/libs/i18n
├── en
│ └── common.json
├── ja
│ └── common.json
├── zh-CN
│ └── common.json
└── zh-TW
└── common.json
src/libs/i18n配下のディレクトリ名は言語コードと一致するようにしています。例えば、enは英語、jaは日本語を指します。
各ディレクトリ内には、キーが一致したjsonファイルを作成します。今回の例では、キーを日本語に揃えています。
src/libs/i18n/ja/comon.json
{
"株式会社 トラストハブ": "株式会社 トラストハブ",
"こんにちは、name様": "こんにちは、{name}様"
}
src/libs/i18n/en/comon.json
{
"株式会社 トラストハブ": "Trust Hub Inc.",
"こんにちは、name様": "Hello {name}"
}
次に、nestjs-cli.jsonに設定を追加します。
NestJSはアプリケーションをビルドする際、jsonなどの静的ファイルを出力先のファイルに自動でコピーしてくれません。先ほどsrc/libs/i18nフォルダ内に作成したjsonファイルをビルド結果に反映できるようにcompilerOptions.assets
に以下の設定を行う必要があります。
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"assets": [
{
"include": "libs/i18n/**/*",
"watchAssets": true,
"outDir": "dist"
}
]
}
}
watchAssets
は必須ではありませんが、watchAssets: true
にすると開発中にjsonファイルの変更を自動で検知して再ビルドしてくれるようになるので便利です。outDir
はご自身のプロジェクトに合わせて設定してください。
次に、AppModuleにi18nModuleをインポートします
I18nModule.forRoot({
fallbackLanguage: 'ja',
loaderOptions: {
path: path.join(__dirname, 'libs/i18n/'),
watch: true,
},
resolvers: [
// リクエストURLのクエリパラメータ`lang`から言語解決する
// 例: `https://api.prd.clove.jp?lang=en`
{ use: QueryResolver, options: ['lang'] },
// リクエストヘッダの`Accept-Language`から言語解決する
AcceptLanguageResolver,
],
}),
- fallbackLanguage
- 言語指定がなかった場合に、fallbackLanguageに指定した値が使用されます。
- 上記の例だと、デフォルトが
ja
(日本語)になります。
- loaderOptions
- 言語翻訳ファイルが格納されているディレクトリを指定します。
- 上記のように指定すると、AppModule.tsファイルと同じ階層のlibs/i18nディレクトリを言語翻訳ファイルの格納先として参照してくれます
- resolvers
- 「リクエストを受け付けた際に、どの言語で処理を行うか」を設定します。
- 上記の例だと、以下のようなルールになります
- 最初にリクエストURLのクエリパラメータから判断する。
https://api.prd.clove.jp?lang=en
のようになっていたら、en
(英語)として扱います。 - リクエストURLのクエリパラメータに値がなかった場合は、リクエストヘッダの
Accept-Language
から判断を行います。Accept-Language: ja
のようになっていたら、ja(日本語)として扱います。
- 最初にリクエストURLのクエリパラメータから判断する。
以上で準備完了です。
簡単なGraphQL APIを作ってテストしてみましょう。
i18n.translateの引数には、${jsonファイル名}.${キー名}
という形式の値を渡します。
@Query(() => String)
async test(@I18n() i18n: I18nContext) {
return i18n.translate('common.株式会社 トラストハブ');
}
- URLに
http://localhost:3030/graphql
を指定した場合
- URLに
http://localhost:3030/graphql?lang=en
を指定した場合
変数を渡して翻訳内容を動的に設定したい場合は以下のようにします。
@Query(() => String)
async test2(
@Args('name') name: string,
@I18n() i18n: I18nContext
) {
return i18n.translate('common.こんにちは、name様');
}
- URLに
http://localhost:3030/graphql
を指定した場合
- URLに
http://localhost:3030/graphql?lang=en
を指定した場合
メール通知の多言語化対応
先ほど紹介した例は、ユーザーから送られてくるリクエスト情報をもとに言語を出し分けるというものでした。しかし、例えば、通販サービスにおいて、「スタッフが発送処理を完了した際に、ユーザーに対して通知メールを送る」というような場面においては、別のアプローチが必要になります。というのも、ユーザーが現在アクティブにサイトを閲覧しているわけではないため、リクエスト情報から言語を判断することができないからです。
以下は、I18nServiceをEmailServiceに導入した例です。ユーザーの住所情報をもとに使用する言語を決定できるようにしています。
import { I18nService } from 'nestjs-i18n';
@Injectable()
export class EmailService {
// I18nServiceをセットする
constructor(private readonly i18n: I18nService) {}
async sendEmailForShipping(
email: string,
userShippingAddress: string,
) {
const lang = this.convertCountryToLang(userShippingAddress.country);
const title = this.i18n.t(
'common.商品が発送されました',
{ lang }, // 言語を指定する
);
await this.sendEmail(email, title);
}
// 国情報を引数に取り、言語コードを返す関数
convertCountryToLang(country: string): string {
switch (country) {
case 'Japan':
return 'ja';
case 'China':
return 'zh-CN';
case 'Taiwan':
return 'zh-TW';
// 他の国はデフォルトで英語にする
default:
return 'en';
}
}
まとめ
nestjs-i18nは非常に導入が簡単で、アプリケーションコード内での呼び出しも行いやすいです。バックエンドにNestJSを使っている方には非常におすすめなパッケージです。
株式会社トラストハブでは、多くのユーザーにとって使いやすいサービスになるように日々新たな試みを行なっています。一緒にtoC向けプロダクトを開発したいという方はぜひこちらからお話しさせてください!
Discussion