Skip to content

(i18n) Improve Japanese ja.ts error messages to be japanese friendly #1486

Description

@ysknsid25

This might be somewhat related to #1453 . However, I believe this issue is worth fixing even in the current i18 version, at least in Japanese.

Motivation

The current Japanese translation in ja.ts embeds issue.expected and issue.received directly into messages. These values use programmer-oriented symbols defined in Valibot's core (e.g. >=3, !0, !"banned"), which is consistent with how the English default messages work.

However, when Japanese messages are displayed, those symbols appear inside natural Japanese sentences and feel deeply unnatural to Japanese speakers.

Example: what users actually see

The >=3 notation is arguably acceptable in English because it reads naturally as a mathematical expression in that context. In Japanese, however, embedding symbols like >=3 or !0 inside a Japanese sentence creates a jarring, unnatural reading experience that most Japanese speakers would find confusing.

minBytes(3) with input "a"

無効なバイト数: >=3 を期待しましたが、 1 を受け取りました

nonEmpty() with empty string

無効な長さ: !0 を期待しましたが、 0 を受け取りました

notValue("banned") with "banned"

無効な値: !"banned" を期待しましたが、 "banned" を受け取りました

end-user exposure

One might argue that Valibot error messages are only read by developers. This is true in many cases. For example, when inspecting API error responses during development.

However, there are common real-world scenarios where these messages are exposed directly to end users.

Frontend validation

// Valibot is used to validate form input on the client side
const result = v.safeParse(schema, formData);
if (!result.success) {
  showToast(result.issues[0].message); // shown directly to the user
}

Backend validation with pass-through error responses

// Backend validates request body and returns issues as-is
const result = v.safeParse(schema, req.body);
if (!result.success) {
  return res.status(400).json({ errors: result.issues.map(i => i.message) });
}
// Frontend displays these messages directly in the UI

In both cases, a message like 無効な長さ: !0 を期待しましたが、 0 を受け取りました would be shown to a general user. The expression !0 is not common in Japan, so many end users will not understand its meaning.

While developers can always override messages with setSpecificMessage, requiring them to redefine every validator message just to make them human-readable is a significant burden. I believe the content should be understandable to anyone, whether they are a programmer or a non-programmer end-user.

Proposal

The direct translation of en.ts into ja.ts sounds very unnatural to Japanese speakers due to cultural and grammatical differences.

Therefore, instead of simply using a direct translation of en.ts for ja.ts, how about revising the message to something that both programmers and average Japanese speakers can understand?

The approach is simple: don't embed issue.expected directly into the Japanese text. Instead, use issue.requirement (the actual constraint value). This alone will make the Japanese text sound overwhelmingly more natural.

min / max / gt / lt series

// Before
minBytes: (issue) => `無効なバイト数: ${issue.expected} を期待しましたが、 ${issue.received} を受け取りました`,

// After
minBytes:    (issue) => `バイト数が不足しています: ${issue.requirement} バイト以上である必要がありますが、 ${issue.received} バイトを受け取りました`,
maxBytes:    (issue) => `バイト数が超過しています: ${issue.requirement} バイト以下である必要がありますが、 ${issue.received} バイトを受け取りました`,
minLength:   (issue) => `文字数が不足しています: ${issue.requirement} 文字以上である必要がありますが、 ${issue.received} 文字を受け取りました`,
maxLength:   (issue) => `文字数が超過しています: ${issue.requirement} 文字以下である必要がありますが、 ${issue.received} 文字を受け取りました`,
minValue:    (issue) => `値が小さすぎます: ${issue.requirement} 以上である必要がありますが、 ${issue.received} を受け取りました`,
maxValue:    (issue) => `値が大きすぎます: ${issue.requirement} 以下である必要がありますが、 ${issue.received} を受け取りました`,
gtValue:     (issue) => `値が小さすぎます: ${issue.requirement} より大きい値である必要がありますが、 ${issue.received} を受け取りました`,
ltValue:     (issue) => `値が大きすぎます: ${issue.requirement} より小さい値である必要がありますが、 ${issue.received} を受け取りました`,

nonEmpty / empty series

// Before
nonEmpty: (issue) => `無効な長さ: ${issue.expected} を期待しましたが、 ${issue.received} を受け取りました`,
// → "無効な長さ: !0 を期待しましたが、 0 を受け取りました"

// After...Very Simple!!
nonEmpty: (_issue) => `空にすることはできません`, 
empty:    (_issue) => `空である必要があります`,

not series

// Before
notBytes:     (issue) => `無効なバイト数: ${issue.expected} を期待しましたが、 ${issue.received} を受け取りました`,
// → "無効なバイト数: !5 を期待しましたが、 5 を受け取りました"

// After
notBytes:     (issue) => `無効なバイト数: ${issue.requirement} バイトは使用できません`,
notLength:    (issue) => `無効な文字数: ${issue.requirement} 文字は使用できません`,
notGraphemes: (issue) => `無効な書記素数: ${issue.requirement} は使用できません`,
notEntries:   (issue) => `無効な項目数: ${issue.requirement} 件は使用できません`,
notSize:      (issue) => `無効なサイズ: ${issue.requirement} は使用できません`,
notWords:     (issue) => `無効な単語数: ${issue.requirement} 単語は使用できません`,
notValue:     (issue) => `使用できない値です: ${issue.requirement} は入力できません`,
notValues:    (_issue) => `使用できない値が含まれています`,

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions