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

colocale

v0.1.14

Published

A lightweight i18n library with fragment collocation pattern that works across frameworks and runtimes

Readme

colocale

A lightweight i18n library that works across frameworks and JavaScript runtimes.

Inspired by GraphQL's fragment collocation pattern, each component can declaratively define the translation keys it needs. Designed to work with any component-based framework including React, Vue, and others, it excels in both client-side and server-side rendering environments.

Features

  • 🎯 Colocation: Define translation keys alongside your components
  • 🔒 Type-safe: Full TypeScript support with auto-generated types
  • 📦 Lightweight: Zero dependencies, simple API
  • 🌐 Pluralization: Built on Intl.PluralRules for proper plural handling
  • Fast: Extract and send only the translations needed by components
  • 🔄 Universal: Works in Node.js, browsers, edge runtimes, and any JavaScript environment
  • 🎨 Framework-agnostic: Compatible with React, Vue, and other component-based frameworks

Runtime & Framework Compatibility

Colocale is designed to work across various JavaScript runtimes and frameworks:

Runtimes:

  • Node.js
  • Browsers
  • Edge runtimes (Cloudflare Workers, Vercel Edge, etc.)
  • Any JavaScript runtime with Intl support

Frameworks:

  • Works with any component-based framework (React, Vue, Svelte, etc.)
  • Particularly effective with frameworks that support server-side rendering
  • Examples available for React and Vue in the example/ directory

Whether you're building a client-side SPA, a server-rendered application, or a hybrid app, colocale adapts to your architecture.

Design Philosophy

Build-Time Safety Over Runtime Fallback

Colocale intentionally does not provide automatic runtime fallback to a default language when translations are missing. This is a deliberate design choice, not a limitation.

Why no automatic fallback?

Traditional i18n libraries often fall back to a default language (like English) when translations are missing. While convenient during development, this approach has significant downsides:

  • Silent failures in production: Missing translations go unnoticed until users report them
  • Inconsistent user experience: Users see a mix of their language and the fallback language
  • No accountability: Developers aren't forced to ensure complete translations before deployment

Our approach: Fail fast at build time, not runtime

Instead of hiding problems at runtime, colocale ensures translation completeness at build/CI time:

  • npx colocale check validates all translations before they reach production
  • CI/CD integration catches missing translations in pull requests
  • Type-safe keys prevent typos and invalid references at compile time
  • Consistent user experience - all translations are complete, or the build fails

Integrate into your CI pipeline:

# .github/workflows/ci.yml
- name: Validate translations
  run: npx colocale check messages

This design philosophy ensures that translation issues are caught early in the development process, not by your users in production. When you do need runtime behavior for truly missing keys (edge cases), the library returns the key name itself, making the issue immediately visible during testing.

Why No Provider/Context?

Unlike many i18n libraries, colocale intentionally avoids using React Context, Vue's provide/inject, or similar dependency injection mechanisms. This design choice enables several key benefits:

🌍 True Framework Agnosticism

  • Works identically in React, Vue, Svelte, or vanilla JavaScript
  • No framework-specific runtime dependencies
  • Same API across all environments

🔍 Explicit Dependencies

  • Component translation requirements are clearly visible in code
  • Easy to trace which translations a component tree needs
  • Facilitates static analysis and tree-shaking

⚙️ Universal Compatibility

  • Works seamlessly in server components, client components, and hybrid scenarios
  • No issues with framework-specific boundaries (like Next.js Server/Client Component boundary)
  • Runs in any JavaScript environment (Node.js, Deno, Bun, browsers)

🎯 Predictable Data Flow

  • Translations flow explicitly through props, following standard component patterns
  • No hidden dependencies through context
  • Easier to test and debug

Embracing Prop Drilling

Yes, colocale requires passing messages through props—this is intentional! While "prop drilling" is often seen as an anti-pattern, for i18n it provides significant advantages:

✅ Why It Works for i18n:

  1. Single prop: Only one messages object needs to be passed down
  2. Stable data: Translations rarely change during runtime
  3. Clear contract: Component interfaces explicitly show i18n dependency
  4. Performance: No context re-renders or provider overhead
  5. Flexibility: Components can be used anywhere without provider setup

