feat(07-07): localize lagoon validation with Laravel-shaped Polish messages
Replay of the user-api corpus needs lagoon::validate.* catalogs and underscore-to-space attribute names so Go 422 bodies match Winter. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -81,7 +81,9 @@ func validateField(ctx context.Context, tx *gorm.DB, model any, field, rule stri
|
||||
case "unique":
|
||||
uniqueTable = arg
|
||||
case "boolean":
|
||||
// Go's bool field type already enforces this; treat as a type-check no-op.
|
||||
if !isLaravelBoolean(val) {
|
||||
return []string{validateMessage(ctx, tr, "boolean", field, nil)}, nil
|
||||
}
|
||||
case "email":
|
||||
tags = append(tags, "email")
|
||||
case "confirmed":
|
||||
@@ -149,14 +151,43 @@ func validateField(ctx context.Context, tx *gorm.DB, model any, field, rule stri
|
||||
}
|
||||
tags = withoutTag(tags, "numeric")
|
||||
}
|
||||
if required && isEmptyValue(val) {
|
||||
return []string{validateMessage(ctx, tr, "required", field, nil)}, nil
|
||||
}
|
||||
usedBetween := betweenMin != "" && betweenMax != ""
|
||||
if len(tags) > 0 {
|
||||
tag := strings.Join(tags, ",")
|
||||
if err := validateOnce.Var(val, tag); err != nil {
|
||||
ruleName := "required"
|
||||
if !required {
|
||||
ruleName = firstNonOmit(tags)
|
||||
msgs := make([]string, 0, len(tags))
|
||||
for _, t := range tags {
|
||||
name, _, _ := strings.Cut(t, "=")
|
||||
if name == "required" || name == "min" || name == "max" {
|
||||
continue
|
||||
}
|
||||
return []string{validateMessage(ctx, tr, ruleName, field, nil)}, nil
|
||||
if err := validateOnce.Var(val, t); err != nil {
|
||||
ruleName := name
|
||||
if name == "oneof" {
|
||||
ruleName = "oneof"
|
||||
}
|
||||
msgs = append(msgs, validateMessage(ctx, tr, ruleName, field, map[string]string{
|
||||
"min": betweenMin, "max": betweenMax,
|
||||
}))
|
||||
}
|
||||
}
|
||||
if usedBetween && !numericRange {
|
||||
n := 0
|
||||
if s, ok := val.(string); ok {
|
||||
n = len(s)
|
||||
} else if !isEmptyValue(val) {
|
||||
n = len(fmt.Sprint(val))
|
||||
}
|
||||
minN, maxN := atoiOr(betweenMin, 0), atoiOr(betweenMax, 0)
|
||||
if n < minN || (maxN > 0 && n > maxN) {
|
||||
msgs = append(msgs, validateMessage(ctx, tr, "between", field, map[string]string{
|
||||
"min": betweenMin, "max": betweenMax,
|
||||
}))
|
||||
}
|
||||
}
|
||||
if len(msgs) > 0 {
|
||||
return msgs, nil
|
||||
}
|
||||
}
|
||||
if uniqueTable != "" {
|
||||
@@ -368,52 +399,79 @@ func withoutTag(tags []string, name string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
func firstNonOmit(tags []string) string {
|
||||
for _, t := range tags {
|
||||
name, _, _ := strings.Cut(t, "=")
|
||||
if name != "omitempty" && name != "required" {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return "invalid"
|
||||
}
|
||||
|
||||
func validateMessage(ctx context.Context, tr *phrasebook.Translator, rule, field string, params map[string]string) string {
|
||||
if params == nil {
|
||||
params = map[string]string{}
|
||||
}
|
||||
params["attribute"] = field
|
||||
key := "lagoon.validate." + rule
|
||||
params["attribute"] = laravelAttribute(field)
|
||||
key := "lagoon::validate." + rule
|
||||
if tr != nil {
|
||||
s := tr.Get(ctx, key, params)
|
||||
if s != "" && s != key {
|
||||
return s
|
||||
}
|
||||
}
|
||||
attr := params["attribute"]
|
||||
switch rule {
|
||||
case "required":
|
||||
return "The " + field + " field is required."
|
||||
return "The " + attr + " field is required."
|
||||
case "integer":
|
||||
return "The " + field + " must be an integer."
|
||||
return "The " + attr + " must be an integer."
|
||||
case "numeric":
|
||||
return "The " + field + " must be a number."
|
||||
return "The " + attr + " must be a number."
|
||||
case "unique":
|
||||
return "The " + field + " has already been taken."
|
||||
return "The " + attr + " has already been taken."
|
||||
case "max":
|
||||
return "The " + field + " may not be greater than " + params["max"] + "."
|
||||
return "The " + attr + " may not be greater than " + params["max"] + "."
|
||||
case "min":
|
||||
return "The " + field + " must be at least " + params["min"] + "."
|
||||
return "The " + attr + " must be at least " + params["min"] + "."
|
||||
case "oneof":
|
||||
return "The selected " + field + " is invalid."
|
||||
return "The selected " + attr + " is invalid."
|
||||
case "email":
|
||||
return "The " + field + " must be a valid email address."
|
||||
return "The " + attr + " must be a valid email address."
|
||||
case "confirmed":
|
||||
return "The " + field + " confirmation does not match."
|
||||
return "The " + attr + " confirmation does not match."
|
||||
case "different":
|
||||
return "The " + field + " and " + params["other"] + " must be different."
|
||||
return "The " + attr + " and " + params["other"] + " must be different."
|
||||
case "mimes":
|
||||
return "The " + field + " must be a file of the allowed types."
|
||||
return "The " + attr + " must be a file of the allowed types."
|
||||
case "between":
|
||||
return "The " + attr + " must be between " + params["min"] + " and " + params["max"] + " characters."
|
||||
case "boolean":
|
||||
return "The " + attr + " field must be true or false."
|
||||
default:
|
||||
return "The " + field + " is invalid."
|
||||
return "The " + attr + " is invalid."
|
||||
}
|
||||
}
|
||||
|
||||
func laravelAttribute(field string) string {
|
||||
return strings.ReplaceAll(field, "_", " ")
|
||||
}
|
||||
|
||||
func isLaravelBoolean(val any) bool {
|
||||
switch v := val.(type) {
|
||||
case bool:
|
||||
return true
|
||||
case string:
|
||||
switch strings.ToLower(strings.TrimSpace(v)) {
|
||||
case "0", "1", "true", "false":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case float64:
|
||||
return v == 0 || v == 1
|
||||
case int:
|
||||
return v == 0 || v == 1
|
||||
default:
|
||||
s := strings.TrimSpace(fmt.Sprint(val))
|
||||
return s == "0" || s == "1" || s == "true" || s == "false"
|
||||
}
|
||||
}
|
||||
|
||||
func atoiOr(s string, fallback int) int {
|
||||
n, err := strconv.Atoi(strings.TrimSpace(s))
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user