📑

【Go】go-playground/validator日本語化の方法

に公開

はじめに

Laravelではlocalejaに設定し、言語ファイルを作成することで簡単にバリデーションメッセージを日本語化することができます。
一方、Goではgo-playground/validatorを使うことで簡単にバリデーションを実行することができますが、日本語化するにはどうすれば良いかわかっていませんでした。
今回は日本語化する方法を調べ実装してみたので、備忘録的に書いていこうと思います。

環境

  • go version go1.24.0 darwin/arm64

前提

  • ユーザー情報を登録することを想定
  • キーバリュー形式でエラーメッセージを出力する

方法1:mapの活用

全体像は以下の通りです。

main.go
package main

import (
    "net/http"
    
    "github.com/go-playground/validator/v10"
    "github.com/labstack/echo/v4"
    "golang.org/x/crypto/bcrypt"
)

type RequestUser struct {
    Name            string `json:"name" validate:"required,max=255"`
    Email           string `json:"email" validate:"required,email"`
    Password        string `json:"password" validate:"required,max=40"`
    ConfirmPassword string `json:"confirm_password" validate:"required,max=40,eqfield=Password"`
    }
    
type User struct {
    Name     string `json:"name" validate:"required,max=255"`
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,max=40"`
}

// DB接続に関する処理は省略
func main() {
    e := echo.New()
    
    e.POST("/create", func(c echo.Context) error {
        req := RequestUser{}
        if err := c.Bind(&req); err != nil {
            return c.JSON(http.StatusBadRequest, err)
        }
        validation := validator.New()
        if err := validation.Struct(req); err != nil {
            errors := map[string]string{}
            for _, err := range err.(validator.ValidationErrors) {
                errors[err.Field()] = err.Tag()
            }
            return c.JSON(http.StatusBadRequest, errors)
        }
    // 登録処理は省略
    })
    e.Logger.Fatal(e.Start(":8080"))
}

ここでエラーが発生すると、以下のようにエラーメッセージが出力されます。

{
    "ConfirmPassword": "eqfield",
    "Name": "required"
}

ここでは「構造体のフィールド」をキーに、「validationタグに付与されたルール名」がバリューになっています。
これらを、それぞれmapを使い、日本語化していこうと思います。

フィールドとメッセージのmap

main.go
// フィールド名用マップ
var fieldNameMap = map[string]string{
    "Name":            "お名前",
    "Email":           "メールアドレス",
    "Password":        "パスワード",
    "ConfirmPassword": "確認用パスワード",
}
// エラーメッセージ用マップ
var errorMessages = map[string]string{
    "required": "この項目は必須です。",
    "email":    "有効なメールアドレスを入力してください。",
    "eqfield":  "パスワードが一致しません。",
}

ここから、フィールドとメッセージそれぞれのmapのキーを紐づくバリューを取り出します。

main.go
fieldName := fieldNameMap[err.Field()]
msg := errorMessages[err.Tag()]
errors[fieldName] = msg

ここでエラーが発生すると、以下のようにエラーメッセージが出力されます。

{
    "お名前": "この項目は必須です。",
    "確認用パスワード": "パスワードが一致しません。"
}

無事に日本語化することができました。

メリット・デメリット

  • メリット
    • カスタマイズしやすい
    • 追加のライブラリが不要
  • デメリット
    • フィールドが増えるたびに、日本語のマッピングを手動で追加する必要がある
    • 同じフィールド名の置換処理を複数の場所で書くと、管理が面倒になる

方法2:go-playgroundの翻訳機能活用

go-playgroundには、翻訳機能を提供するライブラリが存在します。
それらを使い、例えば以下のように実装することができます。(一部抜粋)

