October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk5 min

PostgreSQL Translatable Columns: Add i18n Without Rewriting Your App

PostgreSQL can store translations in JSONB or a dedicated relation, but an application still needs a locale resolver. Learn how to add storage incrementally without a broad rewrite.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

You can store translations in PostgreSQL without adding a column for every language. The practical choices are a locale-keyed jsonb column on the existing row or a separate translation table. Neither changes what an application displays by itself: the app must select a locale and apply explicit fallback rules. To avoid a broad rewrite, add a small compatibility layer at the query or model boundary and keep the rest of the application-facing interface stable.

What PostgreSQL can—and cannot—do for application i18n

PostgreSQL supports character sets, locale-sensitive behavior such as collation, and localized server messages. Those features are different from translating product names, descriptions, or other application content. A collation can affect comparisons and sorting; it does not translate a stored value.

As an Amazon Associate I earn from qualifying purchases.

The schema can hold multiple language versions, but some component still has to decide which one a request needs. That component must define locale negotiation, fallback order, and behavior when no translation exists. A reader’s concern in a public discussion put the schema issue plainly: “I don’t want to add an extra column for each supported language.” One JSONB field or a translation relation addresses that column-growth concern; neither supplies language selection automatically.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose where the translations belong

Use JSONB for a modest, relatively stable set of translations that is usually read alongside its parent row. Use a translation relation when each translation needs its own relational constraints, workflow state, or row-level management. There is no universal winner: choose based on how translations are queried, changed, validated, and exposed through the existing application.

Option 1: A locale-keyed JSONB column

ALTER TABLE products ADD COLUMN name_i18n jsonb;

-- Example value:
-- {"en": "Hat", "es": "Sombrero", "fr-CA": "Chapeau"}

A JSONB object keeps a product’s translated labels next to the product and avoids a physical column per language. Use standardized locale identifiers and a predictable object shape. Decide whether matching requires an exact tag such as fr-CA, whether it may fall back to a language-only tag such as fr, and what the final fallback is. Do not rely on JSON key order to choose a translation.

  • Good fit: locale sets can vary by row, translation payloads are manageable, and common reads fetch the parent record with its localized value.
  • Validation: JSONB does not by itself ensure that keys are supported locales or that required translations exist. Enforce those rules with application validation or suitable database constraints.
  • Indexing: PostgreSQL supports GIN indexes for documented JSONB containment, key-existence, and JSONPath operators. An index helps only when the query predicates use operators it supports; it is not automatically useful for every lookup of a JSON value.
  • Updates: changing JSONB changes the containing row, and PostgreSQL locks the whole row for an update. Keep the document reasonably sized and avoid treating it as a separately mutable translation store with frequent concurrent edits.

Option 2: A translation relation

CREATE TABLE product_translation (
  product_id bigint NOT NULL REFERENCES products(id),
  locale text NOT NULL,
  name text NOT NULL,
  PRIMARY KEY (product_id, locale)
);

This design gives each product-locale translation its own row. The primary key makes the one-translation-per-product-and-locale rule explicit, and ordinary relational constraints can support validation and workflow needs. Reads typically require a join or a separate lookup, and the application still needs a resolver to choose the locale and fallback.

A relation is often a better fit when you need to audit translation completeness, attach review or publication state, or constrain locale values. It is a schema design option, not a built-in PostgreSQL localization framework.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How the options differ

Concern JSONB on the parent row Translation relation
Storage shape One object on the existing row, keyed by locale One row per parent record and locale
Read pattern Can retrieve the parent and its translations together Usually requires a join or lookup
Constraints and workflow Locale and completeness rules need deliberate validation Relational keys and per-translation workflow fields are natural to model
Update scope Updates lock the containing row Each translation is a separate row
Indexing GIN can support suitable JSONB operators Relational indexes and constraints can support locale-keyed lookup

Keep the application interface stable with a resolver

If the application still executes SELECT name FROM products, adding name_i18n does not change the returned value. A database migration alone cannot make existing code display a request-specific language. The narrowest useful change is generally a resolver near the query or model boundary, or an adapter such as a view where the application’s SQL and write behavior make that safe.

For example, a resolver can accept the requested locale and a fallback chain, then choose from JSONB or fetch the corresponding translation row. The exact SQL and interface depend on how the application reads and writes the field. Before using a view for compatibility, confirm how inserts and updates behave and whether the ORM depends on particular table metadata.

A generated column is not a general substitute for that resolver. PostgreSQL generated expressions are limited to immutable expressions over the current row and cannot use subqueries, so they cannot dynamically look up a per-request locale or another translation row. Keep request-specific locale selection in an application-aware layer.

Plan an incremental migration

  1. Map the field’s use. Find reads and writes in application queries, ORM-generated SQL, background jobs, exports, and cache keys. Identify which component can receive the requested locale.
  2. Add storage without changing current behavior. Add nullable JSONB storage or create the translation relation. Leave the existing field’s meaning intact while translations are populated.
  3. Define the resolver contract. Specify exact locale matching, fallback order, and the response when a translation is missing. Decide whether the original-language value is the final fallback.
  4. Populate and validate. Backfill or enter translations, then measure missing values and validate locale and completeness rules before relying on them.
  5. Check query plans and index needs. Add JSONB indexes only for predicates that use supported operators; test the actual application queries.
  6. Roll out in stages. Verify locking and deployment behavior for the exact ALTER TABLE subform, table, and PostgreSQL version. PostgreSQL documents different lock levels for different forms; ACCESS EXCLUSIVE is the default unless otherwise stated.
  7. Keep a rollback path. Retain the old read and write path until the application consistently uses the intended translation route.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Keep sorting and search separate from translation storage

Sorting and comparisons

Collation determines locale-sensitive comparison and sort behavior; it does not provide translated content. The PostgreSQL 17 documentation defines a collation as “an SQL schema object that maps an SQL name to locales provided by libraries installed in the operating system.” PostgreSQL can use providers including ICU, when enabled in the build, and libc. ICU behavior can vary with the ICU version, while libc locale behavior can differ across platforms.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

ICU collations can be customized for language behavior and insensitive comparisons. Nondeterministic collations can treat byte-distinct strings as equal, but carry performance and operational tradeoffs; PostgreSQL’s documentation notes that pattern matching is unavailable for them. If sort order or uniqueness matters, test representative names, accents, and comparisons against the PostgreSQL and ICU build you deploy.

Full-text search

Full-text search uses its own language configurations and dictionaries. Putting translations in JSONB or choosing a collation does not automatically provide suitable tokenization or stemming for each language. Select and validate configurations for the languages the product actually searches, using representative vocabulary.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.