Internationalization
Native i18n support for collections and fields
Internationalization
@deessejs/collections has native, first-class support for internationalization. All user-facing strings can be translated without external i18n libraries.
Configuration
Set up supported locales in your configuration:
// config/database.ts
export const { collections } = defineConfig({
database: { url: process.env.DATABASE_URL! },
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr', 'es', 'de'],
fallbackLocale: 'en'
},
collections: [posts, users]
})Collection Translations
Collections support translated labels and descriptions:
// collections/posts.ts
export const posts = collection({
slug: 'posts',
// Translated labels
label: {
en: 'Blog Post',
fr: 'Article de Blog',
es: 'Artículo de Blog',
de: 'Blog-Beitrag'
},
// Plural labels
plural: {
en: 'Blog Posts',
fr: 'Articles de Blog',
es: 'Artículos de Blog',
de: 'Blog-Beiträge'
},
// Description
description: {
en: 'Blog posts and articles',
fr: 'Articles de blog',
es: 'Artículos de blog',
de: 'Blog-Artikel'
},
fields: { ... }
})Field Translations
Field Labels and Descriptions
fields: {
title: field({
type: text(),
// Translated label
label: {
en: 'Title',
fr: 'Titre',
es: 'Título',
de: 'Titel'
},
// Translated description
description: {
en: 'The title of your post',
fr: 'Le titre de votre article',
es: 'El título de tu entrada',
de: 'Der Titel Ihres Beitrags'
}
})
}Validation Messages
fields: {
email: field({
type: email(),
messages: {
required: {
en: 'Email is required',
fr: 'Email est requis',
es: 'El correo electrónico es obligatorio',
de: 'E-Mail ist erforderlich'
},
invalid: {
en: 'Invalid email format',
fr: 'Format d\'email invalide',
es: 'Formato de correo electrónico no válido',
de: 'Ungültiges E-Mail-Format'
}
}
})
}Modifiers with Translations
fields: {
username: field({
type: text({ min: 3, max: 20 }),
messages: {
tooShort: {
en: 'Username must be at least 3 characters',
fr: 'Le nom d\'utilisateur doit comporter au moins 3 caractères'
},
tooLong: {
en: 'Username must be at most 20 characters',
fr: 'Le nom d\'utilisateur ne peut pas dépasser 20 caractères'
}
}
})
}Enum Value Labels
Translate enum values for better UX:
fields: {
status: field({
type: enumField(['draft', 'published', 'archived']),
// Translated enum values
enumLabels: {
draft: {
en: 'Draft',
fr: 'Brouillon',
es: 'Borrador',
de: 'Entwurf'
},
published: {
en: 'Published',
fr: 'Publié',
es: 'Publicado',
de: 'Veröffentlicht'
},
archived: {
en: 'Archived',
fr: 'Archivé',
es: 'Archivado',
de: 'Archiviert'
}
}
})
}Using Translations
Set Locale for Operations
// Set locale for specific operation
const post = await collections.posts.create({
data: { title: 'Hello' },
locale: 'fr' // Error messages in French
})Get Translated Labels
// Get translated label for a field
const label = collections.posts.fields.title.getLabel('fr')
// Returns: 'Titre'
// Get all labels for a locale
const labels = collections.posts.getLabels('es')Runtime Error Messages
Validation errors automatically respect the current locale:
try {
await collections.users.create({
data: {
username: 'ab' // Too short
}
})
} catch (error) {
// If locale is 'fr':
// "Le nom d'utilisateur doit comporter au moins 3 caractères"
}Missing Translations
When a translation is missing, the framework falls back to the default locale:
label: {
en: 'Title', // Default
fr: 'Titre', // Available
// es: missing, falls back to 'en'
de: 'Titel' // Available
}
// Requesting 'es' returns 'Title' (default)Type Safety: All locales are type-checked. Only defined locales can be used in
defineConfig, providing compile-time safety.
Next Steps
- i18n Concepts - Understanding internal vs external i18n
- Examples - Real-world examples with i18n
- Plugins - Plugin system