From 08ccced73463a4065156bf77ddd69147f2b7d519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ars=C3=A8ne=20Reymond?= <66876397+p0lycarpio@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:56:11 +0100 Subject: [PATCH] fix: localize text validators message (#6719) --- frontend/lang/messages/en-US.json | 8 ++++++ frontend/lib/validators/inputs.test.ts | 37 +++++++++++++++++++++----- frontend/lib/validators/inputs.ts | 20 +++++++++----- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/frontend/lang/messages/en-US.json b/frontend/lang/messages/en-US.json index bbf6ac50e..3cae69e4c 100644 --- a/frontend/lang/messages/en-US.json +++ b/frontend/lang/messages/en-US.json @@ -1428,5 +1428,13 @@ "is-like": "is like", "is-not-like": "is not like" } + }, + "validators": { + "required": "This Field is Required", + "invalid-email": "Email Must Be Valid", + "invalid-url": "Must Be A Valid URL", + "no-whitespace": "No Whitespace Allowed", + "min-length": "Must Be At Least {min} Characters", + "max-length": "Must Be At Most {max} Characters" } } diff --git a/frontend/lib/validators/inputs.test.ts b/frontend/lib/validators/inputs.test.ts index 6b8ff2119..3ddd0be3d 100644 --- a/frontend/lib/validators/inputs.test.ts +++ b/frontend/lib/validators/inputs.test.ts @@ -1,10 +1,33 @@ -import { expect, test } from "vitest"; +import { expect, test, vi } from "vitest"; +import enUS from "~/lang/messages/en-US.json"; + import { required, email, whitespace, url, minLength, maxLength } from "./inputs"; +vi.mock("~/composables/use-global-i18n", () => { + const interpolate = (msg: string, params?: Record) => { + if (!params) return msg; + return msg + .replace("{min}", String(params.min ?? "")) + .replace("{max}", String(params.max ?? "")); + }; + + const t = (key: string, params?: Record) => { + const parts = key.split("."); + let acc: any = enUS as any; + for (const p of parts) acc = acc?.[p]; + const msg: string | undefined = acc; + return interpolate(msg ?? key, params); + }; + + return { useGlobalI18n: () => ({ t }) }; +}); + export { scorePassword } from "./password"; +// Tests + test("validator required", () => { - const falsey = "This Field is Required"; + const falsey = enUS.validators.required; expect(required("123")).toBe(true); expect(required("")).toBe(falsey); expect(required(undefined)).toBe(falsey); @@ -14,7 +37,7 @@ test("validator required", () => { const nulls = [undefined, null]; test("validator email", () => { - const falsey = "Email Must Be Valid"; + const falsey = enUS.validators["invalid-email"]; expect(email("123")).toBe(falsey); expect(email("email@example.com")).toBe(true); @@ -24,7 +47,7 @@ test("validator email", () => { }); test("whitespace", () => { - const falsey = "No Whitespace Allowed"; + const falsey = enUS.validators["no-whitespace"]; expect(whitespace("123")).toBe(true); expect(whitespace(" ")).toBe(falsey); expect(whitespace("123 123")).toBe(falsey); @@ -35,7 +58,7 @@ test("whitespace", () => { }); test("url", () => { - const falsey = "Must Be A Valid URL"; + const falsey = enUS.validators["invalid-url"]; expect(url("https://example.com")).toBe(true); expect(url("")).toBe(falsey); @@ -46,7 +69,7 @@ test("url", () => { test("minLength", () => { const min = 3; - const falsey = `Must Be At Least ${min} Characters`; + const falsey = enUS.validators["min-length"].replace("{min}", String(min)); const fn = minLength(min); expect(fn("123")).toBe(true); expect(fn("12")).toBe(falsey); @@ -59,7 +82,7 @@ test("minLength", () => { test("maxLength", () => { const max = 3; - const falsey = `Must Be At Most ${max} Characters`; + const falsey = enUS.validators["max-length"].replace("{max}", String(max)); const fn = maxLength(max); expect(fn("123")).toBe(true); expect(fn("1234")).toBe(falsey); diff --git a/frontend/lib/validators/inputs.ts b/frontend/lib/validators/inputs.ts index cffb92b59..2304c67dc 100644 --- a/frontend/lib/validators/inputs.ts +++ b/frontend/lib/validators/inputs.ts @@ -1,22 +1,28 @@ +import { useGlobalI18n } from "~/composables/use-global-i18n"; + const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@(([[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; const URL_REGEX = /[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/; export function required(v: string | undefined | null) { - return !!v || "This Field is Required"; + const i18n = useGlobalI18n(); + return !!v || i18n.t("validators.required"); } export function email(v: string | undefined | null) { - return (!!v && EMAIL_REGEX.test(v)) || "Email Must Be Valid"; + const i18n = useGlobalI18n(); + return (!!v && EMAIL_REGEX.test(v)) || i18n.t("validators.invalid-email"); } export function whitespace(v: string | null | undefined) { - return (!!v && v.split(" ").length <= 1) || "No Whitespace Allowed"; + const i18n = useGlobalI18n(); + return (!!v && v.split(" ").length <= 1) || i18n.t("validators.no-whitespace"); } export function url(v: string | undefined | null) { - return (!!v && URL_REGEX.test(v)) || "Must Be A Valid URL"; + const i18n = useGlobalI18n(); + return (!!v && URL_REGEX.test(v)) || i18n.t("validators.invalid-url"); } export function urlOptional(v: string | undefined | null) { @@ -24,9 +30,11 @@ export function urlOptional(v: string | undefined | null) { } export function minLength(min: number) { - return (v: string | undefined | null) => (!!v && v.length >= min) || `Must Be At Least ${min} Characters`; + const i18n = useGlobalI18n(); + return (v: string | undefined | null) => (!!v && v.length >= min) || i18n.t("validators.min-length", { min }); } export function maxLength(max: number) { - return (v: string | undefined | null) => !v || v.length <= max || `Must Be At Most ${max} Characters`; + const i18n = useGlobalI18n(); + return (v: string | undefined | null) => !v || v.length <= max || i18n.t("validators.max-length", { max }); }