Skip to content
Comvi

@comvi/svelte

View .md

The Svelte 5 binding (peer svelte ^5.3.0). Context is set with setI18nContext(); reactive members are readable stores.

  • Values: T, setI18nContext, useI18nInstance, useI18n, useI18nLoader, useI18nPlugins, createLocaleStore, createLoadingStore, createInitializingStore, createInitializedStore, createCacheRevisionStore, createDefaultParamsStore, createDefaultNamespaceStore, createI18n, loader, attachLoader, plugins, definePlugin, icuCompiler, devtools.
  • Types: SetI18nContextOptions, UseI18nReturn, UseI18nLoaderReturn, UseI18nPluginsReturn, SvelteTextTranslationFunction, SvelteRawTranslationFunction, TProps, ComponentMap, DevtoolsOptions, I18n, every @comvi/core type.

I18n is a type only; @comvi/core/tags is not re-exported.

src/lib/i18n.ts
import { createI18n, loader, plugins } from '@comvi/svelte';
export const i18n = createI18n({ locale: 'en', fallbackLocale: 'en' })
.use(loader({ en: () => import('./locales/en.json') }))
.use(plugins());
function setI18nContext(i18n: AnyI18nInstance, options?: SetI18nContextOptions): void
Option Type Default Meaning
autoInit boolean true Schedule init() in a microtask when the instance is neither initialized nor initializing; isInitializing reads true from the call on, so the first render is already inside the gate
onError (error: Error) => void none Called after the instance’s own onError when that init() rejects; with neither, the rejection is swallowed

Call it in the root component.

src/routes/+layout.svelte
<script lang="ts">
import { setI18nContext } from '@comvi/svelte';
import { i18n } from '$lib/i18n';
let { children } = $props();
setI18nContext(i18n, { onError: (error) => console.error(error) });
</script>
{@render children?.()}
function useI18nInstance(): WrapperI18nInstance

The i18n instance itself, not a store, callable during component initialization only. Throws with no setI18nContext ancestor.

function useI18n<D extends DefaultTranslationParams = {}, NS extends string | undefined = undefined>(ns?: NS): UseI18nReturn<D, NS>

Stores take $: $t('key'), $locale. The translation stores are memoized per instance and namespace; dir is rebuilt per call.

Returned Type Meaning
t Readable<NamespaceBoundTranslateFn<NS, D, string>> A store of the translate function
tRaw Readable<NamespaceBoundTranslateFn<NS, D, TranslationResult>> Rich-text half
locale Readable<string> Current locale
isLoading, isInitializing, isInitialized Readable<boolean> Loading flags
cacheRevision Readable<number> Cache and config revision
defaultParams Readable<DefaultParamsSnapshot<D>> Shallow snapshot
dir Readable<'ltr' | 'rtl'> Text direction
setLocale (locale: string) => Promise<void> setLocale
addMessages, setFallbackLocale, setDefaultParams, clearMessages, hasLocale, hasTranslation, on, reportError as on the instance Bound functions
getLoadedLocales, getActiveNamespaces, getDefaultNamespace, getMessagesCache () => … Snapshots
formatNumber, formatDate, formatCurrency, formatRelativeTime bound, trailing locale? Follow the locale in markup and $derived; a trailing locale overrides it
src/lib/Greeting.svelte
<script lang="ts">
import { useI18n } from '@comvi/svelte';
const { t, locale, setLocale } = useI18n();
</script>
<button onclick={() => setLocale($locale === 'en' ? 'de' : 'en')}>
{$t('greeting', { name: 'Alice' })}
</button>
function useI18nLoader(): UseI18nLoaderReturn // AcquiredLoaderApi
function useI18nPlugins(): UseI18nPluginsReturn // AcquiredPluginsApi

