schummar / schummar-translate

TypeScript powered translation library for React and Node.js.
MIT License
128 stars 2 forks source link
i18n internationalization localization translation

schummar-translate

TypeScript powered translation library for React and Node.js.

Example

Given a translation file like this:

// en.ts
export default {
  welcomeMessage: 'Hi, {name}',
  currentTime: 'It is now {time, time, short}',
} as const;

schummar-translate is able to provide type checking and autocomplete/IntelliSense for both translation keys and parameters in ICU format: example

Getting started

Install schummar-translate.

npm install schummar-translate

Create and export a translator instance

// translate.ts
import { createTranslator, TranslationContextProvider } from 'schummar-translate/react';
import en from './en.ts';
import de from './de.ts';

export const { t, useTranslator, getTranslator } = createTranslator({
  sourceDictionary: en,
  sourceLocale: 'en',
  dicts: { de },
});

Use it everywhere in your app

import { t } from './translate';

export App() {
  const [locale, setLocale] = useState('en');

  const toggleLocale = () => {
    setLocale((locale) => (locale === 'en' ? 'de' : 'en'));
  }

  return (
    <TranslationContextProvider locale={locale}>
      <div onClick={toggleLocale}>
        {t('welcomeMessage', { name: 'schummar' })}
      </div>
    </TranslationContextProvider>
  )
}

API

createTranslator

function createTranslator(options: Options): ReturnValue;

type Options = {
  sourceDictionary?: { [id: string]: Dict | string };
  sourceLocale: string;
  fallbackLocale?: string | readonly string[] | ((locale: string) => string | readonly string[]);
  dicts?:
    | { [locale: string]: PartialDict<D> | (() => MaybePromise<PartialDict<D>>) }
    | ((locale: string) => MaybePromise<PartialDict<D> | null>);
  warn?: (locale: string, id: string) => void;
  fallback?: string | ((id: string, sourceTranslation: string) => string);
  placeholder?: string | ((id: string, sourceTranslation: string) => string);
  cacheOptions?: CacheOptions;
  dateTimeFormatOptions?: Intl.DateTimeFormatOptions;
  displayNamesOptions?: Intl.DisplayNamesOptions;
  listFormatOptions?: Intl.ListFormatOptions;
  numberFormatOptions?: Intl.NumberFormatOptions;
  pluralRulesOptions?: Intl.PluralRulesOptions;
  relativeTimeFormatOptions?: Intl.RelativeTimeFormatOptions;
};

type CacheOptions = {
  maxEntries?: number;
  ttl?: number;
};

type ReturnValue = {
  getTranslator: GetTranslator;
  useTranslator: UseTranslator;
  t: ReactTranslator;
  clearDicts: () => void;
};

The are two versions of this function, depending on the used import. When importing 'schummar-translate', it creates a translator without React support (and therefore without the dependency on React). Then the last three parameters do not apply and the return value only contains getTranslator. When importing 'schummar-translate/react' React support and the last three parameters are included.

The return value is meant to be exported so the provided functions can be used everywhere in your app: export const { getTranslator, useTranslator, t } = createTranslator({ ... })

t

function t(id: K, values: V, options?: Options): ReactNode;

type Options = {
  locale?: string;
  fallback?: React.ReactNode;
  placeholder?: React.ReactNode;
  component?: React.ElementType;
};

t can be used to translate string withing JSX: <div>{t('foo', { value: 42 })}</div>. id has to be a flattened key from the source dictionary. values has to be an object containing the ICU paramters used in the string in the source dictionary. If there are no parameters, values is optional.

Of course if you don't like the minimally named t you can rename it in the export: export const { getTranslator, useTranslator, t: translate } = ...

t.unknown

function t.unknown(id: string, values?: Record<string, unknown>, options?: Options): ReactNode;

type Options = {
  locale?: string;
  fallback?: React.ReactNode;
  placeholder?: React.ReactNode;
}

t.unknown does exactly the same as t but without type checking. This can be useful if if the translation is not necessarily available. E.g. t.unknown(`types.${currentType`, undefined, { fallback: currentType }).

t.format

function t.format(template: string, values: V): ReactNode;

t.format can be used to format something using ICU. E.g. t.format('{d, date, short}', { d: new Date() }).

t.render

function t.render(renderFn: (t: HookTranslator<D>) => ReactNode, dependencies?: any[]): ReactNode;

t.render can be used to get access to a translator instance as you would get from useTranslator. That is useful e.g. when working on a class component, where the hook is otherwise not available: <div>{t.render(t => <ComponentThatUsesStringProperty placeholder={t('key1')} />)}</div>

t.locale

function f.locale: ReactNode;

t.locale returns the currently active locale. Mostly useful for useTranslator, to pass into another function.

t.{dateTimeFormat, displayNames, listFormat, numberFormat, pluralRules, relativeTimeFormat}

E.g.

function f.dateTimeFormat(date?: Date | number | string, options?: Intl.DateTimeFormatOptions): ReactNode;

t.dateTimeFormat formats dates and times. It proxies Intl.DateTimeFormat.format but adds caching and inject the active locale. The same is true for displayNames, listFormat, numberFormat, pluralRules and relativeTimeFormat. See MDN: Intl

useTranslator

function useTranslator(locale?: string): HookTranslator;

type HookTranslator = {
  (id: K, values: V, options?: Options): string | string[];
  unknow: (id: string, values?: Record<string, unknown>, options?: Options): string | string[];
  format: (template: string, values: V): string;
  locale: string;
}

type Options = {
  fallback?: string;
  placeholder?: string;
}

React hook that returns a translator that works very similarly to t, but being a hook itself, it does not need internal hooks and therefore returns a string instead of a ReactNode. That is useful in case you need to pass strings somewhere, e.g. as options to a select component etc. If the dictionary value is an array, an array of translated string will be returned. For more details see t, t.unknown and t.format.

getTranslator

function getTranslator(locale: string): Promise<Translator>;

type Translator = {
  (id: K, values: V, options?: Options): string | string[];
  unknow: (id: string, values?: Record<string, unknown>, options?: Options): string | string[];
  format: (template: string, values: V): string;
  locale: string;
}

type Options = {
  fallback?: string;
}

Returns a promise of a translator object. That method can be used in the backend or in the frontend outside of React components. It loads the necessary locales first then resolves the promise. The resulting translator is again very similar to t but obviously returning string and not ReactNode. If the dictionary value is an array, an array of translated string will be returned. For more details see t, t.unknown and t.format.

clearDicts

function clearDicts(): void;

Clears all dictionary and cache data. This will result in dictionaries being reloaded, as soon as they are used again. This allows loading new dictionary versions from a server, for example.