npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fyit/crouton-i18n

v0.1.0

Published

i18n layer extending nuxt-crouton for multi-language support

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-i18n

Quick 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.js

What the generator does automatically:

  1. ✅ Creates translations-ui.ts schema in server/database/schema/
  2. ✅ Exports the schema in schema/index.ts
  3. ✅ Generates database migration
  4. ✅ Adds nuxt-crouton-i18n to layer extends
  5. ✅ Creates i18n/locales/*.json files with starter content
  6. ✅ Configures i18n in the layer's nuxt.config.ts
  7. ✅ Registers translationsUi collection in app.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.sql

Manual 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:generate

4. 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/i18n module uses restructureDir: 'i18n' by default, so langDir is relative to the i18n/ 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:

  1. First checks team-specific overrides in the database
  2. Falls back to system translations in the database
  3. Falls back to JSON locale files
  4. 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 API

Locale Sources

The seed CLI discovers and reads from multiple locale sources (in order, later sources can override):

  1. nuxt-crouton-i18n/locales/ - i18n admin UI strings
  2. nuxt-crouton-supersaas/i18n/locales/ - App-level common strings
  3. [app]/layers/*/i18n/locales/ - Domain layer strings
  4. [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.sql

Seeding 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.com

What Gets Seeded

Each translation is seeded with:

  • teamId: null - System-level translation
  • userId: 'system' - Indicates seeded by CLI
  • isOverrideable: true - Teams can override
  • namespace: 'ui' - Standard UI namespace
  • category - 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 translations

Customizing 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 apply

useT() returns key instead of translation

  1. Check that the translation exists in the database or JSON files
  2. Verify the key path matches exactly (case-sensitive)
  3. 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' **/*.vue

License

MIT