🥌

React 今更だけど改めて Validation ライブラリ yup の日本語化

2022/04/22に公開

React + Typescript + MUIv5 + React Hook Form + yup で開発環境を作成しています。
今回は yup の日本語化についての記事です。
yupのバージョン:0.32.11

yup の日本語化についてはいくつか記事がヒットしましたが、アプリケーションへの導入方法についての情報が見つからなかったので、改めて記事にしました。

yup の日本語化

ロケールが定義できる項目

yup の locale.d.ts ファイルを見てみると下記のような定義になっています。
mixed、string、number、date、boolean、object、array についてロケールが定義できます。

yup locale.d.ts
export interface LocaleObject {
    mixed?: MixedLocale;
    string?: StringLocale;
    number?: NumberLocale;
    date?: DateLocale;
    boolean?: BooleanLocale;
    object?: ObjectLocale;
    array?: ArrayLocale;
}

mixed、string、number、date、boolean、object、array のそれぞれのロケールを見てみます。
日本語ロケールは以下の項目に定義できることがわかります。

yup locale.d.ts
export interface MixedLocale {
    default?: Message;
    required?: Message;
    oneOf?: Message<{values: any;}>;
    notOneOf?: Message<{values: any;}>;
    notType?: Message;
    defined?: Message;
}
export interface StringLocale {
    length?: Message<{length: number;}>;
    min?: Message<{min: number;}>;
    max?: Message<{max: number;}>;
    matches?: Message<{regex: RegExp;}>;
    email?: Message<{regex: RegExp;}>;
    url?: Message<{regex: RegExp;}>;
    uuid?: Message<{regex: RegExp;}>;
    trim?: Message;
    lowercase?: Message;
    uppercase?: Message;
}
export interface NumberLocale {
    min?: Message<{min: number;}>;
    max?: Message<{max: number;}>;
    lessThan?: Message<{less: number;}>;
    moreThan?: Message<{more: number;}>;
    positive?: Message<{more: number;}>;
    negative?: Message<{less: number;}>;
    integer?: Message;
}
export interface DateLocale {
    min?: Message<{min: Date | string;}>;
    max?: Message<{max: Date | string;}>;
}
export interface BooleanLocale {
    isValue?: Message;
}
export interface ObjectLocale {
    noUnknown?: Message;
},
export interface ArrayLocale {
    length?: Message<{length: number;}>;
    min?: Message<{min: number;}>;
    max?: Message<{max: number;}>;
}

日本語を定義する

上記で確認した 「ロケールが定義できる項目」 を参考に日本語を定義していきます。

日本語の定義の仕方

たとえば上記 locale.d.ts ファイル MixedLocale の required プロパティを確認します。

locale.d.ts MixedLocaleの一部抜粋
export interface MixedLocale {
  required: Message;
}

この場合の日本語定義は以下のようになります。

required: (prm: MessageParams) => {`${prm.label}は必須入力です`} 

MessageParams には label 以外にも以下のようなものが定義されています。

types.d.ts
export interface MessageParams {
    path: string; // 変数名
    value: any;
    originalValue: any;
    label: string;
    type: string;
}

たとえば検証スキーマを下記のように書いたとします。
MessageParams の pathは「name」に、label は「名称」に、value には入力値がセットされます。

export const createSchema = yup.object({
  name: yup.string().label('名称').required(),
})

次に locale.d.ts ファイル NumberLocale の min プロパティを確認します。
Message のジェネリクスで、{min: number;}が追加定義されています。

locale.d.ts NumberLocaleの一部抜粋
export interface NumberLocale {
  min?: Message<{min: number;}>;
}

この場合の日本語定義を MessageParams & {min: number}とすることで、追加の値が min プロパティにセットされます。

required: (prm: MessageParams & {min: number}) => {`${prm.label}${prm.min}以上で入力してください`} 

検証スキーマを下記のようにすると、prm.min の値は「18」なります。

export const createSchema = yup.object({
  age: yup.string().label('年齢').min(18),
})

日本語の定義ファイルを作成する

日本語の定義の仕方がわかったので、日本語定義ファイルを作成しsetLocaleで yup にセットします。
ファイル名はなんでもいいですが、yupJpConfig.ts というファイル名で作成しました。
label はスキーマで定義されるかわからないので、定義された場合だけ出力するようにしています。

2022/05/12 サンプル変更
ファイル名は何でもいいですが、yup.locale.ts というファイル名で作成しました。
「export default yup」しておき、yupを使用する個所ではimport yup from 'yup.locale.ts 'とこのファイル経由でyupを使用します。

