# graphile-i18n

<p align="center" width="100%">
  <img height="250" src="https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg" />
</p>

<p align="center" width="100%">
  <a href="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml">
    <img height="20" src="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml/badge.svg" />
  </a>
   <a href="https://github.com/constructive-io/constructive/blob/main/LICENSE"><img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a>
   <a href="https://www.npmjs.com/package/graphile-i18n"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/constructive?filename=graphile%2Fgraphile-i18n%2Fpackage.json"/></a>
</p>

PostGraphile v5 i18n plugin — language-aware fields sourced from `@i18n` translation tables with Accept-Language negotiation and configurable fallback chains.

## Overview

`graphile-i18n` auto-discovers tables tagged with the `@i18n` smart comment, finds the companion translation table, and injects a `localeStrings` field on the base type. The field resolves the best-matching translation row based on the GraphQL context's language codes, falling back to the base table's own values when no translation exists.

## Usage

```typescript
import { I18nPreset } from 'graphile-i18n';

const preset = {
  extends: [
    I18nPreset(),
  ],
};
```

### Custom configuration

```typescript
import { I18nPreset } from 'graphile-i18n';

const preset = {
  extends: [
    I18nPreset({
      defaultLanguages: ['en', 'es'],
      langCodeColumn: 'lang_code',
      allowedTypes: ['text', 'citext'],
    }),
  ],
};
```

### Accept-Language middleware

For production use with Express/PostGraphile, add the Accept-Language context builder:

```typescript
import { I18nPreset, makeI18nContext } from 'graphile-i18n';

const preset = {
  extends: [I18nPreset()],
  grafast: {
    context: makeI18nContext({
      supportedLanguages: ['en', 'es', 'fr'],
    }),
  },
};
```

## Database Setup

Tag the base table with a `@i18n` smart comment pointing to its translation table:

```sql
COMMENT ON TABLE app_public.posts IS E'@i18n posts_translations';
```

The translation table must have:
- A FK column referencing the base table's PK (convention: `{table}_id`)
- A `lang_code` text column (configurable)
- A `UNIQUE(fk_column, lang_code)` constraint
- One or more `text`/`citext` columns matching translatable base columns

```sql
CREATE TABLE app_public.posts_translations (
  id serial PRIMARY KEY,
  post_id int NOT NULL REFERENCES app_public.posts(id) ON DELETE CASCADE,
  lang_code text NOT NULL,
  title text NOT NULL,
  body text,
  UNIQUE (post_id, lang_code)
);
```

## GraphQL API

Once configured, every tagged table gets a `localeStrings` field:

```graphql
query {
  allPosts {
    nodes {
      title          # original base value
      localeStrings {
        langCode     # matched language code (null if no translation)
        title        # translated or base fallback
        body         # translated or base fallback
      }
    }
  }
}
```

### Language selection

Pass `langCodes` in the GraphQL context (automatically handled by `makeI18nContext` or set manually in tests):

```graphql
# With context: { langCodes: ['es'] }
query {
  postByRowId(rowId: 1) {
    localeStrings {
      langCode  # "es"
      title     # "Hola Mundo"
    }
  }
}
```

When the requested language has no translation, the plugin falls back through the `langCodes` array. If no match is found, base table values are returned with `langCode: null`.

## Features

- **Smart tag discovery**: Auto-detects `@i18n` tagged tables and their translation companions
- **Grafast-native resolution**: Uses `lambda` + `object` steps for proper v5 execution
- **Accept-Language negotiation**: Built-in middleware parses headers and injects context
- **Fallback chain**: Tries each language in order, falls back to base table values
- **Convention-based FK**: Discovers FK by `{table}_id` convention or type matching
- **Type-safe**: Full TypeScript types for options, registry, and field mappings

## Options

| Option | Default | Description |
|--------|---------|-------------|
| `langCodeColumn` | `'lang_code'` | Column name on translation table storing the language code |
| `langCodeGqlField` | `'langCode'` | GraphQL field name for the language code in the locale object |
| `allowedTypes` | `['text', 'citext']` | PostgreSQL column types eligible for translation |
| `defaultLanguages` | `['en']` | Fallback languages when no context is provided |

## Constructive Integration

When used with the Constructive framework, the `DataI18n` node type automates translation table creation and optionally composes with `SearchFullText` for multilingual full-text search:

```json
{
  "nodes": [
    {
      "$type": "DataI18n",
      "data": {
        "fields": ["name", "description"],
        "search": {
          "field_name": "search",
          "source_fields": [
            { "field": "name", "weight": "A" },
            { "field": "description", "weight": "B" }
          ]
        }
      }
    }
  ]
}
```

When `search` is provided, the tsvector is created on the **translations table** with dynamic per-row language stemming — each row is stemmed using its own `lang_code` value (e.g., `'spanish'` → Spanish stemmer, `'french'` → French stemmer). PostgreSQL ships with 30+ built-in text search configurations, so multilingual search works out of the box with no per-language configuration.