Context readers, not stores: callable during component initialization, returning plain bound functions. useI18nLoader() gives addActiveNamespace, addActiveNamespaces, removeActiveNamespace, removeActiveNamespaces, reloadMessages, onLoadError; useI18nPlugins() gives onMissingKey. Each throws, in development and production, when the instance lacks the capability. Cached per instance.

src/lib/Dashboard.svelte
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import { useI18n, useI18nLoader } from '@comvi/svelte';
const { t } = useI18n('dashboard');
const { addActiveNamespace, onLoadError } = useI18nLoader();
onMount(() => void addActiveNamespace('dashboard'));
onDestroy(onLoadError((locale, ns, error) => console.error(locale, ns, error)));
</script>
<h1>{$t('page.title')}</h1>
const T: Component<TProps>
type TProps = TBaseProps & Record<string, unknown>
type ComponentMap = Record<string, ComponentHandler>
// ComponentHandler and ComponentTarget are the shape of one ComponentMap entry; neither is exported
type ComponentHandler =
| string
| Component<any>
| ({ props?: Record<string, any> } & (
| { tag: ComponentTarget; component?: ComponentTarget }
| { component: ComponentTarget; tag?: ComponentTarget }
))
type ComponentTarget = string | Component<any>

Renders <tag> markup as real Svelte nodes through <svelte:element>; no HTML string is built.

Prop Type Meaning
i18nKey keyof TranslationKeys | PermissiveKey Required
params TranslationParams Interpolation params; any other prop is a param too and wins a collision
ns, locale, fallback string Overrides; with locale set the component ignores locale changes
markers boolean false turns the in-context editor’s markers off for this element when they cause a problem; it is then not editable in context
components ComponentMap Tag name to a tag name, a component (content arrives as its children snippet), or a { tag | component, props } config
children Snippet Rendered when the key is missing, no fallback was given and onMissingKey returned nothing

In a config whose target is a tag name, a props value of false omits the attribute.

src/lib/Rich.svelte
<script lang="ts">
import { T } from '@comvi/svelte';
import Highlight from './Highlight.svelte';
</script>
<T
i18nKey="rich.message"
components={{
bold: 'strong',
highlight: Highlight,
link: { tag: 'a', props: { href: '/about' } },
}}
/>
<T i18nKey="optional.feature">Default content</T>

The error is reported as reportError(error, { source: 'translation', tagName }) and the tag renders its own children.

Binding Guard Degrades to On the server
Svelte <svelte:boundary>, stays failed until remount the tag’s children throws
React error boundary on the render, client only the tag’s children throws
Vue try/catch on slot handlers <span> with the children caught
Solid try/catch on the call the children caught
function createLocaleStore(i18n: WrapperI18nInstance): Readable<string>
function createLoadingStore(i18n: WrapperI18nInstance): Readable<boolean>
function createInitializingStore(i18n: WrapperI18nInstance): Readable<boolean>
function createInitializedStore(i18n: WrapperI18nInstance): Readable<boolean>
function createCacheRevisionStore(i18n: WrapperI18nInstance): Readable<number>
function createDefaultParamsStore<D>(i18n: WrapperI18nInstance<D>): Writable<DefaultParamsSnapshot<D>>
function createDefaultNamespaceStore(i18n: WrapperI18nInstance): Writable<string>
Store Tracks
createLocaleStore localeChanged
createLoadingStore loadingStateChanged (isLoading)
createInitializingStore loadingStateChanged (isInitializing); also true while a scheduled auto-init is pending
createInitializedStore initialized and destroyed
createCacheRevisionStore every revision event except localeChanged and loadingStateChanged
createDefaultParamsStore configChanged; set and update call setDefaultParams, undefined clears
createDefaultNamespaceStore defaultNamespaceChanged; assigning calls setDefaultNamespace

All are memoized per instance. useI18n() builds and returns them.

  • Svelte: setup, loading, plurals, tags and typing for a Svelte app.
  • Insert values and markup: the <T> boundary and when to reach for @comvi/core/tags.
  • @comvi/core: the i18n instance, the capabilities and every type.