remix-i18n

Install

npm install --save remix-i18n

Config

export interface RemixI18nOptions {
  /**
   * Define the list of supported languages, this is used to determine if one of
   * the languages requested by the user is supported by the application.
   * This should be be same as the supportedLngs in the i18next options.
   */
  supportedLanguages: string[];
  /**
   * Define the fallback language that it's going to be used in the case user
   * expected language is not supported.
   * This should be be same as the fallbackLng in the i18next options.
   */
  fallbackLng: string;
}

export declare class RemixI18n {
  private currentLocale;
  fallbackLng: string;
  supportedLanguages: string[];
  private dict;
  constructor(options: RemixI18nOptions);
  locale: (lang?: string | undefined) => string;
  set: (lang: string, dict: I18nDict) => void;
  t: (key: string, params?: any, lang?: string | undefined) => string;
  private onChangeLanguage?;
  setOnChange: (fn: (locale: string) => void) => void;
}
import { RemixI18n } from 'remix-i18n';

const i18n = new RemixI18n({
  supportedLanguages: ['en', 'tl', 'da', 'zh'],
  fallbackLng: 'zh'
});

Set locale messages

i18n.set('locale', {
  hello: 'Hello World'
});

Client Side

import { RemixBrowser } from '@remix-run/react';
import { startTransition, StrictMode } from 'react';
import { hydrateRoot } from 'react-dom/client';
import { I18nProvider } from 'remix-i18n';
import { i18n, getLocale } from './i18n';

const locale = getLocale(window.location.pathname);
i18n.locale(locale);

startTransition(() => {
  hydrateRoot(
    document,
    <StrictMode>
      <I18nProvider i18n={i18n}>
        <RemixBrowser />
      </I18nProvider>
    </StrictMode>
  );
});

Server Side

// entry.server.tsx
import isbot from 'isbot';
import { renderToReadableStream } from 'react-dom/server';
import { RemixServer } from '@remix-run/react';
import { I18nProvider } from 'remix-i18n';
import { i18n, getLocale } from './i18n';

export default async function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext,
  loadContext: AppLoadContext
) {
  const locale = getLocale(new URL(request.url).pathname);
  i18n.locale(locale);

  const body = await renderToReadableStream(
    <I18nProvider i18n={i18n}>
      <RemixServer context={remixContext} url={request.url} />
    </I18nProvider>,
    {
      signal: request.signal,
      onError(error: unknown) {
        // Log streaming rendering errors from inside the shell
        console.error(error);
        responseStatusCode = 500;
      }
    }
  );

  if (isbot(request.headers.get('user-agent'))) {
    await body.allReady;
  }

  responseHeaders.set('Content-Type', 'text/html');
  return new Response(body, {
    headers: responseHeaders,
    status: responseStatusCode
  });
}

Change locale

const i18n = useI18n();
const location = useLocation();
useEffect(() => {
  const locale = getLocale(location.pathname);
  if (locale !== i18n.locale()) {
    i18n.locale(locale);
  }
}, [location]);

Useage

const { t } = useI18n();

// jsx
<h1>{t('hello')}</h1>;

Plurals

Ref: https://github.com/lukeed/rosetta/issues/4