📚 GraphQL Inspiration: This pattern is inspired by GraphQL's fragment collocation, where data requirements are defined alongside components and aggregated up the tree. Just as GraphQL fragments make data dependencies explicit, colocale makes translation dependencies explicit.

When to Use colocale

colocale is ideal when you want:

  • A framework-agnostic i18n solution that works everywhere
  • Explicit, traceable translation dependencies
  • To work with server-side rendering and modern meta-frameworks
  • Type-safe translations with minimal setup
  • A simple, predictable API without magic

If you prefer context-based solutions or need framework-specific features, consider alternatives like react-i18next or vue-i18n.

Installation

npm install colocale
# or
bun add colocale

Note: If you want to use the codegen command to generate TypeScript types, you'll need TypeScript installed in your project:

npm install -D typescript
# or
bun add -d typescript

CLI Tools

colocale provides 2 subcommands:

# Show help
npx colocale --help

# Validate translation files (RECOMMENDED: Add to CI/CD pipeline)
npx colocale check messages/ja          # Single locale
npx colocale check messages              # All locales + consistency check

# Generate type-safe defineRequirement function
npx colocale codegen messages            # Output: defineRequirement.ts (default)
npx colocale codegen messages src/i18n/defineRequirement.ts  # Custom output path

Why npx colocale check is Essential

The check command is your safety net for preventing translation issues in production:

  • Validates translation file structure: Ensures proper flat structure and valid JSON
  • Checks key consistency across locales: Detects missing or extra keys between languages
  • Validates plural forms: Ensures all required plural forms (_one, _other) are present
  • Verifies placeholder syntax: Catches malformed placeholders before they break at runtime
  • Cross-locale validation: When checking multiple locales, ensures all have identical key structures

Add to your CI/CD pipeline to enforce translation completeness:

# .github/workflows/ci.yml (or similar)
- name: Check translation completeness
  run: npx colocale check messages
  # Build will fail if any translations are missing or inconsistent

This ensures no missing translations slip into production, maintaining a consistent user experience across all supported languages.

Quick Start

💡 Looking for complete examples? Check out the working examples in the example/ directory:

1. Create Translation Files

Create JSON files for each namespace using flat structure (level 0).

Important: Translation files now use flat structure only. Nested objects are not allowed. Use dot notation for grouping (e.g., "profile.name" instead of nested {"profile": {"name": "..."}}).

// messages/en/common.json
{
  "submit": "Submit",
  "cancel": "Cancel",
  "itemCount_one": "1 item",
  "itemCount_other": "{{count}} items"
}
// messages/en/user.json
{
  "profile.name": "Name",
  "profile.email": "Email"
}

2. Placeholder Support

Colocale supports dynamic placeholders in your translation strings using the {{variableName}} syntax.

Translation file:

// messages/en/common.json
{
  "greeting": "Hello, {{name}}!",
  "welcome": "Welcome {{user}}, you have {{count}} new messages"
}

Usage:

const t = createTranslator(messages, commonTranslations);

t("greeting", { name: "Alice" });
// Output: "Hello, Alice!"

t("welcome", { user: "Bob", count: 5 });
// Output: "Welcome Bob, you have 5 new messages"

Placeholder rules:

  • Placeholders use double curly braces: {{variableName}}
  • Variable names must contain only alphanumeric characters and underscores
  • Values are automatically converted to strings
  • Placeholders work seamlessly with pluralization (see pluralization examples in translation files)

3. Generate Type-Safe defineRequirement Function (Recommended)

npx colocale codegen messages

This automatically generates a type-safe defineRequirement function from your translation files. The generated file (default: defineRequirement.ts) includes:

  • TypeScript type definitions for your translation structure
  • A ready-to-use defineRequirement function with full type inference

4. Define Translation Requirements

Create a dedicated file for translation requirements:

// translations.ts (or wherever you organize your i18n code)
import defineRequirement from "@/defineRequirement"; // Generated by codegen
import { mergeRequirements } from "colocale";

// Component-specific translation requirements with full type safety
export const userProfileTranslations = defineRequirement("user", [
  "profile.name",
  "profile.email",
]);

export const commonTranslations = defineRequirement("common", [
  "submit",
  "cancel",
]);

// Page-level merged requirements
export const userPageTranslations = mergeRequirements(
  commonTranslations,
  userProfileTranslations
);

