Skip to content

Internationalization (i18n)

ElyOS uses a database-backed i18n system. Translations are stored in the database (not static JSON files) and loaded at runtime. This allows translations to be edited through an admin interface without restarting.

Default languages: hu (Hungarian) and en (English).

Translations are organized into namespaces. Each namespace corresponds to a functional area:

platform:common # Common texts (save, cancel, etc.)
platform:auth # Authentication
platform:settings # Settings application
platform:users # Users application
platform:chat # Chat application
...

The simplest way to display translations is the T component:

<script lang="ts">
import T from '$lib/components/i18n/T.svelte';
</script>
<!-- Simple translation -->
<T key="platform:common.save" />
<!-- With parameters -->
<T key="platform:common.welcome" params={{ name: 'User' }} />
<!-- Fallback text -->
<T key="platform:common.unknown_key" fallback="Unknown" />
import { getI18nService } from '$lib/i18n';
const i18n = getI18nService();
// Get translation
const text = i18n.t('platform:common.save');
// With parameters
const welcome = i18n.t('platform:common.welcome', { name: 'User' });
// Current locale
const locale = i18n.locale; // 'hu' | 'en'
import { setLocale } from '$lib/i18n/preference.client';
// Switch locale (saves to cookie, refreshes page)
await setLocale('en');

The LocaleSwitcher component handles this automatically:

<script lang="ts">
import LocaleSwitcher from '$lib/components/i18n/LocaleSwitcher.svelte';
</script>
<LocaleSwitcher />

On the server, locals.locale contains the current language:

export const myAction = command(schema, async () => {
const { locals } = getRequestEvent();
const locale = locals.locale; // 'hu' | 'en'
// ...
});

On the client, from the i18n store:

<script lang="ts">
import { getI18nStore } from '$lib/i18n/store.svelte';
const i18nStore = getI18nStore();
const locale = $derived(i18nStore.locale);
</script>

If you’re developing a new feature in the core system, new translations should be placed in seed files so they persist across database reinitialization.

Translations are organized by functional area:

Seed FilePurpose
translations_common.sqlCommon texts (buttons, statuses, error messages)
translations_settings.sqlSettings application translations
translations_user.sqlUsers application translations
translations_log.sqlLog application translations
translations_desktop.sqlDesktop environment (Window, Taskbar, StartMenu)
translations_auth.sqlAuth pages (login, registration)
translations_notifications.sqlNotification system translations
translations_plugin_manager.sqlPlugin Manager translations

Open the appropriate file in packages/database/src/seeds/sql/platform/:

-- translations_myapp.sql
-- =============================================================================
-- MYAPP NAMESPACE - My Application Translations
-- =============================================================================
-- Hungarian (hu) translations
INSERT INTO platform.translations (locale, namespace, key, value) VALUES
('hu', 'myapp', 'title', 'Az én alkalmazásom'),
('hu', 'myapp', 'description', 'Ez az alkalmazás leírása'),
('hu', 'myapp', 'buttons.create', 'Új létrehozása'),
('hu', 'myapp', 'list.empty', 'Nincs megjeleníthető elem')
ON CONFLICT (locale, namespace, key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW();
-- English (en) translations
INSERT INTO platform.translations (locale, namespace, key, value) VALUES
('en', 'myapp', 'title', 'My Application'),
('en', 'myapp', 'description', 'This is the application description'),
('en', 'myapp', 'buttons.create', 'Create New'),
('en', 'myapp', 'list.empty', 'No items to display')
ON CONFLICT (locale, namespace, key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW();

If you created a new seed file, register it in packages/database/src/seeds/config.ts:

export const seedConfig: Record<string, SeedDefinition> = {
// ... existing seeds
translations_myapp: {
file: 'platform/translations_myapp.sql',
dependsOn: ['locales'],
description: 'My App translations'
}
};
Terminál
# Updates only new translations (idempotent)
bun db:seed
# Or just your seed
bun db:seed --no-truncate --only=translations_myapp
<script lang="ts">
import T from '$lib/components/i18n/T.svelte';
</script>
<h1><T key="myapp.title" /></h1>
<p><T key="myapp.description" /></p>
<button>
<T key="myapp.buttons.create" />
</button>
  • Namespace: platform:[app-name] or platform:common
  • Key: snake_case or camelCase, dot-separated hierarchy
  • Examples:
    • platform:common.save → “Save”
    • platform:common.cancel → “Cancel”
    • platform:settings.appearance.title → “Appearance”
    • platform:users.list.empty → “No users”

The I18nProvider component initializes the i18n context in the layout. Usually included in the root layout — no need to add manually to applications.

VariableDefaultDescription
SUPPORTED_LOCALEShu,enComma-separated supported languages
DEFAULT_LOCALEhuDefault language