Examples

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

KeyFormatValue
home.welcome_titletextWelcome!
home.items_counticu{count, plural, one {# item} other {# items}}

Install

npm install @airstrings/web

Create 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>
}

On this page