Note: The defineRequirement function generated by codegen provides full type inference and compile-time validation automatically.

Use in your components:

// UserProfile.tsx (React) or UserProfile.vue (Vue)
import { createTranslator, type Messages } from "colocale";
import { userProfileTranslations } from "./translations";

export default function UserProfile({ messages }: { messages: Messages }) {
  const t = createTranslator(messages, userProfileTranslations);
  return (
    <div>
      <label>{t("profile.name")}</label>
      <label>{t("profile.email")}</label>
    </div>
  );
}

5. Use Translations in Your Application

// UserPage.tsx (React) or UserPage.vue (Vue)
import { createTranslator, type Messages } from "colocale";
import { commonTranslations, userPageTranslations } from "./translations";
import UserProfile from "./UserProfile";

export default function UserPage({ messages }: { messages: Messages }) {
  const t = createTranslator(messages, commonTranslations);

  return (
    <div>
      <UserProfile messages={messages} />
      <button>{t("submit")}</button>
      <button>{t("cancel")}</button>
    </div>
  );
}

6. Load and Extract Translations

Colocale requires translations to be organized in a locale-grouped format. How you load translations depends on your framework and architecture:

Basic structure:

// Compose translations into locale-grouped structure
const allMessages = {
  ja: {
    common: jaCommonTranslations,
    user: jaUserTranslations,
  },
  en: {
    common: enCommonTranslations,
    user: enUserTranslations,
  },
};

// Use pickMessages to extract only the needed translations for a specific locale
import { pickMessages } from "colocale";

const messages = pickMessages(
  allMessages,
  userPageTranslations,
  locale // e.g., "ja" or "en"
);

Translation file structure:

messages/
  ├── ja/
  │   ├── common.json
  │   └── user.json
  └── en/
      ├── common.json
      └── user.json
// messages/ja/common.json
{
  "submit": "送信",
  "cancel": "キャンセル"
}
// messages/en/common.json
{
  "submit": "Submit",
  "cancel": "Cancel"
}

See the example/ directory for complete implementations:

  • React example: Client-side application with static imports for translations
  • Vue example: Client-side application with dynamic imports for translations

Using with Next.js App Router

When using colocale with Next.js App Router, you can take advantage of server-side rendering for optimal performance. This pattern applies to any server-rendering framework, but Next.js is shown as a practical example.

Best Practices for Next.js

Separate translation requirements from Client Components to avoid bundler issues:

  1. Create a dedicated translations.ts file (without 'use client'):
// app/users/translations.ts
import defineRequirement from "@/defineRequirement";
import { mergeRequirements } from "colocale";

export const userProfileTranslations = defineRequirement("user", [
  "profile.name",
  "profile.email",
]);

export const commonTranslations = defineRequirement("common", [
  "submit",
  "cancel",
]);

export const userPageTranslations = mergeRequirements(
  commonTranslations,
  userProfileTranslations
);
  1. Use in Client Components:
// components/UserProfile.tsx
"use client";
import { createTranslator, type Messages } from "colocale";
import { userProfileTranslations } from "../app/users/translations";

export default function UserProfile({ messages }: { messages: Messages }) {
  const t = createTranslator(messages, userProfileTranslations);
  return (
    <div>
      <label>{t("profile.name")}</label>
      <label>{t("profile.email")}</label>
    </div>
  );
}

⚠️ Why separate files? If you export translation requirements from a Client Component (with 'use client'), Next.js's bundler creates proxy functions instead of the actual values, breaking mergeRequirements and type safety.

Loading Translations in Server Components

Translations must be organized in locale-grouped format. Import translation files per locale and namespace, then compose them:

// app/[locale]/users/page.tsx
import { pickMessages } from "colocale";
import { userPageTranslations } from "./translations";
import UserPage from "./UserPage";

// Import translations per locale and namespace (static imports)
import jaCommonTranslations from "@/messages/ja/common.json";
import jaUserTranslations from "@/messages/ja/user.json";
import enCommonTranslations from "@/messages/en/common.json";
import enUserTranslations from "@/messages/en/user.json";