main.go
e.POST("/create", func(c echo.Context) error {
    req := RequestUser{}
    if err := c.Bind(&req); err != nil {
        return c.JSON(http.StatusBadRequest, err)
    }

    validation := validator.New()
    japanese := ja.New()
    uni := ut.New(japanese, japanese)
    trans, _ := uni.GetTranslator("ja")

    validation.RegisterTranslation("required", trans, func(ut ut.Translator) error {
        return ut.Add("required", "{0}は必須項目です。", true)
    }, func(ut ut.Translator, fe validator.FieldError) string {
        field := fieldNameMap[fe.Field()]
        t, _ := ut.T("required", field)
        return t
    })

    validation.RegisterTranslation("email", trans, func(ut ut.Translator) error {
        return ut.Add("email", "{0}は有効なメールアドレスを入力してください。", true)
    }, func(ut ut.Translator, fe validator.FieldError) string {
        field := fieldNameMap[fe.Field()]
        t, _ := ut.T("email", field)
        return t
    })

    validation.RegisterTranslation("max", trans, func(ut ut.Translator) error {
        return ut.Add("max", "{0}は最大{1}文字までです。", true)
    }, func(ut ut.Translator, fe validator.FieldError) string {
        field := fieldNameMap[fe.Field()]
        t, _ := ut.T("max", field, fe.Param())
        return t
    })

    validation.RegisterTranslation("eqfield", trans, func(ut ut.Translator) error {
        return ut.Add("eqfield", "{0}は{1}と一致しなければなりません。", true)
    }, func(ut ut.Translator, fe validator.FieldError) string {
        field := fieldNameMap[fe.Field()]
        t, _ := ut.T("eqfield", field, fe.Param())
        return t
    })

    if err := validation.Struct(req); err != nil {
        errors := map[string]string{}
        for _, err := range err.(validator.ValidationErrors) {
            errors[fieldNameMap[err.Field()]] = err.Translate(trans)
        }
        return c.JSON(http.StatusBadRequest, errors)
    }
    // 登録処理は省略
})

処理解説

  • 日本語ロケールを設定
  • 翻訳管理オブジェクトを作成
  • 日本語翻訳オブジェクトを作成
    • trans, _ := uni.GetTranslator("ja")
    • バリデーションエラーの翻訳に使う
  • カスタムバリデーションの設定
    • 日本語のカスタムバリデーションメッセージを登録
      • validation.RegisterTranslation("バリデーションルール", 翻訳オブジェクト, 登録処理, 翻訳処理)
    • 登録処理
      • ut.Add("ルール名", "メッセージテンプレート", 上書きフラグ)
      • {0}{1}はプレースホルダー
      • 上書きフラグをtrueにすることで、上書きをすることができる
    • 翻訳処理
      • ut.T("ルール名", パラメータ)
      • 先ほどのプレースホルダーにパラメータ(複数設定可)を設定
      • 今回はフィールド名をmapから取得するよう設定
main.go
validation.RegisterTranslation("required", trans, func(ut ut.Translator) error {
    return ut.Add("required", "{0}は必須項目です。", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
    field := fieldNameMap[fe.Field()]
    t, _ := ut.T("required", field)
    return t
})

validation.RegisterTranslation("email", trans, func(ut ut.Translator) error {
    return ut.Add("email", "{0}は有効なメールアドレスを入力してください。", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
    field := fieldNameMap[fe.Field()]
    t, _ := ut.T("email", field)
    return t
})

validation.RegisterTranslation("max", trans, func(ut ut.Translator) error {
    return ut.Add("max", "{0}は最大{1}文字までです。", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
    field := fieldNameMap[fe.Field()]
    t, _ := ut.T("max", field, fe.Param())
    return t
})

validation.RegisterTranslation("eqfield", trans, func(ut ut.Translator) error {
    return ut.Add("eqfield", "{0}は{1}と一致しなければなりません。", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
    firstParam := fieldNameMap[fe.Field()]
    secondParam := fieldNameMap[fe.Param()]
    t, _ := ut.T("eqfield", firstParam, secondParam)
    return t
})
  • エラーメッセージの出力
    • Translateメソッドを使うことで、翻訳されたエラーメッセージを出力するよう設定することができる
    • 翻訳するための言語とそのメッセージを管理している
main.go
if err := validation.Struct(req); err != nil {
    errors := map[string]string{}
    for _, err := range err.(validator.ValidationErrors) {
        errors[fieldNameMap[err.Field()]] = err.Translate(trans)
    }
    return c.JSON(http.StatusBadRequest, errors)
}

ここでエラーが発生すると、以下のようにエラーメッセージが出力されます。

{
    "お名前": "お名前は必須項目です。",
    "確認用パスワード": "確認用パスワードはパスワードと一致しなければなりません。"
}

無事に日本語化することができました。

メリット・デメリット

  • メリット
    • カスタマイズしやすい
    • 国際化対応がしやすい
  • デメリット
    • メンテナンスに手間がかかる
    • 翻訳処理をしているのでパフォーマンスへの影響が考えられる

まとめ

今回はgo-playground/validator日本語化の方法を2つ紹介しました。
他にも翻訳できる方法がないか、調べてみたいと思います!

参考

Discussion