React
A complete React integration with a useSyncExternalStore hook, plus a Next.js server-side pattern with per-locale instance caching.
A minimal but complete React setup using the AirStrings web SDK. One module-level instance, one hook, one component. For every configuration field in detail, see the Web SDK reference.
Strings used in this example
| Key | Format | Value |
|---|---|---|
home.welcome_title | text | Welcome! |
home.items_count | icu | {count, plural, one {# item} other {# items}} |
Install
npm install @airstrings/webCreate a module-level instance
One instance per app. In the browser it caches verified bundles in IndexedDB and refreshes when the tab becomes visible.
import { AirStrings } from '@airstrings/web'
export const airstrings = new AirStrings({
organizationId: 'org_...',
projectId: 'proj_...',
environmentId: 'env_...',
publicKeys: ['pk_...'],
locale: 'en',
})Subscribe with a hook
The SDK exposes data and events, not UI bindings. useSyncExternalStore turns the strings:updated event into React re-renders. on() returns the unsubscribe function React needs.
import { useSyncExternalStore } from 'react'
import { airstrings } from './airstrings'
export function useStrings() {
return useSyncExternalStore(
(cb) => airstrings.on('strings:updated', cb),
() => airstrings.strings,
() => airstrings.strings,
)
}Render
t() returns the raw string (or the key as fallback), format() formats ICU patterns. Calling useStrings() is what re-renders the component when a new bundle lands.
import { airstrings } from './airstrings'
import { useStrings } from './useStrings'
export function Welcome() {
useStrings()
return (
<section>
<h1>{airstrings.t('home.welcome_title')}</h1>
<p>{airstrings.format('home.items_count', { count: 3 })}</p>
</section>
)
}Next.js (server components)
On the server there is no long-lived UI to subscribe. Instead, await whenReady() so the first load cycle completes before rendering, and cache one instance's strings per locale so concurrent renders share a single fetch. In Node the SDK uses an in-memory cache that lives for the process lifetime.
import 'server-only'
import { AirStrings } from '@airstrings/web'
const cache = new Map<string, Promise<Record<string, string>>>()
function getStrings(locale: string): Promise<Record<string, string>> {
let entry = cache.get(locale)
if (!entry) {
entry = (async () => {
const sdk = new AirStrings({
organizationId: 'org_...',
projectId: 'proj_...',
environmentId: 'env_...',
publicKeys: ['pk_...'],
locale,
})
await sdk.whenReady()
return { ...sdk.strings }
})()
cache.set(locale, entry)
}
return entry
}
export async function getT(locale: string) {
const strings = await getStrings(locale)
return (key: string) => strings[key] ?? key
}Use it from any server component:
import { getT } from '@/lib/airstrings'
export default async function Page() {
const t = await getT('en')
return <h1>{t('home.welcome_title')}</h1>
}