zod-i18n

repository·main·Indexed 21 days ago

https://github.com/aiji42/zod-i18n

A library for localizing Zod error messages using i18next. It provides the zodI18nMap and makeZodI18nMap functions to translate Zod issue codes into human-readable, localized strings. The library supports custom namespaces, pluralization, path translation for object schema keys via HandlePathOption, and custom i18n keys for .refine() and z.custom() validations. It is compatible with Next.js projects using next-i18next and form libraries like react-hook-form.

Tokens
3.6K
Snippets
11
Records
14
Agent score
74%

What's inside zod-i18n

  1. Handle object schema keys with handlePath

    main

    When validating objects (e.g., z.object), you can include the object key in the error message using the {{path}} placeholder.

    To enable this:

    1. Use a translation key suffixed with _with_path (e.g., invalid_type_with_path).
    2. If you want to map technical keys (like userName) to human-readable labels (like User's name), use the handlePath.keyPrefix option in makeZodI18nMap to point to a specific section in your translation files.
    i18next.init({
      lng: "en",
      resources: {
        en: {
          zod: {
            errors: {
              invalid_type: "Expected {{expected}}, received {{received}}",
              invalid_type_with_path: "{{path}} is expected {{expected}}, received {{received}}",
            },
          },
          form: {
            paths: {
              userName: "User's name",
            },
          },
        },
      },
    });
    
    z.setErrorMap(
      makeZodI18nMap({
        ns: ["zod", "form"],
        handlePath: {
          keyPrefix: "paths",
        },
      })
    );
    
    const schema = z.object({ userName: z.string() });
    schema.parse({ userName: 1 }); // => User's name is expected string, received number
  2. How namespaces (ns) work

    main

    Namespaces allow you to switch between different translation files. This is useful if you want different error messages for different contexts (e.g., user-facing form validation vs. developer-facing API validation). By default, the library looks in the zod namespace. You can provide an array of namespaces to makeZodI18nMap to search across multiple files.

    import i18next from "i18next";
    import { z } from "zod";
    import { makeZodI18nMap } from "zod-i18n-map";
    
    i18next.init({
      lng: "en",
      resources: {
        en: {
          zod: {
            invalid_type: "Error: expected {{expected}}, received {{received}}",
          },
          formValidation: {
            invalid_type: "it is expected to provide {{expected}} but you provided {{received}}",
          },
        },
      },
    });
    
    // use default namespace
    z.setErrorMap(makeZodI18nMap());
    z.string().parse(1); // => Error: expected string, received number
    
    // select custom namespace
    z.setErrorMap(makeZodI18nMap({ ns: "formValidation" }));
    z.string().parse(1); // => it is expected to provide string but you provided number
  3. Basic usage of zod-i18n-map

    main

    To translate Zod error messages, initialize i18next with your desired language and the zod translation resource, then set the Zod error map using zodI18nMap.

    import i18next from "i18next";
    import { z } from "zod";
    import { zodI18nMap } from "zod-i18n-map";
    // Import your language translation files
    import translation from "zod-i18n-map/locales/es/zod.json";
    
    // lng and resources key depend on your locale.
    i18next.init({
      lng: "es",
      resources: {
        es: { zod: translation },
      },
    });
    z.setErrorMap(zodI18nMap);
    
    // export configured zod instance
    export { z }
  4. Integrate zod-i18n-map with next-i18next

    main

    To use zod-i18n-map within a Next.js project using next-i18next, follow these steps:

    1. Install the package:
      yarn add zod-i18n-map
    
    2. **Add Translation Files**:
       Copy the Zod translation files from the `zod-i18n` repository and place them in your locale directories under the name `zod.json`.
       Example structure:
       ```text
       . 
       └── public 
           └── locales 
               ├── en 
               │   ├── common.json 
               │   └── zod.json  <-- Copy from zod-i18n/packages/core/locales/en/zod.json
               └── ja 
                   ├── common.json 
                   └── zod.json  <-- Copy from zod-i18n/packages/core/locales/ja/zod.json
    1. Configure next-i18next: Ensure your next-i18next.config.js points to your locale path:

      const path = require("path");
      module.exports = {
        i18n: {
          defaultLocale: "en",
          locales: ["en", "ja"],
        },
        localePath: path.resolve("./public/locales"),
      };
    2. Initialize the Error Map in your Page: Inside your React component, use the t function from useTranslation() to initialize makeZodI18nMap and pass it to z.setErrorMap().

    This setup ensures that Zod validation errors are automatically translated using your next-i18next configuration.

    import { useTranslation } from "next-i18next";
    import z from "zod";
    import { makeZodI18nMap } from "zod-i18n-map";
    
    function Page() {
      const { t } = useTranslation();
      z.setErrorMap(makeZodI18nMap({ t }));
      // ... rest of component
    }
  5. Using plurals in error messages

    main

    You can implement i18next-compliant pluralization for messages using {{maximum}}, {{minimum}}, or {{keys}}. In your translation JSON, use suffixes like _one and _other to define different forms.

    {
      "exact_one": "String must contain exactly {{minimum}} character",
      "exact_other": "String must contain exactly {{minimum}} characters"
    }
  6. Translate custom errors (e.g., from refine)

    main

    To translate custom errors generated by .refine(), include an i18n key within the params object of the refinement. You can pass a simple string key or an object containing a key and values for interpolation.

    // Using a simple key
    z.string()
      .refine(() => false, { params: { i18n: "my_error_key" } })
      .safeParse("");
    
    // Using a key with interpolation values
    z.string()
      .refine(() => false, {
        params: {
          i18n: { key: "my_error_key_with_value", values: { msg: "happened" } },
        },
      })
      .safeParse("");
  7. Use custom i18n keys in Zod custom errors

    main

    When using z.custom(), you can pass an i18n object within the params to specify a custom translation key and additional variables. This allows you to bypass the default error messages for specific validation logic.

    The i18n object can be:

    1. A simple string: used as the translation key.
    2. An object with key and values: key is the translation key, and values are the interpolation variables passed to t().

    Example:

    const schema = z.string().refine((val) => val !== 'forbidden', {
      params: {
        i18n: {
          key: 'errors.my_custom_error',
          values: { someVar: 'hello' }
        }
      }
    });
  8. Use makeZodI18nMap in a Next.js Page component

    main

    To enable translated Zod errors in a Next.js page, you must:

    1. Fetch translations on the server using serverSideTranslations in getServerSideProps.
    2. Access the t function via useTranslation() inside the component.
    3. Apply the error map using z.setErrorMap(makeZodI18nMap({ t })).

    This pattern is compatible with form libraries like react-hook-form using the @hookform/resolvers/zod resolver.

    import { useForm } from "react-hook-form";
    import { zodResolver } from "@hookform/resolvers/zod";
    import z from "zod";
    import { makeZodI18nMap } from "zod-i18n-map";
    import { serverSideTranslations } from "next-i18next/serverSideTranslations";
    import { useTranslation } from "next-i18next";
    
    export const getServerSideProps = async ({ locale }) => {
      return {
        props: {
          ...(await serverSideTranslations(locale!)),
        },
      };
    };
    
    const schema = z.object({
      nickname: z.string().min(5),
    });
    
    export default function Page() {
      const { t } = useTranslation();
      z.setErrorMap(makeZodI18nMap({ t }));
      const {
        register,
        handleSubmit,
        formState: { errors },
      } = useForm({
        resolver: zodResolver(schema),
      });
      
      return (
        <form onSubmit={handleSubmit(console.log)}>
          <label htmlFor="nickname">Nickname</label>
          <input id="nickname" {...register("nickname")} />
          {errors.nickname?.message && <p>{errors.nickname.message}</p>}
          <button type="submit">submit</button>
        </form>
      )
    }
  9. Customize error maps with makeZodI18nMap

    main

    For advanced configuration, use makeZodI18nMap. This function accepts a ZodI18nMapOption object to customize behavior.

    Options:

    • t: The i18n translation function.
    • ns: A string or array of strings specifying the translation namespaces to use. The default is zod.
    • handlePath: An object used to configure how object schema keys are handled. It accepts keyPrefix to specify a path in your translation files for mapping object keys to human-readable names.
    export type MakeZodI18nMap = (option?: ZodI18nMapOption) => ZodErrorMap;
    
    export type ZodI18nMapOption = {
      t?: i18n["t"];
      ns?: string | readonly string[];
      handlePath?: {
        keyPrefix?: string;
      };
    };
  10. Configure `makeZodI18nMap` options

    main

    When calling makeZodI18nMap, you can pass a ZodI18nMapOption object to configure how translations are resolved:

    • t: An optional i18n["t"] function. If not provided, it defaults to the global i18next.t.
    • ns: A string or array of strings specifying the i18next namespace(s) to use. Defaults to "zod".
    • handlePath: An option to control how the error path (the location in the object where the error occurred) is translated. If set to false, path translation is disabled.

    If handlePath is enabled (the default), it uses a HandlePathOption configuration.

  11. Configure path translation with `HandlePathOption`

    main

    The handlePath option allows you to translate the error path (e.g., user.address.zipcode) into a localized string. This is useful for displaying user-friendly field names in error messages.

    HandlePathOption properties:

    • context: A string used as a context for the translation (defaults to "with_path").
    • ns: The namespace to use for path translations. If not specified, it inherits the namespace from the main ZodI18nMapOption.
    • keyPrefix: An optional prefix for the translation key. If provided, the path is joined to this prefix (e.g., prefix.path.to.field).