export default async function Page({ params }: { params: { locale: string } }) {
  // Compose into locale-grouped structure
  const allMessages = {
    ja: {
      common: jaCommonTranslations,
      user: jaUserTranslations,
    },
    en: {
      common: enCommonTranslations,
      user: enUserTranslations,
    },
  };

  // pickMessages filters by locale and extracts only the needed translations
  const messages = pickMessages(
    allMessages,
    userPageTranslations,
    params.locale
  );

  return <UserPage messages={messages} />;
}

For larger applications, you can use dynamic imports:

// app/[locale]/users/page.tsx
import { pickMessages } from "colocale";
import { userPageTranslations } from "./translations";
import UserPage from "./UserPage";

export default async function Page({ params }: { params: { locale: string } }) {
  // Extract required namespaces from translation requirements
  const namespaces = userPageTranslations.map((req) => req.namespace);
  // Remove duplicates to avoid importing the same file multiple times
  const uniqueNamespaces = Array.from(new Set(namespaces));

  // Dynamically import only the needed locale's translations
  const translations = await Promise.all(
    uniqueNamespaces.map(async (namespace) => ({
      namespace,
      data: (
        await import(`@/messages/${params.locale}/${namespace}.json`)
      ).default,
    }))
  );

  // Compose into locale-grouped structure
  const allMessages = {
    [params.locale]: Object.fromEntries(
      translations.map(({ namespace, data }) => [namespace, data])
    ),
  };

  // pickMessages filters to the specified locale
  const messages = pickMessages(
    allMessages,
    userPageTranslations,
    params.locale
  );

  return <UserPage messages={messages} />;
}

Next.js Best Practices Summary

  • DO create a separate translations.ts file (without 'use client') for translation requirements
  • DO import translation requirements from this shared file in both Server and Client Components
  • DO colocate translations.ts with the components that use them (e.g., per page or feature folder)
  • DON'T export translation requirements from files with 'use client' directive
  • DON'T define translation requirements inside Client Components if they need to be used in Server Components

API Reference

pickMessages

Extracts only the needed translations from locale-grouped translation files.

function pickMessages(
  allMessages: LocaleTranslations,
  requirements: TranslationRequirement[] | TranslationRequirement,
  locale: Locale
): Messages;

Parameters:

  • allMessages: Object containing translations grouped by locale: { [locale]: { [namespace]: { [key]: translation } } }
  • requirements: Translation requirement(s) defining which keys to extract
  • locale: Locale identifier (see Locale type) - used for filtering translations and proper pluralization with Intl.PluralRules

Locale type: The Locale type provides autocomplete for supported locale codes ("en", "ja") while still accepting any BCP 47 language tag as a string.

Automatic plural extraction: When you specify a base key (e.g., "itemCount"), keys with _one, _other suffixes are automatically extracted based on Intl.PluralRules.

createTranslator

Creates a translation function bound to a specific namespace from a TranslationRequirement.

function createTranslator<R extends TranslationRequirement<string>>(
  messages: Messages,
  requirement: R
): ConstrainedTranslatorFunction<R>;

Key constraint: The returned translator function is constrained to only accept keys defined in the TranslationRequirement.

mergeRequirements

Merges multiple translation requirements into a single array.

function mergeRequirements(
  ...requirements: TranslationRequirement<string>[]
): TranslationRequirement<string>[];

defineRequirement

Helper function to create a TranslationRequirement with compile-time type validation.

Generated by codegen (Recommended):

The codegen command generates a type-safe defineRequirement function that automatically validates namespaces and keys:

npx colocale codegen messages  # Generates defineRequirement.ts
import defineRequirement from "./defineRequirement"; // Generated file

// ✅ Full type safety with auto-completion
const req = defineRequirement("common", ["submit", "cancel"]);

// ❌ Compile error - namespace doesn't exist
const req = defineRequirement("invalid", ["key"]);

// ❌ Compile error - key doesn't exist in namespace
const req = defineRequirement("common", ["invalid"]);

Manual usage (without codegen):

The recommended approach is to use the codegen command to generate the type-safe defineRequirement function. If you need to create translation requirements manually without type safety, you can create them directly:

import type { TranslationRequirement } from "colocale";

// Manually create a translation requirement (no compile-time type safety)
const req: TranslationRequirement<readonly ["submit", "cancel"]> = {
  namespace: "common",
  keys: ["submit", "cancel"],
};

Note: Manual usage does not provide compile-time validation of namespaces and keys. Use the codegen command for full type safety.

License

MIT