yup.locale.ts
import * as yup from 'yup'
import { MessageParams } from "yup/lib/types"

const labelText = (prm: MessageParams) => {
  return prm.label ? `${prm.label}`: ''
}

const jpConfig = {
  mixed: {
    default: (prm: MessageParams) => `${labelText(prm)}無効です`,
    required: (prm: MessageParams) => `${labelText(prm)}必須の入力項目です`,
    oneOf: (prm: MessageParams & {values: any}) => `${labelText(prm)}次の値のいずれかでなければなりません:${prm.values}`,
    notOneOf: (prm: MessageParams & {values: any}) => `${labelText(prm)}次の値のいずれかであってはなりません:${prm.values}`,
    notType: `形式が違います`,
    defined: ``
  },
  string: {
    length: (prm: MessageParams & {length: number}) => `${labelText(prm)}${prm.length}文字でなければなりません`,
    min: (prm: MessageParams & {min: number}) => `${labelText(prm)}少なくとも${prm.min}文字でなければなりません`,
    max: (prm: MessageParams & {max: number}) => `${labelText(prm)}最大${prm.max}文字でなければなりません`,
    matches: (prm: MessageParams & {regex: RegExp}) => `${labelText(prm)}次の形式と一致する必要があります: "${prm.regex}"`,
    email: (prm: MessageParams & {regex: RegExp}) => `${labelText(prm)}メールアドレス形式である必要があります`,
    url: (prm: MessageParams & {regex: RegExp}) => `${labelText(prm)}有効なURLでなければなりません`,
    uuid: (prm: MessageParams & {regex: RegExp}) => `${labelText(prm)}有効なUUIDでなければなりません`,
    trim: (prm: MessageParams) => `${labelText(prm)}前後にスペースを入れてはいけません`,
    lowercase: (prm: MessageParams) => `${labelText(prm)}小文字でなければなりません`,
    uppercase: (prm: MessageParams) => `${labelText(prm)}大文字でなければなりません`,
  },
  number: {
    min: (prm: MessageParams & {min: number}) => `${labelText(prm)}${prm.min}以上である必要があります`,
    max: (prm: MessageParams & {max: number}) => `${labelText(prm)}${prm.max}以下でなければなりません`,
    lessThan: (prm: MessageParams & {less: number}) => `${labelText(prm)}${prm.less}より小さくなければなりません`,
    moreThan: (prm: MessageParams & {more: number}) => `${labelText(prm)}${prm.more}より大きくなければなりません`,
    positive: (prm: MessageParams & {more: number}) => `${labelText(prm)}正の数でなければなりません`,
    negative: (prm: MessageParams & {less: number}) => `${labelText(prm)}負の数でなければなりません`,
    integer: (prm: MessageParams) => `${labelText(prm)}整数でなければなりません`,
  },
  date: {
    min: (prm: MessageParams & {min: Date | string}) =>  `${labelText(prm)}${prm.min}より後でなければなりません`,
    max: (prm: MessageParams & {max: Date | string}) =>  `${labelText(prm)}${prm.max}より前でなければなりません`,
  },
  boolean: {
    isValue: (prm: MessageParams) =>  `${labelText(prm)}値が必要です`,
  },
  object: {
    noUnknown: (prm: MessageParams) => `${labelText(prm)}オブジェクトシェイプで指定されていないキーを含めることはできません`,
  },
  array: {
    length: (prm: MessageParams & {length: number}) => `${labelText(prm)}${prm.length}個が必要です`,
    min: (prm: MessageParams & {min: number}) => `${labelText(prm)}${prm.min}個以上の項目が必要です`,
    max: (prm: MessageParams & {max: number}) => `${labelText(prm)}${prm.max}個以下の項目が必要です`,
  },
}

yup.setLocale(jpConfig)
export default yup

アプリケーションに導入する

アプリケーションのルートコンポーネントなどで、作成した日本語定義ファイルを import します。
※ココがわからず少し手間取ってしまいました😫
他の方法(日本語定義ファイル自体を default export して使う)もありますが、アプリケーションルートで日本語定義ファイルを読み込んでしまう方が、簡単な上 import 先をうっかり間違えてしまうということもありません。

他のコンポーネントなどで yup を使用する時は import yup from 'yup.locale.ts と先ほど作成した「yup.locale.ts」ファイル経由でyupを使用します。

Sample.tsx
import yup from 'yup.locale.ts

export const createSchema = yup.object({
  name: yup.string().lable('名称').required()
})

以上、Validation ライブラリ yup の日本語化 でした。

Discussion