Conversation
43946c0 to
09c71a6
Compare
3831682 to
2c49cc6
Compare
09c71a6 to
3ed608d
Compare
6b1dec9 to
bc17dee
Compare
There was a problem hiding this comment.
LGTM.
Seems like vite does not support dynamic imports with variable strings, so we can't natively lazy-load locales. However, I tried this plugin https://www.npmjs.com/package/vite-plugin-dynamic-import which allows dynamically importing basically anything, and got it to work using the example provided by the zod docs https://zod.dev/error-customization?id=internationalization
// vite.config.ts
import dynamicImport from 'vite-plugin-dynamic-import'
export default {
plugins: [
dynamicImport(/* options */)
]
}
// lib/i18n.ts
import { z } from "zod";
const loadLocale = async (locale: string) => {
const { default: locale } = await import(`zod/v4/locales/${locale}.js`);
z.config(locale());
};
// Usage
await loadLocale("fr");4dd604a to
d470136
Compare
bc17dee to
5bbd945
Compare
|
@maanlamp that certainly is an option, good find. But some possible problems:
I can achieve the same result without the plugin by adding a switch (the plugin does the same internally). I had this before but I didn't notice I had to re-trigger the validation. What do you think about that? const loadZodLocale = async (locale: string) => {
let localeImport: any;
switch (lng) {
case "en-US":
localeImport = await import("zod/v4/locales/en.js");
break;
case "nl-NL":
localeImport = await import("zod/v4/locales/nl.js");
break;
}
if (localeImport?.default) {
z.config(localeImport.default());
}
}; |
Yeah if we don't integrate zod into the react lifecycle it wont update after changing locales. Might be worth looking into before merging. Perhaps only ever update it when also changing
I'm quite stringent about this; It's become a bit of an in-joke to call this Wouter's law: any software project will need i18n at some point. There's no point in not translating from the start, IMO. It's not a lot of work and it saves time when every app inevitably needs to be translated down the road. I'm not in favour of leaving this up to the whims of those working on the project.
Sadly the native LocaleMatcher proposal has still not landed, but formatjs provides a ponyfill: https://www.npmjs.com/package/@formatjs/intl-localematcher We can read all translation files' names and provide that as the list of supported locales. That way, we can pick the best match for any given language tag: import "@formatjs/intl-localematcher";
const supportedZodLocales = Object.fromEntries(
Object.entries(
import.meta.glob(
"../../../node_modules/zod/v4/locales/[!index]*.js",
{
import: "default",
},
),
).map(([key, mod]) => [
key.match(/\/([^/]+)\.js$/)?.[1],
mod,
]),
);
const defaultLocale = "nl";
const loadZodLocale = async (locale: string) => {
const locale = Intl.LocaleMatcher.match(
[locale],
Object.keys(supportedZodLocales),
defaultLocale,
{ algorithm: "best fit" }
);
const config = await supportedZodLocales[locale]();
z.config(config);
};If required, we can do this at build time to keep the polyfill from increasing our bundle size. Maybe we can even integrate this with our general i18n locale solution.
If we need it for our code to work (ergonomically), I don't see it as "code that we'll never use". I do think we can do without the plugin, as it is possible to glob
I'm more in favour of a plugin or the aforementioned glob solution. That way we automatically support what we need. No manual updating/referencing. I'm more than willing to pay for that with a bigger bundle. |
|
What you propose is cool. But I kind of get this idea 😆: Of all our projects that are multi-lingual, I think NONE have more than 2 languages to support (nl & en). If you really feel strongly about this, we can build it in. But just know my preference. |
|
The part I am missing in your approach is error / unhappy path handling. The code you propose has no handling for locale typos / unsupported locales, and silently fails. Assuming that is not intentional, we could fix this by constraining the locale argument type from In short: const loadZodLocale = async (locale: "en-US" | "nl-NL") => {
let localeImport: { default(): Partial<$ZodConfig> };
switch (locale) {
case "en-US":
localeImport = await import("zod/v4/locales/en.js");
break;
default:
localeImport = await import("zod/v4/locales/nl.js");
break;
}
z.config(localeImport.default());
};Anyway, I get the message; I also prefer practical solutions. In this case, I don't fully agree — 10 extra lines to support all locales automatically, with graceful fallback, is not an apache — but we don't have to agree. At least we are implementing translations by default, which I do feel strongly about. |

To test, add some validation to a component (eg. Home) and change language with the dropdown. Example: