Back to list
dev_to 2026年4月24日

Kotori: React 向けの強力な型付けされモジュラーな国際化ライブラリ

Kotori, strongly typed and modular i18n library for React

Translated: 2026/4/24 22:00:27
reacttypescripti18ninternationalizationcode-splitting

Japanese Translation

Kotori は、型安全性と開発体験を重視し、オーバヘッドなしに開発を行う開発者向けに設計された、React 用の型付けされモジュラーな国際化ライブラリです。サイズ:0.39kb(gzipped)。依存関係:なし。セットアップ:JSON、コード生成、スキーマファイルはいずれも不要。 「魔法」:型が推測される変数 Kotori の代表的な機能は、TypeScript のテンプレート文字列型を活用して文字列をパースする方法にあります。別々にスキーマを維持したり、コード生成ステップを実行したりする代わりに、主要な言語の文字列が型契約となります。あなたの英語の文字列に「{{name}}」が含まれている場合、Kotori は他のすべての言語に「{{name}}」が含まれていることを保証し、翻訳関数を呼び出す際に name 値を提供していることを保証します。 const { dict } = kotori({ primaryLanguageTag: 'en', secondaryLanguageTags: ['zh', 'ja', 'ms'], }) // ❌ TypeScript エラー:日本語の翻訳が不足 const intro = dict({ // 「真の信憑性のあるソース」 en: 'Hello {{name}}, is it {{time}} now?', // ❌ TypeScript エラー:キー 'name' が不足 zh: '你好,现在是 {{time}} 吗?', // ❌ TypeScript エラー:未知のキー 'nam' ms: 'Hai {{nam}}, adakah pukul {{time}} sekarang?', }); // ^ オプショナル:さらに型を厳密化 // 文字列を厳密な契約に変換することで、Kotori は開発中の最も一般的な国際化バグをキャッチし、本番環境ではなく開発時にキャッチします: // ✅ 完全に機能 t('intro', { name: 'John', time: '12:25' }) // ❌ TypeScript エラー:{ name } が不足 t('intro', { time: '12:25' }) // ❌ TypeScript エラー:未知のキー 'nama' t('intro', { nama: 'John', time: '12:25' }) // ❌ TypeScript エラー:'time' の形式が不正(HH:MM を期待) t('intro', { name: 'John', time: '12-00' }) このアプローチは、「文字列の誤入力」カテゴリのバグを完全に消去します。コードがコンパイルされる場合、変数がすべてのサポートされている言語に正しくマッピングされていることに自信を持てます。 真にモジュラー(コード分割可能) 最も標準的な国際化ライブラリは、1 つの巨大な中央集権的な辞書を読み込みます。Kotori はこのモデルを逆転させます。それは翻訳を、それらを使用するコンポーネントや機能ファイル内に直接置かれるように促します。これは単純に見えますが、建築的な含意は巨大です。定義をこのように分離することで、あなたはモダンなバンドラ(Vite または Webpack など)のネイティブな機能を活かし、自動的に翻訳をコード分割します。ユーザーが /page1 に訪問しても、/page2 の翻訳はダウンロードされません。あなたは必要な場所で翻訳を定義します: // page1.tsx import { createTranslations, dict } from './utils' const intro = dict({ en: 'my name is {{name}}, I am {{age}} years old.', zh: '我叫{{name}},我今年{{age}}岁了。', }) const { useTranslations } = createTranslations({ intro }) このモジュラーな設計により、あなたのバンドラは自動的に翻訳をコード分割できます。ユーザーが実際に訪問しているページのための文字列のみを読み込みます。 グローバルな状態、ローカルな定義 注意点として、定義がコンポーネントに局在化されていることは重要ですが、背後にある言語状態はグローバルです。あなたの kotori インスタンス(通常はユーティリティファイルで定義される)が現在のロケールを管理します。ページ 1 の設定コンポーネントで setLanguage('jp') を呼び出すと、ページ 2、ページ 3、および任意の子コンポーネントにわたるすべての useTranslations フックは、新しい日本語の文字列と瞬時に再レンダリングされます。 試してください 完全に型安全性:コンパイル時に、本番環境ではなく欠落する変数や翻訳キーをキャッチします。 真にモジュラー:翻訳をコンポーネントとコロケートさせることで、自動コード分割を有効にします。 ゼロ設定:JSON ファイル、外部の CLI ツール、コード生成なし。純粋な TypeScript だけです。 React の国際化を扱うための軽量で「目に見えない」方法をお探しの場合は、ぜひ試してあなたの感想を教えてください。 GitHub: https://github.com/tylim88/Kotori

