sveltekit-superforms でエラーメッセージを日本語化できました

# はじめに

以下の投稿で、[SvelteKit] で作成したアプリに入力チェックを導入しました。ただし、エラーメッセージの日本語化を実現できずに、保留していました。

https://qiita.com/fsd-tetsu/items/4ca5e6152d25628b68bb

日本語化できる方法が分かったので、その方法を記載します。

# 修正内容

前述の記事に記載した `users.js` を以下の様に修正することで、日本語を表示できました。

```diff_javascript:src/lib/users.js
import { z } from 'zod';
+import i18next from 'i18next';
+import translation from 'zod-i18n-map/locales/ja/zod.json' assert {type: "json"};
+import { makeZodI18nMap } from 'zod-i18n-map';
+
+i18next.init({
+	lng: 'ja',
+	resources: {
+		ja: { translation },
+	},
+});
+
+z.setErrorMap(makeZodI18nMap({ t:i18next.t, ns: 'translation' }));

// See https://zod.dev/?id=primitives for schema syntax
/**
 * ユーザーのスキーマ
 */
export const userSchema = z.object({
  id: z.string().max(20),
  name: z.string().min(2).max(20).endsWith("!"),
  email: z.string().email()
});

...
```

アプリを起動してアクセスすると、日本語が表示されることを確認できましたー。:joy:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2773745/f88d40c5-618d-f5ff-dcd7-17563724684e.png)

# 解決までの経緯

この解決策に至った経緯は、`node_modules` フォルダ内にダウンロードされた [zod-i18n-map] のソースコードを確認していたところ、`makeZodI18nMap()` 関数の引数に `t` と `ns` を指定できることに気づいたのがきっかけでした。

```typescript:node_modules/zod-i18n-map/dist/index.d.ts
...
type ZodI18nMapOption = {
    t?: i18n["t"];
    ns?: string | readonly string[];
    handlePath?: HandlePathOption;
};
...
```

これ以前にいろいろ調べた結果、`i18next.init(..)` した状態を `zod-i18n-map` 側で共有できていないっぽいことに気づいていました。ですので、この `t` に `i18next.t` を指定すれば良さそう！と思いついた次第です。

```js
z.setErrorMap(makeZodI18nMap({ t:i18next.t }));
```

---

しかし、これだけでは日本語化されませんでした。
いままで、エラーメッセージが `undefined` だったのが、英語で表示されるようになっただけでした。

さらに [zod-i18n-map] のソースコードを追っていくと、`ns` というパラメータがデフォルトで `zod` となっていることが分かりました。このパラメータは [i18next] における[ネームスペース](https://www.i18next.com/principles/namespaces)に相当します。


前述の `index.d.ts` より、`makeZodI18nMap()` 関数の引数に `ns` というパラメータがあります。この関数のソースコードを確認すると、ここで指定した値(`option.ns`)によって `ns` が上書きされることが分かりました。

```javascript:node_modules/zod-i18n-map/dist/index.js
...
var defaultNs = "zod";
var makeZodI18nMap = (option) => (issue, ctx) => {
  const { t, ns, handlePath } = {
    t: import_i18next.default.t,
    ns: defaultNs,
    ...option, // t と ns が上書きされる
    handlePath: {
      context: "with_path",
      ns: option?.ns ?? defaultNs,
      keyPrefix: void 0,
      ...option?.handlePath
    }
  };
...
```

そして、あてずっぽうで `ns` に `undefined` を指定したところ、日本語でエラーメッセージを取得できました。

```js
z.setErrorMap(makeZodI18nMap({ t:i18next.t, ns: undefined }));
```

---

2023/4/20 追記：

あてずっぽうではいかんですよねw
というわけで、[i18next] についてもっと調査してみたところ、ちゃんと理解できましたー。

以下の様に、`ns` に `translation` を指定すれば良かったのです。
（どうやら、まだJavaScript の省略記法に慣れてなかったようです。。。）
参考: https://www.i18next.com/principles/fallback#namespace-fallback

```diff_javascript:src/lib/users.js
import { z } from 'zod';
import i18next from 'i18next';
import translation from 'zod-i18n-map/locales/ja/zod.json' assert {type: "json"};
import { makeZodI18nMap } from 'zod-i18n-map';

i18next.init({
	lng: 'ja',
	resources: {
		ja: { translation }, // この記述は実際には ja: { translation: translation }。キー名が ns に相当する。
	},
});

-z.setErrorMap(makeZodI18nMap({ t:i18next.t, ns: undefined }));
+z.setErrorMap(makeZodI18nMap({ t:i18next.t, ns: 'translation' }));

// See https://zod.dev/?id=primitives for schema syntax
/**
 * ユーザーのスキーマ
 */
export const userSchema = z.object({
  id: z.string().max(20),
  name: z.string().min(2).max(20).endsWith("!"),
  email: z.string().email()
});

...
```

ちなみに、動的に言語を切り替える場合は以下の様に `addResourceBundle()` と `changeLanguage()` を使うことで実現できます。

```js
import ja from 'zod-i18n-map/locales/ja/zod.json' assert { type: "json" };
import es from 'zod-i18n-map/locales/es/zod.json' assert { type: "json" };

i18next.init();

i18next.addResourceBundle('ja', 'translation', ja);
i18next.changeLanguage('ja');
console.log("ja:", i18next.t('errors.custom')); // → ja: 入力形式が間違っています。

i18next.addResourceBundle('es', 'customNs', es); // 独自のネームスペースを設定可能。
i18next.changeLanguage('es');
console.log("es:", i18next.t('customNs:errors.custom')); // → es: Entrada inválida
// 独自のネームスペースをコロン区切りで先頭に記載することでネームスペースが適用される。
```

# まとめ

これで [SvelteKit] で入力チェックエラーを日本語化することに成功しました。一安心です。:blush:

ドキュメントを見ただけでは把握できなかったことが、ソースコードを追うことで把握できました。JavaScritp の場合、`node_modules` にソースコード一式がダウンロードされるし、直接修正してデバッグもできるので便利です。

まだまだ、JavaScript 初心者なところがあるので、もっと鍛えていこうと思います。:smiley:


[SvelteKit]:https://kit.svelte.dev/
[zod-i18n-map]:https://github.com/aiji42/zod-i18n
[i18next]:https://www.i18next.com/