package validation import ( "errors" "fmt" "strings" "github.com/go-playground/locales/en" "github.com/go-playground/locales/id" ut "github.com/go-playground/universal-translator" v10 "github.com/go-playground/validator/v10" entranslations "github.com/go-playground/validator/v10/translations/en" idtranslations "github.com/go-playground/validator/v10/translations/id" ) // Constants for supported locales const ( LocaleID = "id" LocaleEN = "en" ) // ErrorMessage represents a validation error message type ErrorMessage struct { Field string `json:"field"` Message string `json:"-"` } type validator struct { validate *v10.Validate translator ut.Translator } // validatorInstance adalah instance global dari validator. var validatorInstance *validator // New creates a new validation instance with the specified locale // dan menginisialisasi instance global validatorInstance. func New(locale string) error { v := &validator{} parsedLocale := parseLocale(locale) uni := ut.New(en.New(), id.New(), en.New()) translator, found := uni.GetTranslator(parsedLocale) if !found { return fmt.Errorf("translator not found for locale: %s", parsedLocale) } validate := v10.New() if err := setupValidations(validate); err != nil { return fmt.Errorf("failed to setup validations: %w", err) } if err := setupTranslations(validate, translator, parsedLocale); err != nil { return fmt.Errorf("failed to setup translations for locale %s: %w", parsedLocale, err) } v.validate = validate v.translator = translator validatorInstance = v // Inisialisasi instance global return nil } func parseLocale(locale string) string { switch strings.ToLower(locale) { case "id": return LocaleID case "en": return LocaleEN default: return LocaleID // Default to Indonesian } } // setupValidations configures custom validation rules. func setupValidations(validate *v10.Validate) error { rules := NewValidatorRules(&InMemoryOptionSource{}) if err := rules.RegisterAllCustomRules(validate); err != nil { return err } return nil } // setupTranslations configures translations for validation messages. func setupTranslations(validate *v10.Validate, translator ut.Translator, locale string) error { // Register default translations based on locale if err := registerDefaultTranslations(validate, translator, locale); err != nil { return fmt.Errorf("failed to register default translations for locale %s: %w", locale, err) } // Register custom password validation translation err := validate.RegisterTranslation("password", translator, func(ut ut.Translator) error { return ut.Add("password", "harus mengandung minimal 8 karakter, huruf besar, huruf kecil, dan angka.", true) }, func(ut ut.Translator, fe v10.FieldError) string { translated, err := ut.T(fe.Tag(), fe.Field()) if err != nil { return fe.Field() + " is invalid" } return translated }, ) if err != nil { return fmt.Errorf("failed to register password translation: %w", err) } return nil } // registerDefaultTranslations sets up default translations for the specified locale. func registerDefaultTranslations(validate *v10.Validate, translator ut.Translator, locale string) error { switch locale { case LocaleID: return idtranslations.RegisterDefaultTranslations(validate, translator) case LocaleEN: return entranslations.RegisterDefaultTranslations(validate, translator) default: // Fallback to English if the locale is not supported return entranslations.RegisterDefaultTranslations(validate, translator) } } // Validate validates a struct using the global validator instance // and returns a slice of ErrorMessage. func Validate(s any) []ErrorMessage { if validatorInstance == nil { return []ErrorMessage{{Field: "", Message: "Validator belum diinisialisasi. Panggil validation.New() terlebih dahulu."}} } err := validatorInstance.validate.Struct(s) if err != nil { return TranslateError(err) } return nil } // TranslateError takes a validation error and translates it using the global translator. func TranslateError(err error) []ErrorMessage { if validatorInstance == nil { return nil } var validationErrors v10.ValidationErrors if !errors.As(err, &validationErrors) { return nil } errorMessages := make([]ErrorMessage, 0, len(validationErrors)) for _, e := range validationErrors { fieldLabel := e.Field() msg, err := validatorInstance.translator.T(e.Tag(), fieldLabel) if err != nil { msg = fieldLabel + " is Invalid" } errorMessages = append(errorMessages, ErrorMessage{ Field: e.Tag(), Message: msg, }) } return errorMessages }