Database Designi18nLocalizationSQLSaaS

Multi-Language Database Design for SaaS

Design database schemas for multi-language SaaS applications — translation tables, locale storage, fallback chains, and performance optimization strategies.

Sam Rivera
Sam Rivera
June 1, 202613 min read

TL;DR: Multi-language database design involves choosing between translation tables, locale columns, and JSON storage. This guide covers schema patterns, fallback strategies, and query optimization for SaaS i18n. Examples from tanstackship.com.

Introduction

When your SaaS needs to support multiple languages, the database schema is the foundation. A poor design leads to complex queries, performance issues, and maintenance headaches. The right approach depends on your content types and access patterns. For the big-picture i18n architecture, start with our i18n for SaaS: Architecture Patterns Compared.

Schema Design Patterns

PatternFlexibilityQuery ComplexityPerformanceBest For
Translation tableHighMediumGoodDynamic content
Locale columnsLowLowExcellentFixed few locales
JSON/JSONB columnMediumLowGoodSimple translations
EAV (Entity-Attribute-Value)Very highHighPoorRarely recommended
Separate DB per localeMaximumHighVariesEnterprise multi-region

Translation Table Pattern (Recommended)

sql
-- Base content table (language-independent)
CREATE TABLE blog_posts (
  id TEXT PRIMARY KEY,
  slug TEXT NOT NULL UNIQUE,
  author_id TEXT NOT NULL,
  published_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Translation table (one row per locale)
CREATE TABLE blog_post_translations (
  blog_post_id TEXT NOT NULL REFERENCES blog_posts(id),
  locale TEXT NOT NULL,
  title TEXT NOT NULL,
  excerpt TEXT,
  body TEXT NOT NULL,
  meta_description TEXT,
  is_auto_translated BOOLEAN DEFAULT false,
  updated_at TIMESTAMP DEFAULT NOW(),
  PRIMARY KEY (blog_post_id, locale)
);

-- Query with fallback chain
SELECT 
  COALESCE(t.title, f.title) as title,
  COALESCE(t.body, f.body) as body
FROM blog_posts p
LEFT JOIN blog_post_translations t 
  ON t.blog_post_id = p.id AND t.locale = 'de'
LEFT JOIN blog_post_translations f 
  ON f.blog_post_id = p.id AND f.locale = 'en'
WHERE p.slug = 'getting-started';

Locale Columns Pattern (Simple, Few Locales)

sql
-- Locale columns: simple but not scalable
CREATE TABLE products (
  id TEXT PRIMARY KEY,
  name_en TEXT NOT NULL,
  name_zh TEXT,
  name_de TEXT,
  name_ja TEXT,
  description_en TEXT NOT NULL,
  description_zh TEXT,
  description_de TEXT,
  description_ja TEXT,
  price DECIMAL(10, 2) NOT NULL
);

Good for 2-3 locales with simple schemas. Becomes unwieldy with more languages.

JSONB Pattern (Flexible)

sql
-- JSONB translations: flexible but harder to query
CREATE TABLE pages (
  id TEXT PRIMARY KEY,
  slug TEXT NOT NULL,
  translations JSONB NOT NULL DEFAULT '{}',
  created_at TIMESTAMP DEFAULT NOW()
);

-- Example JSONB content
{
  "en": {
    "title": "About Us",
    "body": "<h1>Our Story</h1><p>...</p>"
  },
  "de": {
    "title": "Über Uns",
    "body": "<h1>Unsere Geschichte</h1><p>...</p>"
  },
  "zh": {
    "title": "关于我们",
    "body": "<h1>我们的故事</h1><p>...</p>"
  }
}

Performance Optimization

StrategyImpactImplementation
Composite index on (locale, slug)HighCREATE INDEX idx_locale_slug ON blog_post_translations(locale, blog_post_id)
Materialized view for active localesMediumPre-join common locale pairs
Redis cache for translation queriesHighCache after first DB lookup
Limit fallback depthMediumMax 2-level fallback (target → en)
Batch load all translationsHighSingle query for all locales, filter in app

Conclusion

The translation table pattern is optimal for most SaaS applications — it scales to any number of locales, supports fallback chains, and integrates well with ORMs. Use locale columns only for simple cases with 2-3 languages. Avoid EAV patterns entirely. Always index your locale + lookup key combinations.

For data modeling beyond i18n, see Data Modeling: Users, Organizations, and Permissions. SQL indexing best practices are covered in SQL Indexing Strategies for Web Applications, and Database Migrations: Strategies for Zero-Downtime provides deployment guidance for schema changes.