how to inline translate in Zod schema?
Closed this issue · 3 comments
hafeyang commented
I wonder how to useTranslate
out of component$
/* eslint-disable @typescript-eslint/no-unused-vars */
import { $, component$ } from '@builder.io/qwik';
import { routeLoader$, z } from '@builder.io/qwik-city';
import type { InitialValues, SubmitHandler } from '@modular-forms/qwik';
import { formAction$, useForm, zodForm$ } from '@modular-forms/qwik';
const loginSchema = z.object({
email: z
.string()
.min(1, 'Please enter your email.') // =====> how to get localized message out of component ?
.email('The email address is badly formatted.'),
password: z
.string()
.min(1, 'Please enter your password.')
.min(8, 'You password must have 8 characters or more.'),
});
type LoginForm = z.infer<typeof loginSchema>;
export const useFormLoader = routeLoader$<InitialValues<LoginForm>>(() => ({
email: '',
password: '',
}));
// loginSchema can not move into component$ because useFormLoader refers loginSchema and should exported see https://qwik.builder.io/docs/route-loader/
export default component$(() => {
const [loginForm, { Form, Field }] = useForm<LoginForm>({
validate: zodForm$(loginSchema),
});
return <Form></Form>;
});
inlineTranslate
function needs to get locale context , also not work out of component
robisim74 commented
Hi @hafeyang ,
dynamic values in Zod schema have some known issues: fabian-hiller/modular-forms#38
So a valid TypeScript solution like this will not work:
// Transform in function
const loginSchema = (ctx: SpeakState) => z.object({
email: z
.string()
.min(1, inlineTranslate('form.email@@Please enter your email', ctx))
.email(inlineTranslate('form.emailFormatted@@The email address is badly formatted', ctx))
});
// Use ReturnType
type LoginForm = z.infer<ReturnType<typeof loginSchema>>;
export const useFormLoader = routeLoader$<InitialValues<LoginForm>>(() => ({
email: '',
password: '',
}));
export default component$(() => {
const ctx = useSpeakContext();
const [loginForm, { Form, Field }] = useForm<LoginForm>({
validate: zodForm$(() => loginSchema(ctx)),
loader: useFormLoader()
});
return <Form>
<Field name="email">
{(field, props) => (
<div>
<input {...props} type="email" value={field.value} />
{field.error && <div>{field.error}</div>}
</div>
)}
</Field>
<button type="submit">Login</button>
</Form>;
});
I think the best and clearest solution is for the schema to do the schema, and translate into the html:
Solution A
// Use only the keys in the schema
const loginSchema = z.object({
email: z
.string()
.min(1, 'form.email@@Please enter your email')
.email('form.emailFormatted@@The email address is badly formatted')
});
type LoginForm = z.infer<typeof loginSchema>;
export const useFormLoader = routeLoader$<InitialValues<LoginForm>>(() => ({
email: '',
password: '',
}));
export default component$(() => {
const t = useTranslate();
const [loginForm, { Form, Field }] = useForm<LoginForm>({
validate: zodForm$(loginSchema),
loader: useFormLoader()
});
return <Form>
<Field name="email">
{(field, props) => (
<div>
<input {...props} type="email" value={field.value} />
{field.error && <div>{t(field.error)}</div>} {/* Translate where the translation is displayed */}
</div>
)}
</Field>
<button type="submit">Login</button>
</Form>;
});
In this case you have to assign
form
asset toruntimeAssets
: it will not be inlined, but requested only when needed.
Alternatively, to inline the translation, you'd have to drop the schema and put the validation inside the html:
Solution B
type LoginForm = {
email: string;
password: string;
};
export const useFormLoader = routeLoader$<InitialValues<LoginForm>>(() => ({
email: '',
password: '',
}));
export default component$(() => {
const t = useTranslate();
const [loginForm, { Form, Field }] = useForm<LoginForm>({
loader: useFormLoader()
});
return <Form>
<Field name="email"
validate={[
required(t('form.email@@Please enter your email')),
email(t('form.emailFormatted@@The email address is badly formatted')),
]}>
{(field, props) => (
<div>
<input {...props} type="email" value={field.value} />
{field.error && <div>{field.error}</div>}
</div>
)}
</Field>
<button type="submit">Login</button>
</Form>;
});
hafeyang commented
thanks a lot!