Intl-T

Overview

Typed i18n where your JSON is the API

Intl-T

intl-t is a fully-typed, object-based internationalization (i18n) library for TypeScript, React, and Next.js. It offers a type-safe and amazing developer experience for managing translations in modern applications.

Banner

GitHubnivandres/intl-t

297

Your message file is the API. Write JSON, get a fully typed tree, with autocomplete on every key, at every depth, and the variables each message declares inferred for you.

Try it

No key registry to keep in sync. No t("some.string.you.hope.exists"). No codegen step.

("
  • homepage
  • homepage.title
  • homepage.welcome
  • homepage.items
  • homepage.hero
  • homepage.hero.cta
");

Type a dot and TypeScript walks your JSON with you.

In your app

components/hero.tsx
"use client";

import { useTranslation } from "@/i18n/translation";

export function Hero() {
  const t = useTranslation("homepage");

  return (
    <>
      <h1>{t.title}</h1>
      <p>{t.welcome({ user: "Ada" })}</p>
      <p>{t.items({ count: 3 })}</p>
      <button>{t.hero.cta}</button>
    </>
  );
}
app/[locale]/page.tsx
import { getTranslation } from "@/i18n/translation";

export default async function Page() {
  const t = await getTranslation("homepage");

  return (
    <>
      <h1>{t.title}</h1>
      <p>{t.welcome({ user: "Ada" })}</p>
    </>
  );
}
server.ts
import { t } from "./i18n/translation";

const tl = t[locale]; // this request's branch

console.log(String(tl.homepage.welcome({ user: "Ada" })));

A node renders straight into JSX. Call it to inject variables. Walk it to go deeper. It is the same object all the way down.

The numbers

Bundle5.6 KB gzip (core) · 7.7 KB gzip (provider + hook, react external) — the same client scenario measures 12.4 KB in next-intl
DependenciesZero third-party. The @intl-t/* packages are this library, split by surface.
StartupCreating a loader-backed tree is O(1) — ~6µs whether it holds 100 keys or 5,000
Throughput~1M plain reads/sec · ~120k interpolated renders/sec — a 500-string page renders its text in ~2ms
Tree-shakableImport intl-t/utils and nothing else ships

Why Intl-T

Honest version — here is where it wins, and where it doesn't.

It wins on developer experience. The typed tree with variable inference is something flat-key libraries structurally cannot do: they hand you a string and hope. Here, t.account.options.change is a real path your compiler checks, and the variables it needs are part of its type.

It wins on routing flexibility. Intl-T separates where the locale comes from (strategy) from what the URL looks like (pathPrefix). That lets you have /es/about URLs without an [locale] folder, or fully static pages with the locale hidden — combinations other Next.js i18n libraries cannot express. See Routing.

It wins on weight. Zero third-party dependencies, tree-shakable, and the realistic client runtime is ~40% lighter than next-intl's (7.7 KB vs 12.4 KB min+gzip, react external). On servers, cold start stays flat as your tree grows.

Other libraries are still the safer pick if you need a larger ecosystem, a bigger community answering questions, or an off-the-shelf translation-management integration today — they are battle-tested at a scale Intl-T is not. If those matter more than the DX, keep what you have. And if you're curious how far typed i18n can go, try the migration: your message files carry over unchanged.

Everywhere you render

Next.js App RouterRSC, server actions, request locale, routing proxy
Next.js Pages RoutergetServerSideProps, provider, hooks
React (Vite, CRA, …)Provider, hooks, JSX injection, localStorage persistence
Node, Bun, DenoThe core tree, no framework

On this page