@uphillhealth/i18n
v1.1.2
Published
UpHill Health library for internationalization and localization
Downloads
343
Readme
lib-ts-i18n
aka i18n
Installation
npm install --save-dev @uphillhealth/i18n@latest js-cookie@latest
Dates and numbers
It uses Intl Native API to format dates in a localized way. It should also use Intl to format numbers in the future.
Intl API documentation https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl.
Handling translation and localization
The best approach is to create general dictionaries for languages and then extend them to create localization.
The main language in the application, or the fallback language, should be American English to better readability.
We must use valid language codes because we often get language or use native APIs from the browser.
For general languageThe format is en|es|pt
and for country specific it is en-GB|es-MX|pt-PT
.
When exporting locale assets, this pattern must be followed to not cause any problem with translations.
Bellow we have the full example of organization and usage:
- folder organization
src/assets
└ locales
├ index.ts // Exports
├ en.ts // Generical English (US)
├ en-GB.ts // Localization for British English (Default language)
├ es.ts // General Spanish (Spain)
├ es-AR.ts // Localization for Argentina
├ es-UR.ts // Localization for Uruguay
├ pt.ts // General Portuguese (Brazil)
├ pt-PT.ts // Localization for Portugal
es.ts
and pt.ts
files indicates spanish and portuguese respectively, they will contain general translation for these languages and every other file that has {languageCode}-{countryCode}.{extension}
will be localization only.
This folder structure is just an example, it can be organized in any way.
Bellow we have a full demonstration:
- pt.ts file
export default {
'Hello {{name}}!': 'Olá {{name}}!',
'Train station': 'Estação de trem',
};
and then in pt-PT.ts
- pt-PT.ts file
// get pt
import pt from './pt';
export default {
...pt, // extend it
// modify only what is needed to localize it for Portugal
'Train station': 'Estação de comboio',
};
at index.ts
for exports
- index.ts
import { I18nAssets } from '@uphillhealth/i18n';
export const localeAssets: I18nAssets {
'en' : () => import('./en'),
'en-GB': () => import('./en-gb'),
'es' : () => import('./es'),
'es-AR': () => import('./es-ar'),
'es-UR': () => import('./es-ur'),
'pt' : () => import('./pt'),
'pt-PT': () => import('./pt-pt')
}
We use dynamic imports to load just what is selected by user in the application or when it's not choosen, we use browser main language (navigator.language).
And then give it to I18n component:
import { I18nAssets } from '@uphillhealth/i18n';
import { localeAssets } from '@/assets/locales';
<I18n assets={localeAssets}>
<SomeComponent />
</I18n>;
Core
Pure JavaScript functions that can be re-utilizable.
getLanguage
Returns a language prioritizing:
initialLanguage (given) > cookie > browser language > default (is only checked against available translations)
verifyLanguageHasTranslation
Verify if the given language has a translation available at the application. Returns metadata that informs any action perfomerd.
templateFactory
Receives a dictionary and returns another method that can translate and interpolate dynamic values.
const dictionary = {
Sentence: 'Frase',
'Hello {{name}}!': 'Olá {{name}}!',
};
const translate = translateFactory(dictionary);
// simple
translate('Sentence');
// with context
translate('Hello {{name}}!', { name: 'John' });
dateTimeFormat
Uses Intl.DateTimeFormat
to format and localize date and time.
relativeDateFormat
Uses Intl.RelativeTimeFormat
and dateTimeFormat
function to format and localize.
React Components
I18n
It initializes the language flow and instances I18nContext.
The decision order to choose a language is:
- initialLanguage (component prop)
- stored cookie (is only store if cookieAttributes are given)
- browser language
- fallback
- Props
interface I18nProps {
assets?: I18nAssets;
children: ReactNode | ReactNode[];
cookieAttributes?: I18nCookieAttributes; // js-cookie cookie's attributes + name (all optional, default name is i18n)
initialLanguage?: string;
loadingComponent?: ReactElement;
onLoaded?: () => void;
}
- Usage
import { I18nAssets } from '@uphillhealth/i18n/core';
export const assets: I18nAssets = {
en: () => import('./en'),
pt: () => import('./pt'),
};
import { translate, RelativeDate } from '@uphillhealth/i18n/react';
export const SomeComponent = () => {
return (
<>
<p>{translate('Sentence')}</p>
<p>
<RelativeDate ocurrence={new Date()} />
</p>
</>
);
};
import { I18n } from '@uphillhealth/i18n';
import { assets } from '@/assets/locales';
import { SomeComponent } from '@/components';
<I18n assets={assets}>
<SomeComponent />
</I18n>;
useI18n
Shorthand for useContext(I18nContext)
Global data related to i18n that can be used across the application.
- Context props
// enum, interfaces & types abstractions
export type PluralizedDictionary = Record<'0' | '1' | '2' | string, string>;
export type I18nDictionary = Record<string, string | PluralizedDictionary>;
enum LanguageVerificationType {
'fallback' = 'FALLBACK',
'shorter' = 'SHORTER',
'unchanged' = 'UNCHANGED',
}
interface I18nMetaData {
desired: string;
onDisplay?: string;
onDisplayType?: LanguageVerificationType;
}
// Internal state
interface I18nState {
availableLanguages: string[];
dictionary: I18nDictionary;
isLoadingLanguage: boolean;
language: string;
metadata?: I18nMetaData;
}
// This is what actually will be available when using the context
interface I18nContextProps extends Omit<I18nState, 'isLoadingLanguage'> {
setLanguage: (language: string) => void;
translate: TranslateMethod;
utilities: {
months: UtilityMonths;
};
}
- Usage
import { useI18n } from '@uphillhealth/i18n';
export const SomeComponent = () => {
const { availableLanguages, dictionary, language, metadata, setLanguage, translate } = useI18n();
return <>...</>;
};
translate
Abstraction of core function templateFactory.
- simple translation
import { translate } from '@uphillhealth/i18n';
export const SomeComponent = () => {
return (
<>
{translate('Sentence')}
{translate('Hello {{name}}!', { name: 'John' })} // with context
</>
);
};
- Pluralization and translation
To use plural options, at translation files you can add the following:
export default {
'plural.email': {
'0': 'Hey {{name}}, você não tem emails',
'1': 'Hey {{name}}, você tem {{count}} email',
'2': 'Hey {{name}}, você tem {{count}} emails',
'99': 'Hey {{name}}, você tem vários emails',
},
};
import { translate } from '@uphillhealth/i18n';
export const SomeComponent = () => {
return (
<>
// without giving a count, fallbacks to 0{translate('plural.email', { name: 'Jonh' })}
// Hey John, você não tem emails
{translate('plural.email', { count: 0, name: 'Jonh' })}
// Hey John, você não tem emails
{translate('plural.email', { count: 1, name: 'Jonh' })}
// Hey John, você tem 1 email // from 2 though 98
{translate('plural.email', { count: 50, name: 'Jonh' })}
// Hey John, você tem 50 emails // 99+
{translate('plural.email', { count: 123, name: 'Jonh' })}
// Hey John, você tem vários emails
</>
);
};
RelativeDate
Abstraction of core function relativeDateFormat.
It will show a relative format for dates that are in a 7-day range both in the future or past, other than that it will be a formatted specific date + time, the time can be hidden and can be added preposition.
- Props
interface RelativeDateProps {
/* Base date to compare the ocurrence date. Generally you won't set it.
* Defaults to new DateTime()
*/
compareWith?: Date;
hideTime?: boolean;
preposition?: string;
ocurrence: Date;
}
- Usage
import { RelativeDate } from '@uphillhealth/i18n';
export const SomeComponent = () => {
return (
<>
<RelativeDate ocurrence={new Date()} />
</>
);
};
DateTime
Abstraction of core function dateTimeFormat.
It will show a formatted specific date + time, the time can be hidden. If options are given, hideTime is ignored. Only the options are considered.
- Props
interface DateTimeProps {
/*
* Hide only works when using default options, with you give options it will be descarted
*/
hideTime?: boolean;
ocurrence: Date;
options?: Intl.DateTimeFormatOptions;
}
- Usage
import { DateTime } from '@uphillhealth/i18n';
export const SomeComponent = () => {
return (
<>
<DateTime ocurrence={new Date()} />
</>
);
};