ShipEasy Docs

Remix

ShipEasyI18n integration for Remix — server-side label loading in loader(), useShipEasyI18n() hook in components, and inline data via root loader.

Package: @i18n/remix

Install

npm install @i18n/remix

Root loader — inline label data

Load labels once in the root loader and pass them to the client as inline data. This avoids a separate client fetch on every page:

// app/root.tsx
import { json, type LoaderFunctionArgs } from '@remix-run/node';
import { fetchLabels } from '@i18n/remix/server';

export async function loader({ request }: LoaderFunctionArgs) {
  const labels = await fetchLabels({
    i18nKey: process.env.ShipEasyI18n_KEY!,
    profile: 'en:prod',
    chunk: 'index',
  });

  return json({ i18nData: labels });
}

Then pass the data to the provider in App:

// app/root.tsx (continued)
import { useLoaderData } from '@remix-run/react';
import { ShipEasyI18nProvider } from '@i18n/remix';

export default function App() {
  const { i18nData } = useLoaderData<typeof loader>();

  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <ShipEasyI18nProvider initialData={i18nData}>
          <Outlet />
        </ShipEasyI18nProvider>
        <Scripts />
      </body>
    </html>
  );
}

Per-route loader() with fetchLabels()

For routes that need a different label chunk:

// app/routes/checkout.tsx
import { json, type LoaderFunctionArgs } from '@remix-run/node';
import { fetchLabels } from '@i18n/remix/server';

export async function loader({ request }: LoaderFunctionArgs) {
  const labels = await fetchLabels({
    i18nKey: process.env.ShipEasyI18n_KEY!,
    profile: 'en:prod',
    chunk: 'checkout',
  });

  return json({ i18nData: labels });
}

useShipEasyI18n() hook in components

// app/components/NavBar.tsx
import { useShipEasyI18n } from '@i18n/remix';

export function NavBar() {
  const { t, ready } = useShipEasyI18n();

  return (
    <nav>
      <a href="/" data-label="nav.home">{t('nav.home')}</a>
      <a href="/account" data-label="nav.account">{t('nav.account')}</a>
    </nav>
  );
}

Because Remix runs loaders on every navigation, label data stays fresh without a separate CDN fetch on each route transition.

For the full implementation spec including package source code and edge cases, see plans/frameworks/remix.md in the repository.