@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
jsonbobjects 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-Languageheader 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
jsonbqueries. -
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
SELECTreturns 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
jsonbtype 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:
| Component | Purpose |
|---|---|
TranslatableModule | Global module that registers all providers |
@Translatable() | Property decorator that marks fields as translatable |
TranslatableMixin | Class mixin that adds translation methods to entities |
TranslatableMiddleware | Reads Accept-Language and sets locale via AsyncLocalStorage |
TranslatableInterceptor | Resolves translatable fields in API responses |
TranslatableService | Central 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 helpers | PostgreSQL JSONB query functions for SelectQueryBuilder |