🌍

NestJS で Swagger Spec の言語を切り替えて出力する

2023/12/10に公開

こんにちは!アルダグラムでエンジニアをしているヤスです。

本記事は株式会社アルダグラム Advent Calendar 2023 10日目の記事です。

KANNA では、データ連携のために OpenAPI を公開し、契約していただいた会社様のリソースと連携できる環境を整えています。
また、昨年よりグローバルにも展開し、さまざまな国の方に使っていただけている状況でして、データ連携を求められるお客様も存在します。

日本でのお客様への仕様の共有は Swagger Spec を用いているので、Spec ファイルの多言語化を行い海外のお客様へ提供することにしました。

Swagger Spec を多言語化するにあたって、アプリケーション上で拡張した点について、お話しできればと思います。

言語の設定方法

ご存知の通り、Swagger Spec 内の記述では多言語の切り替えに対応しておりません。

それ故に、Swagger の生成時に言語の切り替えを必要とします。

.env 内に、SERVER_LANG というパラメータを用意することで、言語の切り替えのキーとすることにしました。

実装方法

Swagger Spec の記述を行う際に、ApiProperty や ApiOperation というデコレーターを利用することで、仕様書の記述定義を行います。

文言の管理上、同一箇所に定義されている方がわかりやすいと考えたので、 ApiProperty のデコレーターを拡張したものを用意することで、日本語と英語の切り替えを行えるようにしました。

ApiProperty を拡張したコードはこちらです。

import { applyDecorators } from '@nestjs/common'
import { ApiProperty, ApiPropertyOptions } from '@nestjs/swagger'

interface ApiPropertyTranslateOptions extends ApiPropertyOptions {
  descriptionTranslate?: Record<string, any>
  exampleTranslate?: Record<string, any>
}

export function ApiPropertyTranslate(
  apiPropertyTranslateOptions: ApiPropertyTranslateOptions
) {
  const lang = process.env.SERVER_LANG ?? ''

  if (
    apiPropertyTranslateOptions.descriptionTranslate &&
    apiPropertyTranslateOptions?.descriptionTranslate[lang]
  ) {
    apiPropertyTranslateOptions.description =
      apiPropertyTranslateOptions?.descriptionTranslate[lang]
    apiPropertyTranslateOptions.descriptionTranslate = undefined
  }

  if (
    apiPropertyTranslateOptions.exampleTranslate &&
    apiPropertyTranslateOptions?.exampleTranslate[lang]
  ) {
    apiPropertyTranslateOptions.example =
      apiPropertyTranslateOptions?.exampleTranslate[lang]
    apiPropertyTranslateOptions.exampleTranslate = undefined
  }

  return applyDecorators(ApiProperty(apiPropertyTranslateOptions))
}

ApiPropertyOptions を拡張する形で、descriptionTranslate や exampleTranslate といった、API の仕様に関する文言を受け取れるようにしています。

Swagger を生成する際に通常使われる apiPropertyTranslateOptions の description, exemple に指定された言語の文言を指定することで、Swagger Spec の生成時に利用されるようにしています。

最後に、applyDecorators 上で定義することで、デコレーターとして扱えるようにしています。

拡張したデコレーターはこのように使います。

import { ApiPropertyTranslate } from '@core/decorators/api-property-translate'

export class CreateUserRequest {
  @ApiPropertyTranslate({
    descriptionTranslate: {
      ja: 'メールアドレス',
      en: 'Email Address'
    },
    example: 'yamadatarou_xxxx@xxxxx.xxxx',
    required: true
  })
  email!: string

  @ApiPropertyTranslate({
    descriptionTranslate: {
      ja: '氏名(姓)',
      en: 'Last Name'
    },
    exampleTranslate: {
      ja: '山田',
      en: 'Smith'
    },
    required: true
  })
  lastName!: string
}

新たな言語を追加する際は少し手間ですが、英語化できてればいいよねということでこのような構成です。

開発時には比較しながら定義できるので、気に入っています。

また、コントローラーに指定する ApiOperation も同様の方法で拡張しています。

import { applyDecorators } from '@nestjs/common'
import { ApiOperation, ApiOperationOptions } from '@nestjs/swagger'

interface ApiOperationTranslateOptions extends ApiOperationOptions {
  summaryTranslate?: Record<string, any>
}

export function ApiOperationTranslate(
  apiOperationTranslateOptions: ApiOperationTranslateOptions
) {
  const lang = process.env.SERVER_LANG ?? ''

  if (
    apiOperationTranslateOptions.summaryTranslate &&
    apiOperationTranslateOptions?.summaryTranslate[lang]
  ) {
    apiOperationTranslateOptions.summary =
      apiOperationTranslateOptions?.summaryTranslate[lang]
    apiOperationTranslateOptions.summaryTranslate = undefined
  }

  return applyDecorators(ApiOperation(apiOperationTranslateOptions))
}

開発していての所感

この構成にしたことは現在のところ、問題は出ておらず、後悔はしていません。

しかし、 i18next を代表する言語管理ライブラリ上で設計されているように、ファイルに書き出す形で言語の管理も考えられました。
言語化ツールとのインテグレーションができたかもしれません。

仕様書として、対応する言語が増えたら考えようかと思います。

終わりに

NestJS は言語自体の拡張性が高いところが気に入っています。

今回は静的なファイルの切り替えをメインにしており、そこまでリッチなことをしなくてもいいので、どちらかというと力技にしてしまった感が強いです。
どのようにすれば効率的に開発できるのかを考え、共通処理を実装するのは楽しいですね。

もっとアルダグラムエンジニア組織を知りたい人、ぜひ下記の情報をチェックしてみてください!

アルダグラム Tech Blog

Discussion