npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

drizzle-i18n

v0.2.0

Published

Type-safe database localization layer for Drizzle ORM

Downloads

156

Readme

drizzle-i18n

Type-safe database localization for Drizzle ORM.

Features

  • Two localization strategies -- separate translation table (normalized) or JSON column (denormalized)
  • Three dialects -- PostgreSQL (jsonb), MySQL (json), SQLite (text with mode: "json")
  • Fully type-safe -- $inferSelect, $inferInsert, locale keys, and query results all carry correct types
  • Zero runtime dependencies -- only drizzle-orm as a peer dependency
  • drizzle-kit compatible -- generated tables work with drizzle-kit generate / push / migrate

Installation

npm i drizzle-i18n

Requires drizzle-orm >= 0.35.0 as a peer dependency.

Quick Start

Strategy 1: Translation Table

Stores translations in a separate {entity}_translations table with a foreign key, locale column, and translatable fields.

import { pgTable, serial, integer, text } from "drizzle-orm/pg-core";
import { translationTable, withTranslation } from "drizzle-i18n/pg";

// 1. Define the base entity
export const products = pgTable("products", {
  id: serial("id").primaryKey(),
  sku: text("sku").notNull(),
  price: integer("price").notNull(),
});

// 2. Declare translatable fields
const productI18n = translationTable(products, {
  name: text("name").notNull(),
  description: text("description"),
});

// 3. Export table + relations (drizzle-kit needs the table export)
export const productTranslations = productI18n.table;
export const productTranslationsRelations = productI18n.translationsRelations;
export const productsRelations = productI18n.parentRelations;

// 4. Query with a locale (locale filter is in the JOIN, not WHERE)
const rows = await withTranslation(db, products, productI18n, {
  locale: "ar",
  fallback: "en",
}).where(eq(products.id, 1));
// => [{ id: 1, sku: "ABC", price: 100, name: "هاتف", description: "..." }]

Strategy 2: JSON Column

Stores translations inline as JSON objects ({ "en": "...", "ar": "..." }).

import { pgTable, serial, integer } from "drizzle-orm/pg-core";
import { jsonTranslations, forLocale } from "drizzle-i18n/pg";

// 1. Spread JSON columns into the table
export const categories = pgTable("categories", {
  id: serial("id").primaryKey(),
  sortOrder: integer("sort_order"),
  ...jsonTranslations({
    name: { notNull: true },
    description: {},
  }),
});
// Produces: name jsonb NOT NULL, description jsonb

// 2. Extract a single locale in queries
const rows = await db.select({
  id: categories.id,
  name: forLocale(categories.name, "ar", { fallback: "en" }),
}).from(categories);
// => [{ id: 1, name: "إلكترونيات" }]

Strict Locale Typing

Use createI18n() to lock down valid locales across all helpers:

import { createI18n } from "drizzle-i18n/pg";

const i18n = createI18n({
  defaultLocale: "en",
  locales: ["en", "ar", "fr"] as const,
  strict: true, // runtime validation
});

// All helpers are locale-scoped
i18n.forLocale(col, "ar");   // OK
i18n.forLocale(col, "de");   // Type error + runtime error (strict mode)

// jsonTranslations columns are typed as { en: string } & Partial<{ ar: string; fr: string }>

Mutation Helpers

import {
  insertWithTranslations, upsertTranslation,
  setTranslations, updateLocale,
} from "drizzle-i18n/pg";

// Insert parent + translations in one atomic call (transactional)
await insertWithTranslations(db, products, productI18n, {
  values: { sku: "ABC", price: 999 },
  translations: {
    en: { name: "Phone", description: "A phone" },
    ar: { name: "هاتف", description: "هاتف ذكي" },
  },
});

// Upsert a single locale (ON CONFLICT DO UPDATE)
await upsertTranslation(db, productI18n, {
  product_id: 1,
  locale: "fr",
  name: "Telephone",
});

// Bulk upsert multiple locales (partial -- only provided locales are touched)
await setTranslations(db, productI18n, {
  product_id: 1,
  translations: {
    en: { name: "Smartphone" },
    ar: { name: "هاتف ذكي", description: "وصف المنتج" },
  },
});

// Update a single locale in a JSON column
await updateLocale(db, categories, categories.name, {
  where: eq(categories.id, 1),
  locale: "fr",
  value: "Electronique",
});

Relational Query Post-Processing

import { localizeResults } from "drizzle-i18n/pg";

const rows = await db.query.products.findMany({ with: { translations: true } });
const localized = localizeResults(rows, productI18n, {
  locale: "ar",
  fallback: "en",
});
// => [{ id: 1, sku: "ABC", name: "هاتف", description: "..." }]
// translations array is stripped, fields are flattened

API Reference

Schema

| Function | Strategy | Description | | --- | --- | --- | | translationTable(parent, columns, opts?) | Separate table | Generate {entity}_translations table + relations | | jsonTranslations(fields) | JSON column | Generate JSON/JSONB columns to spread into a table |

Queries

| Function | Strategy | Description | | --- | --- | --- | | forLocale(column, locale, opts?) | JSON column | SQL expression extracting a locale value as a plain string | | withTranslation(db, parent, i18nResult, opts) | Separate table | Locale-aware LEFT JOIN -- returns flat rows with translated fields | | localizeResults(rows, i18nResult, opts) | Separate table | Post-process relational query results into flat rows | | missingTranslations(db, parent, i18nResult, locale) | Separate table | Find entities missing a translation for a locale | | orderByLocale(column, locale, direction?) | JSON column | ORDER BY expression on a translated JSON column |

Mutations

| Function | Strategy | Description | | --- | --- | --- | | insertWithTranslations(db, parent, i18nResult, data) | Separate table | Insert parent + translations atomically (transactional) | | upsertTranslation(db, i18nResult, data) | Separate table | Insert or update a single locale row | | setTranslations(db, i18nResult, data) | Separate table | Bulk upsert multiple locales (partial -- safe for different column subsets) | | updateLocale(db, table, column, opts) | JSON column | Partial JSON update for one locale |

Utilities

| Function | Description | | --- | --- | | createI18n(config) | Factory with strict locale typing across all helpers | | exportTranslations(rows, i18nResult, fkKey) | Convert translation rows to locale-keyed format | | importTranslations(data, fkKey) | Convert locale-keyed format back to flat rows |

Dialect Support

Import from the dialect-specific entry point:

| Dialect | Import | JSON Column Type | | --- | --- | --- | | PostgreSQL | drizzle-i18n/pg | jsonb | | MySQL | drizzle-i18n/mysql | json | | SQLite | drizzle-i18n/sqlite | text({ mode: "json" }) |

Contributing

See CONTRIBUTING.md for development setup and guidelines.

License

MIT