The `i18n_module` provides app-level configuration via `app_settings_i18n` with supported languages, default language, and fallback chain — all manageable via GraphQL mutations.

For the full i18n guide (blueprint patterns, ORM queries, SQL search examples), see the [constructive-sdk-i18n skill](https://github.com/constructive-io/constructive-skills/tree/main/.agents/skills/constructive-sdk-i18n).

---

## Education and Tutorials

 1. 🚀 [Quickstart: Getting Up and Running](https://constructive.io/learn/quickstart)
Get started with modular databases in minutes. Install prerequisites and deploy your first module.

 2. 📦 [Modular PostgreSQL Development with Database Packages](https://constructive.io/learn/modular-postgres)
Learn to organize PostgreSQL projects with pgpm workspaces and reusable database modules.

 3. ✏️ [Authoring Database Changes](https://constructive.io/learn/authoring-database-changes)
Master the workflow for adding, organizing, and managing database changes with pgpm.

 4. 🧪 [End-to-End PostgreSQL Testing with TypeScript](https://constructive.io/learn/e2e-postgres-testing)
Master end-to-end PostgreSQL testing with ephemeral databases, RLS testing, and CI/CD automation.

 5. ⚡ [Supabase Testing](https://constructive.io/learn/supabase)
Use TypeScript-first tools to test Supabase projects with realistic RLS, policies, and auth contexts.

 6. 💧 [Drizzle ORM Testing](https://constructive.io/learn/drizzle-testing)
Run full-stack tests with Drizzle ORM, including database setup, teardown, and RLS enforcement.

 7. 🔧 [Troubleshooting](https://constructive.io/learn/troubleshooting)
Common issues and solutions for pgpm, PostgreSQL, and testing.

## Related Constructive Tooling

### 📦 Package Management

* [pgpm](https://github.com/constructive-io/constructive/tree/main/pgpm/pgpm): **🖥️ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.

### 🧪 Testing

* [pgsql-test](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-test): **📊 Isolated testing environments** with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation.
* [pglite-test](https://github.com/constructive-io/constructive/tree/main/postgres/pglite-test): **🪶 Drop-in pgsql-test replacement backed by PGlite** — in-process Postgres, no server required, instance-per-suite isolation.
* [pgsql-seed](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-seed): **🌱 PostgreSQL seeding utilities** for CSV, JSON, SQL data loading, and pgpm deployment.
* [supabase-test](https://github.com/constructive-io/constructive/tree/main/postgres/supabase-test): **🧪 Supabase-native test harness** preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
* [graphile-test](https://github.com/constructive-io/constructive/tree/main/graphile/graphile-test): **🔐 Authentication mocking** for Graphile-focused test helpers and emulating row-level security contexts.
* [pg-query-context](https://github.com/constructive-io/constructive/tree/main/postgres/pg-query-context): **🔒 Session context injection** to add session-local context (e.g., `SET LOCAL`) into queries—ideal for setting `role`, `jwt.claims`, and other session settings.

### 🧠 Parsing & AST

* [pgsql-parser](https://www.npmjs.com/package/pgsql-parser): **🔄 SQL conversion engine** that interprets and converts PostgreSQL syntax.
* [libpg-query-node](https://www.npmjs.com/package/libpg-query): **🌉 Node.js bindings** for `libpg_query`, converting SQL into parse trees.
* [pg-proto-parser](https://www.npmjs.com/package/pg-proto-parser): **📦 Protobuf parser** for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums.
* [@pgsql/enums](https://www.npmjs.com/package/@pgsql/enums): **🏷️ TypeScript enums** for PostgreSQL AST for safe and ergonomic parsing logic.
* [@pgsql/types](https://www.npmjs.com/package/@pgsql/types): **📝 Type definitions** for PostgreSQL AST nodes in TypeScript.
* [@pgsql/utils](https://www.npmjs.com/package/@pgsql/utils): **🛠️ AST utilities** for constructing and transforming PostgreSQL syntax trees.

### 📚 Documentation & Skills

* [constructive-skills](https://github.com/constructive-io/constructive-skills): **📖 Platform documentation and AI agent skills** — feature catalog, blueprint reference, SDK guides (i18n, billing, limits, events, uploads, security, entities, search, AI), and deployment guides.

Install skills for AI coding agents:

```bash
# All platform skills (security, blueprints, codegen, billing, etc.)
npx skills add constructive-io/constructive-skills

# Individual repo skills (pgpm, testing, CLI, search, etc.)
npx skills add https://github.com/constructive-io/constructive --skill pgpm
npx skills add https://github.com/constructive-io/constructive --skill constructive-testing
```

## Credits

**🛠 Built by the [Constructive](https://constructive.io) team — creators of modular Postgres tooling for secure, composable backends. If you like our work, contribute on [GitHub](https://github.com/constructive-io).**

## Disclaimer

AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.

No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