Original Content

Kotori is a strongly-typed, modular i18n library for React. It’s designed for developers who care about type safety and developer experience without the overhead. Size: 0.39kb gzipped. Dependencies: Zero. Setup: No JSON, no codegen, no schema files. The "Magic": Type-Inferred Variables The standout feature of Kotori is how it leverages TypeScript’s template literal types to parse your strings. Instead of maintaining a separate schema or running a codegen step, your primary language string becomes the type contract. If your English string contains {{name}}, Kotori ensures that every other language also includes {{name}}, and that you provide a name value when calling the translation function. const { dict } = kotori({ primaryLanguageTag: 'en', secondaryLanguageTags: ['zh', 'ja', 'ms'], }) // ❌ TypeScript error: missing japanese translation const intro = dict({ // The "Source of Truth" en: 'Hello {{name}}, is it {{time}} now?', // ❌ TypeScript error: missing key 'name' zh: '你好,现在是 {{time}} 吗?', // ❌ TypeScript error: unknown key 'nam' ms: 'Hai {{nam}}, adakah pukul {{time}} sekarang?' })<{ name: string; time: `${number}:${number}` }> // ^ Optional: Narrow your types further By turning your strings into a strict contract, Kotori catches the most common i18n bugs during development rather than in production: // ✅ Works perfectly t('intro', { name: 'John', time: '12:25' }) // ❌ TypeScript error: missing { name } t('intro', { time: '12:25' }) // ❌ TypeScript error: unknown key 'nama' t('intro', { nama: 'John', time: '12:25' }) // ❌ TypeScript error: invalid format for 'time' (expects HH:MM) t('intro', { name: 'John', time: '12-00' }) This approach eliminates the "string typo" category of bugs entirely. If the code compiles, you can be confident that your variables are correctly mapped across all supported languages. Truly Modular (Tree-shakeable) Most standard i18n libraries load one giant, centralized dictionary. Kotori flips this model. It encourages you to colocate your translations directly inside the component or feature files that use them. This might seem simple, but the architectural implications are huge. By separating definitions this way, you are leveraging the native power of modern bundlers (like Vite or Webpack) to code-split your translations automatically. A user visiting /page1 never downloads the translations for /page2. You define the translation where you need it: // page1.tsx import { createTranslations, dict } from './utils' const intro = dict({ en: 'my name is {{name}}, I am {{age}} years old.', zh: '我叫{{name}},我今年{{age}}岁了。', }) const { useTranslations } = createTranslations({ intro }) Because of this modular design, your bundler can naturally code-split your translations. You only load the strings for the page the user is actually visiting. Global State, Local Definition It’s important to note that while your definitions are localized to the component, the underlying language state is global. Your kotori instance (usually defined in a utility file) manages the current locale. When you call setLanguage('jp') in a settings component on Page 1, every useTranslations hook across Page 2, Page 3, and any child component re-renders with the new Japanese strings instantly. Give it a try Fully Type-Safe: Catch missing variables and translation keys at compile time, not in production. Truly Modular: Enable automatic code-splitting by colocating translations with their components. Zero-Config: No JSON files, no external CLI tools, and no codegen. Just pure TypeScript. If you’re looking for a lightweight, "invisible" way to handle internationalization in React, I’d love for you to check it out and let me know what you think. GitHub: https://github.com/tylim88/Kotori