Skip to main content

How to Integrate i18next with Weaverse Hydrogen

This guide explains how to integrate i18next as the primary translation engine in your Weaverse Hydrogen project. While Weaverse provides a built-in useThemeText() hook for simple { {variable} } substitution, integrating i18next enables advanced localization features like plurals, context-based formatting, and ordinals—without breaking Weaverse’s visual translation workflow!

How It Works

Weaverse’s resolution chain allows you to inject an externalT function into the withWeaverse configuration. When provided, Weaverse evaluates translations in the following priority:
  1. External t (i18next) ← Highest priority
  2. Merchant Overrides (from Weaverse Studio)
  3. Static Content (schema.i18n.staticContent)
  4. Translation Key ← Fallback
By routing translations through i18next first and gracefully falling back to Weaverse if strings aren’t matched, your components enjoy full i18next capabilities while giving non-technical merchants complete control inside the Weaverse builder.

Step 1: Install Dependencies

You only need the core i18next package. React bindings (react-i18next) are fully optional unless you strictly prefer using them in your components over useThemeText.
npm install i18next

Step 2: Create the i18n Bridge

Create an initialization and bridge file (e.g., app/utils/i18n.ts) that holds your i18next singleton and the adapter function (weaverseT) expected by Weaverse.
import i18next from 'i18next';
import type { TranslateFunction } from '@weaverse/hydrogen';

// 1. Singleton initialization
let initPromise: Promise<void> | null = null;
function ensureInit() {
  if (initPromise) return initPromise;
  
  initPromise = i18next.init({
    fallbackLng: 'en',
    interpolation: { escapeValue: false }, // React already safeguards against XSS
    compatibilityJSON: 'v4', // Required for advanced pluralizations (_one, _other)
    saveMissing: false,
  }).then(() => undefined);
  
  return initPromise;
}

// 2. Safely initialize translation state per-request (Server / Client)
export async function initI18n(locale: string, translations: Record<string, any>) {
  const lang = locale.toLowerCase().split('_')[0]; // Map 'EN_US' -> 'en'
  await ensureInit();

  if (!i18next.hasResourceBundle(lang, 'translation')) {
    i18next.addResourceBundle(lang, 'translation', translations, true, false);
  }

  if (i18next.language !== lang) {
    await i18next.changeLanguage(lang);
  }
}

// 3. The `externalT` adapter for Weaverse
export const weaverseT: TranslateFunction = (key, variables) => {
  if (!i18next.isInitialized) return key;
  
  const result = i18next.t(key, variables as object | undefined);
  
  // If no translation is found, return the raw key so Weaverse can handle fallbacks gracefully!
  return typeof result === 'string' ? result : key;
};

Step 3: Configure Weaverse at the Root level

You now need to inject weaverseT into Weaverse and guarantee initI18n runs before your app renders the React tree. Open your app/root.tsx (or equivalent layout file).

Add the externalT injection:

Pass the weaverseT instance to your withWeaverse wrapper configuration.
// app/root.tsx
import { weaverseT } from '~/utils/i18n';
// ...
export const Layout = withWeaverse(RootLayout, { t: weaverseT });

Initialize the language on the Server:

Inside your route loader, extract the storefront locale and explicitly trigger initI18n using your base JSON bundle.
// app/root.tsx
import { initI18n } from '~/utils/i18n';
import baseTranslations from '~/i18n/en.json';

export async function loader(args: LoaderFunctionArgs) {
  const data = await loadData(args);
  
  // E.g., data.locale is the Hydrogen active locale prefix
  await initI18n(data.locale, baseTranslations); 

  return data;
}

Sync Client Hydration:

If the user transitions languages seamlessly via client-side routing, add a useEffect within the RootLayout implementation to sync i18next on the browser.
export function RootLayout() {
  const { locale } = useRouteLoaderData<typeof loader>('root');
  
  useEffect(() => {
    if (locale) initI18n(locale, baseTranslations);
  }, [locale]);
  
  return (/* Your Layout UI */);
}

Step 4: Write Components

Your components don’t need any updates. Standard keys simply fall back to Weaverse, but keys mapped in your i18next bundles using advanced interpolations now resolve correctly! Theme JSON Example:
{
  "cart": {
    "itemCount_one": "{{count}} item",
    "itemCount_other": "{{count}} items"
  }
}
React Component Example:
import { useThemeText } from "@weaverse/hydrogen";

export function CartItemCount({ count }: { count: number }) {
  const { t } = useThemeText();
  
  // Will output "1 item" or "3 items" depending on the count!
  return <span>{t('cart.itemCount', { count })}</span>;
}

Summary Checklist

  • Install i18next.
  • Create an i18n bridge script containing a secure async init method and the weaverseT proxy.
  • Pass the localized locale variable to initI18n in root.tsx loader.
  • Wrap RootLayout via withWeaverse(RootLayout, { t: weaverseT }).
  • Enjoy full i18next pluralization combined with Weaverse’s visual translation integration!