Framework integration guide
Documentation index for the SDK: docs/README.md.
This document explains how to use @unolio/sdk with React, Next.js, Vite, Vue, Preact, and Svelte. It also answers whether you need a separate SDK per framework.
Do you need one SDK per framework?
No. The same package ships a framework-agnostic core (createClient from @unolio/sdk) that works anywhere modern JavaScript runs (browser with fetch, Node 18+, etc.).
@unolio/sdk— always use this for the translation client (t,preload,setLanguage, etc.).@unolio/sdk/react— optional helpers for React only:UnolioProvider,useUnolioClient,<Trans>,parseRichMessage. These are thin wrappers around React Context and hooks.
For Vue, Svelte, or plain usage, you do not need a second npm package unless you choose to publish your own @your-org/unolio-vue later. The pattern is always:
- Create one
createClient(...)instance (module scope or dependency-injection container). - Call
setLanguageandpreloadwhen the active locale changes (often in a root layout /onMounted/ route change). - Expose
t(and any other client methods you need) to the tree via your framework’s context / provide / stores.
Optional future packages (@unolio/sdk-vue, etc.) would only be convenience — the core API stays one.
Shared concepts (all stacks)
| Topic | Practice |
|---|---|
| API key & base URL | Never commit real keys. Use env vars (VITE_*, NEXT_PUBLIC_*, import.meta.env, process.env in Node). |
| CORS | The browser calls your baseUrl; that API must allow your app origin. |
| Initial load | After setLanguage(lang), call await client.preload(lang) before showing translated UI (or show a loading state until preload finishes). |
| Rich / ICU messages | Core + React: parseRichMessage and <Trans> are React-only today. Other frameworks can use client.t(key) for plain strings, or parse templates with your own component layer. |
| SSR | If the client runs only in the browser, keep the provider in a client-only boundary (see Next.js). For SSR of copy, you’d load messages on the server separately (out of scope here; core is client-oriented for fetch). |
React (CRA, Vite + React, etc.)
Install:
npm install @unolio/sdk react
Typical pattern: one module-level client, a small provider that preloads on lang, and a useTranslation hook.
/* eslint-disable react-refresh/only-export-components -- i18n entry: provider + hook */
import { createClient } from '@unolio/sdk';
import { UnolioProvider, useUnolioClient } from '@unolio/sdk/react';
import { useState, useEffect, type ReactNode } from 'react';
const i18n = createClient({
apiKey: import.meta.env.VITE_UNOLIO_API_KEY as string,
baseUrl: import.meta.env.VITE_UNOLIO_BASE_URL as string,
});
export function I18nProvider({
children,
lang = 'de',
}: {
children: ReactNode;
lang?: string;
}) {
const [ready, setReady] = useState(false);
useEffect(() => {
i18n.setLanguage(lang);
i18n.preload(lang).then(() => setReady(true));
}, [lang]);
if (!ready) return <div>Loading…</div>;
return <UnolioProvider client={i18n}>{children}</UnolioProvider>;
}
export function useTranslation() {
const client = useUnolioClient();
return { t: client.t.bind(client) };
}
Usage: wrap your app root with I18nProvider. Use useTranslation() for t('key'). For rich messages, use <Trans i18nKey="…" /> from @unolio/sdk/react (it reads the same client from context — do not pass client into Trans).
Next.js
Next splits Server Components vs Client Components. Anything that calls fetch to your API, holds useState/useEffect, or React Context must run on the client.
- Put
I18nProvider(and any component usinguseTranslation/Trans) in a file marked'use client'at the top. - Prefix public env vars with
NEXT_PUBLIC_so they are available in the browser bundle, e.g.NEXT_PUBLIC_UNOLIO_API_KEY,NEXT_PUBLIC_UNOLIO_BASE_URL. - In
createClient, read those vars inside the client module (not from server-only secrets unless you proxy).
Example env usage:
const i18n = createClient({
apiKey: process.env.NEXT_PUBLIC_UNOLIO_API_KEY!,
baseUrl: process.env.NEXT_PUBLIC_UNOLIO_BASE_URL!,
});
Wrap layout.tsx or app/providers.tsx so children sit under I18nProvider. App Router and Pages Router both follow the same rule: provider = client component subtree.
If you must avoid exposing the API key to the browser, you cannot use the browser client directly with that key; you’d need a server-side proxy route that attaches credentials — that is an application architecture choice, not a separate SDK.
Vite
Vite is a bundler, not a framework. You integrate based on the template you chose:
| Template | Follow |
|---|---|
| Vite + React | Same as React. Use import.meta.env.VITE_* for keys (must prefix with VITE_). |
| Vite + Vue | Same as Vue. Use import.meta.env.VITE_*. |
| Vite + Svelte | Same as Svelte and SvelteKit. |
Example .env:
VITE_UNOLIO_API_KEY=lk_your_key_here
VITE_UNOLIO_BASE_URL=https://your-api-domain.com
Vue 3
Install core only:
npm install @unolio/sdk
Create the client once (e.g. src/i18n/client.ts). Provide it from main.ts or a plugin, and expose a useI18n composable via inject.
// src/i18n/client.ts
import { createClient } from '@unolio/sdk';
export const unolio = createClient({
apiKey: import.meta.env.VITE_UNOLIO_API_KEY as string,
baseUrl: import.meta.env.VITE_UNOLIO_BASE_URL as string,
});
export const injectionKey = Symbol('unolio');
// src/i18n/plugin.ts
import type { App } from 'vue';
import { unolio, injectionKey } from './client';
export async function installI18n(app: App, lang: string) {
unolio.setLanguage(lang);
await unolio.preload(lang);
app.provide(injectionKey, unolio);
}
// src/i18n/useI18n.ts
import { inject } from 'vue';
import { injectionKey } from './client';
export function useI18n() {
const client = inject(injectionKey);
if (!client) throw new Error('Unolio i18n not installed');
return { t: client.t.bind(client), client };
}
Call installI18n(app, 'de') before app.mount (or await in an async bootstrap). You do not need @unolio/sdk/vue unless you later extract this into a published package.
Preact
Option A — preact/compat: Alias react and react-dom to Preact’s compat builds in your bundler so @unolio/sdk/react believes it is talking to React. Then use the same React provider and hooks as documented in the React section.
Option B — Preact-native: Use createClient from @unolio/sdk only and wire Preact Context (createContext / useContext) yourself, mirroring what UnolioProvider does. The API surface you need from the client is t, preload, setLanguage.
You do not need a separate npm SDK; you may need build aliases or a tiny local wrapper.
Svelte and SvelteKit
Use createClient from @unolio/sdk. For SvelteKit, initialize in +layout.ts / +layout.svelte (or a dedicated store module) so setLanguage + preload run when the locale changes.
Example with a writable “ready” flag and a thin t helper:
// src/lib/i18n.ts
import { createClient } from '@unolio/sdk';
import { writable } from 'svelte/store';
const client = createClient({
apiKey: import.meta.env.VITE_UNOLIO_API_KEY,
baseUrl: import.meta.env.VITE_UNOLIO_BASE_URL,
});
export const i18nReady = writable(false);
export async function initI18n(lang: string) {
i18nReady.set(false);
client.setLanguage(lang);
await client.preload(lang);
i18nReady.set(true);
}
export function t(key: string, vars?: Record<string, string | number>) {
return client.t(key, vars);
}
In root layout, await initI18n('de') (or react to $page.params.lang). In components, import t from src/lib/i18n or bind a store. No separate Svelte SDK is required.
Summary
| Framework | Packages to install | Pattern |
|---|---|---|
| React | @unolio/sdk + react | UnolioProvider + useUnolioClient / your useTranslation |
| Next.js | same as React | 'use client' + NEXT_PUBLIC_* env |
| Vite | same as chosen UI | import.meta.env.VITE_* |
| Vue 3 | @unolio/sdk | provide / inject + createClient |
| Preact | @unolio/sdk (+ optional compat for @unolio/sdk/react) | Alias to React adapter or manual context |
| Svelte | @unolio/sdk | module + stores / layout init |
You do not need a distinct SDK product per framework for Unolio’s HTTP API — only one core and, for React, the optional @unolio/sdk/react subpath.
For UMD/script-tag HTML (no bundler), see the main README “Script tag” section.