raisindb-translations
RaisinDB Translations
How Translations Work
RaisinDB uses a file-overlay system for multi-language content:
- Base content lives in
.node.yamland serves as the default language (typically English). - Translation overlays live in
.node.{locale}.yamlfiles alongside the base file (e.g.,.node.fr.yaml,.node.de.yaml). - Only fields marked
translatable: truein their archetype or element type definition appear in translation files. - At query time, the server merges the base content with the requested locale's overlay.
content/launchpad/home/
.node.yaml # Base (English)
.node.fr.yaml # French overlay
.node.de.yaml # German overlay
Mark Fields as Translatable
Add translatable: true to fields that need translation in archetypes and element types. Fields without this flag must not appear in translation files.
Archetype (archetypes/landing-page.yaml):
fields:
- $type: TextField
name: title
required: true
translatable: true # Translated
- $type: TextField
name: slug
required: true # NOT translated — same across locales
- $type: TextField
name: description
translatable: true # Translated
- $type: SectionField
name: content
allowed_element_types: [launchpad:Hero, launchpad:TextBlock]
Element type (elementtypes/hero.yaml):
fields:
- { $type: TextField, name: headline, translatable: true }
- { $type: TextField, name: subheadline, translatable: true }
- { $type: TextField, name: cta_text, translatable: true }
- { $type: ReferenceField, name: cta_link } # NOT translated (references are structural)
Translation File Format
Translation files contain only translated properties. They omit node_type, archetype, and non-translatable fields. Section elements are matched by uuid.
Base file (.node.yaml):
node_type: launchpad:Page
archetype: launchpad:LandingPage
properties:
title: Welcome to Launchpad
slug: home
description: Your gateway to launching amazing projects
content:
- uuid: hero-1
element_type: launchpad:Hero
headline: Launch Your Vision
subheadline: Build, deploy, and scale your ideas with Launchpad
cta_text: Get Started
cta_link:
raisin:ref: /launchpad/contact
raisin:workspace: launchpad
- uuid: intro-1
element_type: launchpad:TextBlock
heading: Why Launchpad?
content: |
Launchpad is your all-in-one platform for turning ideas into reality.
French (.node.fr.yaml):
title: Bienvenue sur Launchpad
description: Votre passerelle pour lancer des projets exceptionnels
content:
- uuid: hero-1
headline: Lancez votre vision
subheadline: Construisez, deployez et faites evoluer vos idees avec Launchpad
cta_text: Commencer
- uuid: intro-1
heading: Pourquoi Launchpad ?
content: |
Launchpad est votre plateforme tout-en-un pour concretiser vos idees.
German (.node.de.yaml):
title: Willkommen bei Launchpad
description: Ihr Tor zum Start grossartiger Projekte
content:
- uuid: hero-1
headline: Starten Sie Ihre Vision
subheadline: Bauen, deployen und skalieren Sie Ihre Ideen mit Launchpad
cta_text: Jetzt starten
- uuid: intro-1
heading: Warum Launchpad?
content: |
Launchpad ist Ihre All-in-One-Plattform, um Ideen in die Realitat umzusetzen.
Key rules:
- No
node_typeorarchetype-- those belong only in the base file. - No non-translatable fields (
slug,cta_link, etc.). uuidmust match the base file exactly;element_typecan be omitted.
Repeatable CompositeField Translations
When a CompositeField has multiple: true and contains sub-fields marked translatable: true, each item in the array must have a uuid — in both the base content and translation overlays. This allows the server to merge only translatable fields per-item instead of replacing the entire array (which would lose non-translatable fields).
Element type (elementtypes/feature-grid.yaml):
fields:
- $type: TextField
name: heading
translatable: true
- $type: CompositeField
name: features
multiple: true
fields:
- { $type: TextField, name: title, required: true, translatable: true }
- { $type: TextField, name: description, required: true, translatable: true }
- { $type: TextField, name: icon } # NOT translatable
Base content (.node.yaml) — each composite item needs a uuid:
content:
- uuid: features-1
element_type: launchpad:FeatureGrid
heading: Features
features:
- uuid: feat-fast
icon: zap
title: Fast Development
description: Build and iterate quickly
- uuid: feat-scale
icon: trending-up
title: Scalable
description: Grows with your needs
Translation overlay (.node.fr.yaml) — only translatable fields + uuid:
content:
- uuid: features-1
heading: Fonctionnalites
features:
- uuid: feat-fast
title: Developpement rapide
description: Construisez et iterez rapidement
- uuid: feat-scale
title: Evolutif
description: Grandit avec vos besoins
The icon field is NOT in the translation because it is not translatable. The server preserves it from the base content during merge. Without UUIDs on composite items, the validator will reject the content with COMPOSITE_MISSING_UUID.
Rule: If a repeatable CompositeField has ANY sub-field with translatable: true, every item must have a unique uuid.
Frontend Locale Store
Track the active language and generate SQL clauses. The key function is localeClause():
// lib/stores/locale.ts
export type Locale = 'en' | 'de' | 'fr';
export const locale = writable<Locale>(getInitialLocale());
export function getCurrentLocale(): Locale {
if (browser) {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored === 'de' || stored === 'fr') return stored;
}
return 'en';
}
/**
* Returns a SQL AND clause for the current locale.
* English (default) returns empty string — no filtering needed.
*/
export function localeClause(): string {
const current = getCurrentLocale();
if (current === 'en') return '';
return `AND locale = '${current}'`;
}
The default locale (English) returns an empty string so the base content is used without filtering.
Querying with Locale
Append localeClause() to any SQL query that returns translatable content:
import { localeClause } from '$lib/stores/locale';
export async function getPageByPath(path: string): Promise<PageNode | null> {
const sql = `
SELECT id, path, name, node_type, archetype, properties
FROM ${WORKSPACE_NAME}
WHERE path = $1 ${localeClause()}
LIMIT 1
`;
return queryOne<PageNode>(sql, [nodePath]);
}
export async function getNavigation(): Promise<NavItem[]> {
const sql = `
SELECT id, path, name, node_type, properties
FROM ${WORKSPACE_NAME}
WHERE CHILD_OF('/${WORKSPACE_NAME}')
AND node_type = 'launchpad:Page'
${localeClause()}
`;
return query<NavItem>(sql);
}
The server merges the locale overlay onto the base content before returning results.
Supported Locales
RaisinDB uses BCP 47 language codes: en, fr, de, es, pt-BR, zh-Hans, ja, ko, ar, it, and any valid BCP 47 code. Add a new locale by creating .node.{locale}.yaml files alongside your base content.
Validation
MANDATORY — run after every translation file change:
npm run validate
Common errors:
| Error | Cause | Fix |
|---|---|---|
TRANSLATION_FIELD_NOT_TRANSLATABLE |
Translation includes a non-translatable field | Remove the field or add translatable: true to the type definition |
TRANSLATION_MISSING_UUID |
Element uuid has no match in the base file | Ensure uuid matches an element in .node.yaml |
TRANSLATION_INVALID_LOCALE |
Invalid BCP 47 code in filename | Use a valid locale code (e.g., fr, de, pt-BR) |
COMPOSITE_MISSING_UUID |
Repeatable composite with translatable sub-fields has an item without uuid |
Add a unique uuid to each item in the composite array |
COMPOSITE_DUPLICATE_UUID |
Two items in the same composite array share the same uuid |
Ensure each item has a unique uuid value |
More from maravilla-labs/raisindb
raisindb-sql
SQL syntax for querying RaisinDB workspaces: CRUD, JSONB properties, hierarchy queries, graph relations, full-text search. Use when writing queries in frontend or server-side functions.
3raisindb-auth
Authentication flows for RaisinDB apps: anonymous access, login, register, session management, auth state listeners. Use when adding authentication to your frontend.
3raisindb-file-uploads
Upload, store, and display files using the raisin:Asset system. Covers single/batch uploads, progress tracking, signed URLs, and thumbnails. Use when adding file handling to your app.
3raisindb-frontend-react
Build a React Router frontend for your RaisinDB app with path-based routing, archetype-to-component mapping, and SSR-to-WebSocket upgrade. Use when creating a React frontend.
3raisindb-overview
Core concepts of RaisinDB content-driven applications. Use when building any RaisinDB app. Teaches: path-as-URL routing, archetype-to-component mapping, content modeling, project structure.
3raisindb-access-control
Roles, permissions, groups, and row-level security for RaisinDB. Configure anonymous access, custom roles, and fine-grained permissions in your package. Use when setting up authorization.
3