@execonline-inc/translations
v9.17.0
Published
Support for typed and interpolatable translations
Downloads
12
Readme
translations
The translations
package provides a library for support of typed translation keys and typed React-based translation interpolation.
Usage
This library uses the i18next library underneath a custom adapter. The i18next library handles translating, pluralizing, and basic interpolation (using {{abc}}
placeholders).
The custom adapter implementation handles type-checking and component interpolation (using <abc/>
and <abc>...</abc>
placeholders).
All translation content must be added to the translation files whose location is specified with the loadPath
configuration which is passed to i18next.
See the translations
function for integrating the library into an application. Create a translations module in the application and export the relevant things.
Types
Components
AlreadyTranslated
This component represents content that has already been translated (such as pre-translated content from a backend). Its use in a component tree helps to distinguish content that has been addressed for translation with content that has not.
import { AlreadyTranslated, AlreadyTranslatedText } from '@execonline-inc/translations';
const text: AlreadyTranslatedText = { kind: 'already-translated-text', text: 'content' };
const component = <AlreadyTranslated content={text} />;
L
This component will localize a date/time according to a given format.
import { LocalizationFormat, Localizeable, translations } from '@execonline-inc/translations';
const { L } = translations<...>(...) // see `translations` documentation
const localizeable: Localizeable = new Date();
const format: LocalizationFormat = 'long-date-and-time';
const component = <L localizeable={localizeable} format={format} />;
T
This component performs all available translation features given a typed translation definition via its props.
import { translations } from '@execonline-inc/translations';
const { T } = translations<...>(...) // see `translations` documentation
const component = <T kind="something to translate" />;
NotTranslated
Some content is not suitable for translation. Some examples of this are:
- proper names (though there may be cases where they can be represented in the target language)
- email addresses
- URLs
Rendering this type of content directly in component trees may be confused with content that hasn't been addressed for translation yet, so wrapping it in this component makes it clear that it has been considered for translation.
import { NotTranslated } from '@execonline-inc/translations';
const text: string = 'not translatable';
const component = <NotTranslated text={text} />;
TranslationsContext
This component is an implementation of a React Context whose data is a TranslationsState
.
import { TranslationsContext, TranslationsState } from '@execonline-inc/translations';
const state: TranslationsState = { kind: 'uninitialized' };
const consumer = (ts: TranslationsState): React.ReactNode => <>{ts.kind}</>;
const Example: React.FC = () => (
<TranslationsContext.Provider value={state}>
<TranslationsContext.Consumer>{consumer}</TranslationsContext.Consumer>
</TranslationsContext.Provider>
);
TranslationsLoader
This component loads the the translations when it is mounted and wraps its children in a TranslationsContext.Provider
. It renders the component passed as the loading
prop until the translations are loaded to prevent the normal child components from rendering without content (because it's awaiting translation) and eventually appearing suddenly.
import { translations, TranslationsLoader } from '@execonline-inc/translations';
const { loader } = translations<...>(...) // see `translations` documentation
const component = <TranslationsLoader loader={loader} loading={<></>} />;
Functions
alreadyTranslatedText
This decoder decodes to an AlreadyTranslatedText
object from a string.
import { alreadyTranslatedText, AlreadyTranslatedText } from '@execonline-inc/translations';
const decoder: Decoder<AlreadyTranslatedText> = alreadyTranslatedText;
const result: Result<string, AlreadyTranslatedText> = decoder.decodeAny('content');
defaultSettings
These are default settings for i18next
based on ExecOnline's use of the library.
initTask
This function will return a Task
to initialize i18next
. It provides the opportunity for i18next
to be configured as necessary by the user of this library.
import { initTask, TranslationsLoader } from '@execonline-inc/translations';
import i18next from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpApi from 'i18next-http-backend';
const i18nextSettings = {
/* ... */
};
const i18nextWithModules = i18next.use(HttpApi).use(LanguageDetector);
const loader = initTask(i18nextWithModules, i18nextSettings);
<TranslationsLoader loader={loader} loading={<></>} />;
localization
This function will localize a date/time for a given language and according to a given format.
import { localization, LocalizationFormat, Localizeable } from '@execonline-inc/translations';
const localizeable: Localizeable = new Date();
const format: LocalizationFormat = 'long-date-and-time';
const language = 'en-US';
const result = localization(localizeable, format, language);
localizer
This curried function will localize a date/time according to a given format under certain translation mapping load states.
When the state is loaded
or loaded-from-fallback
, localization occurs via a call to localization
.
When the state is uninitialized
, the localizeable argument is converted to a string.
import {
localizer,
LocalizationFormat,
Localizeable,
TranslationsState,
} from '@execonline-inc/translations';
const localizeable: Localizeable = new Date();
const format: LocalizationFormat = 'long-date-and-time';
const state: TranslationsState = { kind: 'uninitialized' };
const result: string = localizer(localizeable, format)(state);
translation
This curried function performs the lookup in the translation mapping file and simple interpolation when appropriate based on the text to be translated and the state of the mapping.
When the state is loaded
or loaded-from-fallback
, the mapped translation text is returned. If the text is found to be "not translatable", then it is returned without translation.
When the state is uninitialized
, an empty string is returned.
import { translations, TranslationsState } from '@execonline-inc/translations';
const { translation } = translations<...>(...) // see `translations` documentation
const state: TranslationsState = { kind: 'uninitialized' };
const result: string = translation('something to translate', {})(state);
translator
This curried function performs all available translation features given a typed translation definition and the mapping load state.
import { translations, TranslationsState } from '@execonline-inc/translations';
const { translator } = translations<...>(...) // see `translations` documentation
const state: TranslationsState = { kind: 'uninitialized' };
const result: React.ReactNode = translator({ kind: 'something to translate' })(state);
translations
This function returns an object of functions and components that are typed according to the translation types provided to the translations
function.
import {
Config,
translations,
interpolator,
Interpolator,
parameterized,
Parameterized,
ParameterizedFn,
scalar,
} from '@execonline-inc/translations';
import i18next, * as i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
const loader = initTask(i18next.use(LanguageDetector), {
...defaultSettings,
backend: {
loadPath: 'public/locales/{{lng}}/{{ns}}.json',
},
});
const translatablePlainTextKeys = ['something to translate', 'something else'] as const;
const notTranslatable = ['[email protected]', 'https://example.com/'] as const;
type TranslatablePlainTextKey = typeof translatablePlainTextKeys[number];
type NotTranslatable = typeof notTranslatable[number];
type ParameterizedProps =
| { kind: '{{count}} minutes'; count: number }
| { kind: '<bolded>So</bolded> cool!'; bolded: Interpolator };
type PlainTextKey = TranslatablePlainTextKey | NotTranslatable;
type ParameterizedKey = ParameterizedProps['kind'];
const parameterizedValues: ParameterizedFn<ParameterizedKey, ParameterizedProps> = (
props: ParameterizedProps
): Parameterized<ParameterizedKey, ParameterizedProps> => {
switch (props.kind) {
case '{{count}} minutes':
return parameterized(props, { count: scalar(props.count) });
case '<bolded>So</bolded> cool!':
return parameterized(props, { bolded: interpolator(props.bolded) });
}
};
const { L, translation, translator, T } = translations<
PlainTextKey,
NotTranslatable,
ParameterizedKey,
ParameterizedProps
>(translatablePlainTextKeys, notTranslatable, parameterizedValues);