NestboltNestbolt

@nestbolt/translatable

Introduction

JSON-based model translations for NestJS with TypeORM, automatic locale resolution, fallback chains, and PostgreSQL JSONB query helpers.

@nestbolt/translatable provides automatic locale-aware API responses for NestJS + TypeORM applications. Translations are stored as JSON objects directly in your database columns -- no separate translation tables, no joins, no complexity.

How It Works

Instead of creating a separate translations table with rows for each locale, this package stores all translations for a field in a single PostgreSQL jsonb column:

-- What gets stored in the database
SELECT name FROM products WHERE id = 1;
-- → {"en": "Laptop", "ar": "حاسوب محمول", "fr": "Ordinateur portable"}

When a client sends a request with an Accept-Language header, the response interceptor automatically resolves translatable fields to the requested locale:

GET /products/1
Accept-Language: ar

{ "id": 1, "name": "حاسوب محمول", "slug": "laptop" }

When no Accept-Language header is present, the full translation map is returned:

GET /products/1

{ "id": 1, "name": { "en": "Laptop", "ar": "حاسوب محمول" }, "slug": "laptop" }

Key Features

  • JSON-based storage -- Translations are stored as jsonb objects in the same table, eliminating the need for separate translation tables, join queries, or complex schema migrations.

  • Automatic locale resolution -- A middleware reads the Accept-Language header and a globally-registered interceptor transforms responses automatically. Your controllers need no changes.

  • Fallback locale chains -- Configure an ordered list of fallback locales. When a translation is missing in the requested locale, the system walks the chain until it finds one.

  • Chainable translation API -- Set, get, replace, and remove translations with a fluent, chainable API on your entities.

  • PostgreSQL JSONB query helpers -- Filter, search, and sort by translated values using type-safe query builder helpers that generate efficient jsonb queries.

  • Validation decorators -- Validate incoming translation maps in your DTOs with @IsTranslations(), including support for required locales.

  • Translation completeness tracking -- Check which locales are missing, whether an entity is fully translated, and generate completeness reports for admin dashboards.

  • Event emission -- Optionally emit events when translations change, enabling audit logs, cache invalidation, or search index updates.

  • GraphQL support -- The interceptor detects GraphQL execution contexts automatically and resolves translations from the underlying HTTP request headers.

  • Skip translation -- Use @SkipTranslation() on individual routes or entire controllers to bypass automatic resolution, returning the raw translation maps for admin panels or editing interfaces.

Why No Separate Translation Tables?

Traditional i18n approaches create a translations table with a row per field per locale. This leads to:

  • Complex join queries for every read operation
  • Migrations that touch multiple tables when adding a translatable field
  • N+1 query problems when loading lists of entities
  • Schema complexity that grows with every new locale

The JSON-based approach stores all translations for a field in a single column. This means:

  • A simple SELECT returns all translations -- no joins required
  • Adding a new translatable field is a single column addition
  • Adding a new locale requires zero schema changes
  • PostgreSQL's jsonb type provides efficient indexing and querying

The tradeoff is that this approach works best with PostgreSQL (which has native jsonb support). If you are using PostgreSQL with TypeORM, this package provides a streamlined, performant approach to model translations.

Architecture Overview

The package consists of several cooperating components:

ComponentPurpose
TranslatableModuleGlobal module that registers all providers
@Translatable()Property decorator that marks fields as translatable
TranslatableMixinClass mixin that adds translation methods to entities
TranslatableMiddlewareReads Accept-Language and sets locale via AsyncLocalStorage
TranslatableInterceptorResolves translatable fields in API responses
TranslatableServiceCentral service for locale management and fallback resolution
@SkipTranslation()Decorator to bypass auto-resolution on routes or controllers
@IsTranslations()Validation decorator for translation map DTOs
Query helpersPostgreSQL JSONB query functions for SelectQueryBuilder