@fyit/crouton-i18n
v0.1.0
Published
i18n layer extending nuxt-crouton for multi-language support
Maintainers
Readme
@fyit/crouton-i18n
Multi-language support layer extending @fyit/crouton for FYIT scaffolded collections.
Features
- 🌍 Multi-language input components
- 🔄 Auto-sync with English as primary language
- 📝 Team-specific translation overrides
- 🎯 Built-in support for EN, NL, FR
- ⚡ Inherits all CRUD features from base layer
- 🗄️ Database-backed translations with JSON fallback
Installation
# Install both base and addon
pnpm add @fyit/crouton @fyit/crouton-i18nQuick Start (Using Generator)
The easiest way to set up i18n is via the collection generator. When you generate a collection with translations enabled, everything is configured automatically.
1. Create a config file with translations
// crouton.config.js
export default {
collections: [
{ name: 'products', fieldsFile: './schemas/products.json' }
],
targets: [
{ layer: 'shop', collections: ['products'] }
],
translations: {
collections: {
products: ['name', 'description'] // Fields to translate
}
},
dialect: 'sqlite'
}2. Run the generator
crouton-generate --config ./crouton.config.jsWhat the generator does automatically:
- ✅ Creates
translations-ui.tsschema inserver/database/schema/ - ✅ Exports the schema in
schema/index.ts - ✅ Generates database migration
- ✅ Adds
nuxt-crouton-i18nto layer extends - ✅ Creates
i18n/locales/*.jsonfiles with starter content - ✅ Configures i18n in the layer's
nuxt.config.ts - ✅ Registers
translationsUicollection inapp.config.ts
3. Seed translations (optional)
Import your JSON locale files into the database:
# Preview what will be seeded
crouton-generate seed-translations --dry-run
# Seed all translations
crouton-generate seed-translations
# Seed from specific layer
crouton-generate seed-translations --layer bookings
# Generate SQL for manual insertion
crouton-generate seed-translations --sql > seed.sqlManual Configuration
If you're not using the generator, follow these steps:
1. Add layers to nuxt.config.ts
export default defineNuxtConfig({
extends: [
'@fyit/crouton', // Base layer (required)
'@fyit/crouton-i18n' // i18n addon
]
})2. Copy the schema file
Create server/database/schema/translations-ui.ts:
import { nanoid } from 'nanoid'
import { sqliteTable, text, integer, unique } from 'drizzle-orm/sqlite-core'
export const translationsUi = sqliteTable('translations_ui', {
id: text('id').primaryKey().$default(() => nanoid()),
userId: text('user_id').notNull(),
teamId: text('team_id'),
namespace: text('namespace').notNull().default('ui'),
keyPath: text('key_path').notNull(),
category: text('category').notNull(),
values: text('values', { mode: 'json' }).$type<Record<string, string>>().notNull(),
description: text('description'),
isOverrideable: integer('is_overrideable', { mode: 'boolean' }).notNull().default(true),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$default(() => new Date()),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().$onUpdate(() => new Date())
}, (table) => ({
uniqueTeamNamespaceKey: unique().on(table.teamId, table.namespace, table.keyPath)
}))Export it in server/database/schema/index.ts:
export * from './translations-ui'3. Run migration
pnpm db:generate4. Register the collection
In app/app.config.ts:
import { translationsUiConfig } from '@fyit/crouton-i18n/app/composables/useTranslationsUi'
export default defineAppConfig({
croutonCollections: {
translationsUi: translationsUiConfig,
// ... other collections
}
})5. Create locale files
Create layers/[your-layer]/i18n/locales/en.json:
{
"yourLayer": {
"collections": {
"products": { "title": "Products" }
}
}
}Configure in your layer's nuxt.config.ts:
export default defineNuxtConfig({
i18n: {
locales: [
{ code: 'en', file: 'en.json' },
{ code: 'nl', file: 'nl.json' },
{ code: 'fr', file: 'fr.json' }
],
langDir: './locales' // NOT './i18n/locales'!
}
})⚠️ Important: Use
langDir: './locales'not'./i18n/locales'. The@nuxtjs/i18nmodule usesrestructureDir: 'i18n'by default, so langDir is relative to thei18n/directory.
Components
CroutonI18nInput
Multi-language input for forms:
<CroutonI18nInput
v-model="state.translations"
:fields="['name', 'description']"
:default-values="{
name: state.name,
description: state.description
}"
@update:english="(data) => { state[data.field] = data.value }"
label="Translations"
/>CroutonI18nDisplay
Display translated content:
<CroutonI18nDisplay
:translations="item.translations"
:field="'name'"
:fallback="item.name"
/>CroutonI18nLanguageSwitcher
Switch between available languages:
<CroutonI18nLanguageSwitcher />Composables
useT()
Translation function with team override support:
const { t, tContent, locale } = useT()
// UI translations (from database with JSON fallback)
const label = t('products.name')
// Entity content translations
const productName = tContent(product, 'name')The useT() composable:
- First checks team-specific overrides in the database
- Falls back to system translations in the database
- Falls back to JSON locale files
- Returns
[key]if no translation found
useEntityTranslations()
Manage entity-specific translations:
const { getTranslation, setTranslation } = useEntityTranslations()
const translated = getTranslation(entity.translations, 'name', 'nl')useTranslationsUi()
Access the translations collection config:
const { schema, columns, config } = useTranslationsUi()Translation Architecture
┌─────────────────────────────────────────────────┐
│ useT() │
├─────────────────────────────────────────────────┤
│ 1. Team Override (DB) │
│ └─ translations_ui WHERE teamId = :team │
│ │
│ 2. System Translation (DB) │
│ └─ translations_ui WHERE teamId IS NULL │
│ │
│ 3. JSON Locale Files (fallback) │
│ └─ layers/*/i18n/locales/*.json │
│ │
│ 4. Key as fallback │
│ └─ Returns [keyPath] if nothing found │
└─────────────────────────────────────────────────┘Seeding Translations
The seed CLI imports JSON locale files into the database as system-level translations.
Quick Start
# From your project root
pnpm crouton:i18n:seed --dry-run # Preview what will be seeded
pnpm crouton:i18n:seed --sql > seed.sql # Generate SQL
pnpm crouton:i18n:seed # Seed via APILocale Sources
The seed CLI discovers and reads from multiple locale sources (in order, later sources can override):
- nuxt-crouton-i18n/locales/ - i18n admin UI strings
- nuxt-crouton-supersaas/i18n/locales/ - App-level common strings
- [app]/layers/*/i18n/locales/ - Domain layer strings
- [app]/i18n/locales/ - App-level overrides
Options
| Option | Description |
|--------|-------------|
| --dry-run | Preview translations without seeding |
| --sql | Output SQL statements for direct database use |
| --api-url <url> | API endpoint URL (default: http://localhost:3000) |
| --team-id <id> | Seed to specific team (default: null = system) |
| --force | Overwrite existing translations |
| --source <dir> | Seed from specific directory only |
| -h, --help | Show help message |
SQL Output
For direct database insertion without running the Nuxt server:
# Generate SQL
pnpm crouton:i18n:seed --sql > seed.sql
# Apply to SQLite database
sqlite3 .data/hub/d1/your-db.sqlite < seed.sqlSeeding via API
For dynamic environments or when the server is running:
# Default (localhost:3000)
pnpm crouton:i18n:seed
# Custom API URL
pnpm crouton:i18n:seed --api-url https://your-app.comWhat Gets Seeded
Each translation is seeded with:
teamId: null- System-level translationuserId: 'system'- Indicates seeded by CLIisOverrideable: true- Teams can overridenamespace: 'ui'- Standard UI namespacecategory- First segment of key path (e.g., "common" from "common.save")values- JSON object with all locale values
Example Workflow
# 1. Preview what will be seeded
pnpm crouton:i18n:seed --dry-run
# 2. Generate and review SQL
pnpm crouton:i18n:seed --sql > seed.sql
cat seed.sql | head -50
# 3. Apply to database
sqlite3 .data/hub/d1/your-db.sqlite < seed.sql
# 4. Verify in app
# Navigate to /admin/translations to see seeded translationsCustomizing Locales
Override default locales in your nuxt.config:
export default defineNuxtConfig({
extends: [
'@fyit/crouton',
'@fyit/crouton-i18n'
],
i18n: {
locales: [
{ code: 'en', name: 'English', file: 'en.json' },
{ code: 'es', name: 'Español', file: 'es.json' },
{ code: 'de', name: 'Deutsch', file: 'de.json' }
]
}
})Layer Architecture
@fyit/crouton (base CRUD - required)
+
@fyit/crouton-i18n (addon - this layer)Troubleshooting
"Cannot find module" for locale files
Check that langDir is relative to i18n/ directory:
// ❌ Wrong
langDir: './i18n/locales'
// ✅ Correct
langDir: './locales'Translations not appearing in dashboard
Ensure the collection is registered in app.config.ts:
import { translationsUiConfig } from '@fyit/crouton-i18n/app/composables/useTranslationsUi'
croutonCollections: {
translationsUi: translationsUiConfig
}Database table not created
Run migrations:
pnpm db:generate
# Restart dev server to applyuseT() returns key instead of translation
- Check that the translation exists in the database or JSON files
- Verify the key path matches exactly (case-sensitive)
- Check browser console for API errors
Migration from Previous Versions
Component prefix changed: Translations* → CroutonI18n*
# Find and replace in your project
sed -i '' 's/<Translations/<CroutonI18n/g' **/*.vue
sed -i '' 's/<\/Translations/<\/CroutonI18n/g' **/*.vueLicense
MIT
