@kystverket/sprak-react
v0.0.14
Published
React translation library powered by sprak
Downloads
1,986
Readme
@kystverket/sprak-react
React translation library powered by sprak. Provides components, hooks, and a Vite plugin for adding type-safe i18n to React applications.
Installation
npm install @kystverket/sprak-reactPeer Dependencies
react>= 18.0.0react-dom>= 18.0.0
Quick Start
import { SprakProvider, NamespaceProvider, useTranslation } from '@kystverket/sprak-react';
import translations from 'virtual:sprak-translations'; // via Vite plugin
function App() {
return (
<SprakProvider locale="en">
<NamespaceProvider ns="common" translations={translations.common}>
<Greeting />
</NamespaceProvider>
</SprakProvider>
);
}
function Greeting() {
const { t } = useTranslation('common');
return <h1>{t('hello', { name: 'World' })}</h1>;
}Translations are supplied per-namespace via <NamespaceProvider>. Use the Vite plugin to bundle all translations at build time — they are loaded from your .sprak.json config file and injected as a virtual module.
Vite Plugin
The sprakPlugin bundles translations at build time and exposes them via virtual:sprak-translations. It also enables HMR — translation files are watched and hot-reloaded during development.
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { sprakPlugin } from '@kystverket/sprak-react/plugin';
export default defineConfig({
plugins: [react(), sprakPlugin()],
});Options
| Option | Type | Description |
| -------- | -------- | ------------------------------------------------------------------------- |
| config | string | Explicit path to the .sprak.json config file. Auto-detected if omitted. |
TypeScript Support
Add the type declaration to get autocompletion for the virtual module import:
/// <reference types="@kystverket/sprak-react/virtual-sprak" />Or add it to your tsconfig.json:
{
"compilerOptions": {
"types": ["@kystverket/sprak-react/virtual-sprak"]
}
}Components
<SprakProvider>
Root provider that supplies translation data and locale state to the component tree.
Props
| Prop | Type | Required | Description |
| ------------------ | ----------- | -------- | ---------------------------------------------------------------- |
| locale | string | Yes | The active locale code (e.g. "en", "nb-NO"). |
| defaultNamespace | string | No | Default namespace for hooks. Defaults to "default". |
| fallbackLocale | string | No | Locale to fall back to when a key is missing in the current locale. |
| children | ReactNode | Yes | Child components. |
<NamespaceProvider>
Provide translations for a specific namespace. Supports nesting — if an ancestor already provides the same namespace, the outermost provider takes precedence, allowing apps to override library defaults.
<NamespaceProvider ns="settings" translations={translations.settings}>
<SettingsPage />
</NamespaceProvider>Props
| Prop | Type | Required | Description |
| -------------- | -------------- | -------- | ------------------------------------------------------------- |
| ns | string | Yes | The namespace to scope all child useTranslation() calls to. |
| translations | Translations | No | Translations for this namespace (language → key-value map). |
| link | string | No | Link translations from another namespace. See below. |
| children | ReactNode | Yes | Child components. |
Linking Namespaces
The link prop lets you reuse translations from an already-provided namespace as if they were supplied by an outer <NamespaceProvider>. This is useful when a library component has its own namespace but the host application wants to feed it translations it already has.
The format is "sourceNamespace" or "sourceNamespace:keyPrefix":
"app"— links all translations from theappnamespace."app:ui"— links only theuisubtree from theappnamespace."app:ui.buttons"— links theui.buttonssubtree.
Linked translations are injected before explicit translations, so they take priority (like an outer provider). If the linked namespace has not been provided by a parent, the link is silently ignored.
// Suppose the app provides an "app" namespace with { en: { ui: { save: "Save", cancel: "Cancel" } } }
<NamespaceProvider ns="app" translations={appTranslations}>
{/* The "library" namespace now inherits "app"'s "ui" subtree */}
<NamespaceProvider ns="library" link="app:ui">
<LibraryComponent />
</NamespaceProvider>
</NamespaceProvider>You can also combine link with explicit translations — the linked entries act as a fallback layer above the explicit ones:
<NamespaceProvider ns="library" link="app:ui" translations={libraryOverrides}>
<LibraryComponent />
</NamespaceProvider><Trans>
Rich-text translation component with JSX interpolation. Supports {{placeholder}} value substitution and XML-like component tags in translation strings.
// Translation value: "Hello <bold>{{name}}</bold>, visit <link>our page</link>"
<Trans
i18nKey="welcome"
values={{ name: 'World' }}
components={{
bold: <strong />,
link: <a href="/about" />,
}}
/>Props
| Prop | Type | Required | Description |
| ------------ | ---------------------------------- | -------- | ----------------------------------------------------------------- |
| i18nKey | string | Yes | Translation key to look up. |
| ns | string | No | Namespace override (defaults to the context's default namespace). |
| values | Record<string, string \| number> | No | Interpolation values for {{placeholder}} tokens. |
| components | Record<string, ReactNode> | No | Map of tag names to React elements for rich-text interpolation. |
Hooks
useTranslation(namespace?)
Returns a translation function t for the given namespace (or the default namespace from context).
function MyComponent() {
const { t } = useTranslation('common');
return <p>{t('greeting', { name: 'World' })}</p>;
}Return Value
| Property | Type | Description |
| -------- | ------------------------------------------- | ------------------------------------------------------ |
| t | (key: string, params?: TParams) => string | Translate a key, optionally with interpolation params. |
Key Resolution
- Looks up the key in the current locale's namespace data.
- Falls back to the
fallbackLocaleif the key is missing. - Returns the raw key string as a last resort.
Interpolation
Use {{placeholder}} syntax in translation values:
{
"greeting": "Hello, {{name}}! You have {{count}} messages."
}t('greeting', { name: 'Alice', count: 5 });
// → "Hello, Alice! You have 5 messages."useLocale()
Read and change the active locale.
function LocaleSwitcher() {
const { locale, setLocale, locales } = useLocale();
return (
<select value={locale} onChange={(e) => setLocale(e.target.value)}>
{locales.map((l) => (
<option key={l}>{l}</option>
))}
</select>
);
}Return Value
| Property | Type | Description |
| ----------- | -------------------------- | -------------------------------------------------- |
| locale | string | The current active locale code. |
| setLocale | (locale: string) => void | Change the active locale. |
| locales | string[] | All locale codes found in the loaded translations. |
Exports
Main Entry (@kystverket/sprak-react)
Components: SprakProvider, NamespaceProvider, Trans
Hooks: useTranslation, useLocale
Types: TFunction, TParams, UseTranslationResult, UseLocaleResult, SprakProviderProps, NamespaceProviderProps, LocaleContextValue, NamespaceMap, TransProps
Re-exports from shared: DEFAULT_NAMESPACE, NamespacedTranslations, Translations, SprakConfig
Plugin Entry (@kystverket/sprak-react/plugin)
Functions: sprakPlugin
Types: SprakPluginOptions
Server Entry (@kystverket/sprak-react/server)
Functions: createTranslator
Types: CreateTranslatorOptions, TFunction, TParams, UseTranslationResult, Translations, NamespacedTranslations
Server Components
The createTranslator function provides the same { t, scopedT } API as useTranslation, but without React context — making it compatible with React Server Components, API routes, and any non-React environment.
import { createTranslator } from '@kystverket/sprak-react/server';
import translations from 'virtual:sprak-translations';
export default function Page({ params }: { params: { locale: string } }) {
const { t } = createTranslator({
translations: translations.common,
locale: params.locale,
fallbackLocale: 'en',
});
return <h1>{t('hello', { name: 'World' })}</h1>;
}createTranslator(options)
| Option | Type | Required | Description |
| ---------------- | -------------- | -------- | ------------------------------------------------------------------- |
| translations | Translations | Yes | Translations for the namespace (language → key-value map). |
| locale | string | Yes | The active locale code. |
| fallbackLocale | string | No | Locale to fall back to when a key is missing in the current locale. |
Returns { t, scopedT } — identical to useTranslation().
Full Example
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { sprakPlugin } from '@kystverket/sprak-react/plugin';
export default defineConfig({
plugins: [react(), sprakPlugin()],
});// main.tsx
import { StrictMode, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { SprakProvider, NamespaceProvider, Trans, useTranslation, useLocale } from '@kystverket/sprak-react';
import translations from 'virtual:sprak-translations';
function LocaleSwitcher() {
const { locale, setLocale, locales } = useLocale();
return (
<select value={locale} onChange={(e) => setLocale(e.target.value)}>
{locales.map((l) => (
<option key={l}>{l}</option>
))}
</select>
);
}
function Greeting() {
const { t } = useTranslation('common');
return <h1>{t('hello', { name: 'World' })}</h1>;
}
function Settings() {
const { t } = useTranslation(); // uses "settings" from NamespaceProvider
return <p>{t('theme')}</p>;
}
function App() {
return (
<SprakProvider locale="en" fallbackLocale="en" defaultNamespace="common">
<NamespaceProvider ns="common" translations={translations.common}>
<LocaleSwitcher />
<Greeting />
<NamespaceProvider ns="settings" translations={translations.settings}>
<Settings />
</NamespaceProvider>
<Trans
i18nKey="welcome"
ns="common"
values={{ name: 'World' }}
components={{ bold: <strong />, link: <a href="/about" /> }}
/>
</NamespaceProvider>
</SprakProvider>
);
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
);License
ISC
