class ValidationException extends I18nSupportException {

    private static final boolean propertyI18nEnabled = Boolean.getBoolean("i18n.validation.property.enabled");

    private List<Detail> details;

    public ValidationException(String message) {
        super(message);
    }

    public ValidationException(String property, String message, Object... args) {
        this(message, Collections.singletonList(new Detail(property, message, null)), args);
    }

    public ValidationException(String message, List<Detail> details, Object... args) {
        super(message, args);
        this.details = details;
    }

    public ValidationException(Set<? extends ConstraintViolation<?>> violations) {
        ConstraintViolation<?> first = violations.iterator().next();
        if (Objects.equals(first.getMessageTemplate(), first.getMessage())) {
            //模版和消息相同,说明是自定义的message,而不是已经通过i18n获取的.
            setI18nCode(first.getMessage());
        } else {
            setI18nCode("validation.property_validate_failed");
        }
        String property = first.getPropertyPath().toString();

        //{0} 属性 ，{1} 验证消息
        //property也支持国际化?
        String propertyI18n = propertyI18nEnabled ?
                first.getRootBeanClass().getName() + "." + property
                : property;

        setArgs(new Object[]{propertyI18n, first.getMessage()});

        details = new ArrayList<>(violations.size());
        for (ConstraintViolation<?> violation : violations) {
            details.add(new Detail(violation.getPropertyPath().toString(),
                                   violation.getMessage(),
                                   null));
        }
    }

    public List<Detail> getDetails(Locale locale) {
        return CollectionUtils.isEmpty(details)
                ? Collections.emptyList()
                : details
                .stream()
                .map(detail -> detail.translateI18n(locale))
                .collect(Collectors.toList());
    }

    @Override
    public String getLocalizedMessage(Locale locale) {<FILL_FUNCTION_BODY>}

    @Getter
    @Setter
    @AllArgsConstructor
    public static class Detail {

        @Schema(description = "字段")
        String property;

        @Schema(description = "说明")
        String message;

        @Schema(description = "详情")
        Object detail;

        public Detail translateI18n(Locale locale) {
            if (StringUtils.hasText(message) && message.contains(".")) {
                return new Detail(property, LocaleUtils.resolveMessage(message, locale, message), detail);
            }
            return this;
        }
    }
}

        if (propertyI18nEnabled && "validation.property_validate_failed".equals(getI18nCode()) && getArgs().length > 0) {
            Object[] args = getArgs().clone();
            args[0] = LocaleUtils.resolveMessage(String.valueOf(args[0]), locale, String.valueOf(args[0]));
            return LocaleUtils.resolveMessage(getI18nCode(), locale, getOriginalMessage(), args);
        }
        return super.getLocalizedMessage(locale);
    
<methods>public transient void <init>(java.lang.String, java.lang.Object[]) ,public transient void <init>(java.lang.String, java.lang.Throwable, java.lang.Object[]) ,public final java.lang.String getLocalizedMessage() ,public java.lang.String getLocalizedMessage(java.util.Locale) ,public final Mono<java.lang.String> getLocalizedMessageReactive() ,public java.lang.String getMessage() ,public java.lang.String getOriginalMessage() ,public static java.lang.String tryGetLocalizedMessage(java.lang.Throwable, java.util.Locale) ,public static java.lang.String tryGetLocalizedMessage(java.lang.Throwable) ,public static Mono<java.lang.String> tryGetLocalizedMessageReactive(java.lang.Throwable) <variables>private java.lang.Object[] args,private java.lang.String i18nCode