/ Foundations — 04
Languages as subpath imports.
No mega-bundle of translations. Each locale is its own subpath import at ≤ 1 KB brotli, lazy-loaded on demand. RTL inversion lives in the state machine, not in CSS.
Phase 1 locales
- English
- 日本語
- 简体中文
- 繁體中文
- 한국어
- Español
- Français
- Deutsch
- العربية
- עברית
Switching at runtime
Wrap your app once, then switch the imported locale bundle at any time. Components below re-read messages on every change.
<script lang="ts">
import { LocaleProvider } from '@kumiki/components';
import * as ja from '@kumiki/locale/ja';
import * as en from '@kumiki/locale/en';
let active = $state<'ja' | 'en'>('ja');
const bundle = $derived(active === 'ja' ? ja : en);
</script>
<button onclick={() => (active = active === 'ja' ? 'en' : 'ja')}>
{active.toUpperCase()}
</button>
<LocaleProvider.Root locale={active} messages={bundle.messages} dir={bundle.direction}>
{@render children()}
</LocaleProvider.Root> RTL is not an afterthought
Reading direction is propagated from LocaleProvider through context. Direction-
sensitive keymaps (Tabs ArrowRight, Slider, RadioGroup) read direction from the
machine context — the controller doesn't know about RTL.
The direction toggle on every component detail page lets you preview RTL for any locale, without switching languages. Use it to verify your styling.
What's localised
The @kumiki/locale bundles cover:
combobox: listbox label, "no results", clear button.dialog: close-button label.tabs: default tablist label.formField: required marker, "required" / "type mismatch" errors.
The Validation messages composed by Form Field can be replaced wholesale or extended via Standard Schema — no per-validator adapters required.