@casipe/react-i18n
v1.0.2
Published
React library for internationalization and localization
Readme
@casipe/react-i18n
aka react-i18n
Installation
publish-legacy
N/A
publish
N/A
publish-next
N/A
Handling translation and localization
The best approach is to create general dictionaries for languages and then extend then 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 get language from browser and the format is en|es|pt for general language or country specific en-GB|es-MX|pt-BR. So when exporting locale assets, this pattern must be followed.
Bellow we have the full example of organization and usage:
- folder organization
src/assets
└ locales
├ index.ts // Exports
├ en-gb.ts // British English (just localization, no extension)
├ es.ts // General Spanish (Spain)
├ es-mx.ts // Localization for Mexico
├ es-ar.ts // Localization for Argentina
├ es-ur.ts // Localization for Uruguay
├ pt.ts // General Portuguese (Brazil)
├ pt-pt.ts // Localization for Portugales.ts and pt.ts files indicates to spanish and portuguese respectivelly, they will contain general translation for these languages and every other file that has {languageCode}-{countryCode}.{extension} will be localization only.
Bellow will have a full demonstration:
- es.ts
export default {
'Statefull component': 'Componente con estado',
'Hello <%= name %>!': 'Hola <%= name %>!',
'Subway stations': 'Estaciones de metro',
}and then in pt.ts
- es-mx.ts
// get pt
import pt from './pt'
export default {
...es, // extend it
// modify what is needed accordingly localization for mexico
'Hello <%= name %>!': 'Olá <%= name %>!',
}The only exception to language extension is English languages as the base language is already in English we don't have a en.ts file. So in that case we just put the sentences that we want to override, any word that we don't declare, will use the base sentence that is already right.
- en-gb.ts
export default {
'Subway station': 'Underground stations',
}and finally at index.ts
- index.ts
import { I18nAssets } from '@casipe/react-i18n';
export const localeAssets: I18nAssets {
'en-gb': () => import('./en-gb'),
es: () => import('./es'),
'es-ar': () => import('./es-ar'),
pt: () => import('./pt'),
'pt-br': () => import('./pt-br')
}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:
- I18n
import { localeAssets, translate, t, I18nContext } from '@/assets/locales'
;<I18n assets={localeAssets}>
<div>
<p>From here you can use translate method</p>
<p>or even get I18nContext</p>
</div>
</I18n>Using with LanguageRouter from @casipe/react-app-context
It's the same approach to organize and export files as I18n is already abstracted. Instead of giving assets to I18n, you give it to LanguageRouter.
import { LanguageRouter } from '@casipe/react-app-context'
;<LanguageRouter assetsLocales={localeAssets}>
<div>
<p>From here you can use translate method</p>
<p>or even get I18nContext</p>
</div>
</LanguageRouter>Known issues
- Returning to default language
When manually changing languages, that is a behavior that user usually won't do, to any language and then coming back to
enthat is the default, it won't translate back. Only with a total refresh or the addition of aen: () => import('./en')with all texts duplicated.
Components
I18n
I18n is a translation component to adapt your application based on given or browser language.
- Props
interface TranslationVars {
[key: string]: unknown;
}
interface I18nDictionary {
[key: string]: string;
}
interface I18nAssetImported {
default: I18nDictionary;
}
type I18nAssetAsync = () => Promise<I18nAssetImported>
interface I18nAssets {
[key: string]: I18nAssetAsync;
}
// component interface
interface I18nProps {
children: ReactNode | ReactNodeArray;
lang?: string;
country?: string;
assets?: I18nAssets;
onLoaded?: () => void;
}
interface I18nState {
dictionary: I18nDictionary;
isLoadingLanguage: boolean;
lang: string;
}
type TranslateType = (text: string, vars?: TranslationVars) => string
// context interface
interface I18nContextProps {
country: string;
dictionary: I18nDictionary;
lang: string;
translate: TranslateType;
}- Usage
import { I18nAssets, I18n, translate, t } from '@casipe/react-i18n'
const assets: I18nAssets = {
es: () => import('./es'),
pt: () => import('./pt')
}
...
<I18n lang="es" assets={assets}>
<p>{translate('Sentence')}</p>
<p>{t('Sentence')}</p>
<p>{t('Untranslated sentence')}</p>
<p>{t('Hello <%= name %>!', { name: 'Alejandro' })}</p>
<p>{t('Goodbye <%= name %>!', { name: 'Alejandro' })}</p>
<hr />
<h3 style={{ 'margin-bottom': 0 }}>
Using <mark>I18nContext/Consumer.translate</mark> method
</h3>
</I18n>
...- Inline Context consumption
import { I18n, I18nContext, t } from '@casipe/react-i18n';
...
<I18n lang="es" assets={assets}>
<I18nContext.Consumer>
{({ translate }) => {
return (
<>
<p>{translate('Sentence')}</p>
<p>{t('Sentence')}</p>
<p>{t('Untranslated sentence')}</p>
<p>{t('Hello <%= name %>!', { name: 'Alejandro' })}</p>
<p>{t('Goodbye <%= name %>!', { name: 'Alejandro' })}</p>
</>
);
}}
</I18nContext.Consumer>
</I18n>
...- useContext(I18nContext)
import { I18nContext } from '@casipe/react-i18n';
const SomeComponent = () => {
const { translate } = useContext(I18nContext);
return (
<>
<p>{translate('Sentence')}</p>
<p>{t('Sentence')}</p>
<p>{t('Untranslated sentence')}</p>
<p>{t('Hello <%= name %>!', { name: 'Alejandro' })}</p>
<p>{t('Goodbye <%= name %>!', { name: 'Alejandro' })}</p>
</>
);
};
...
<I18n lang="es" assets={assets}>
<SomeComponent />
</I18n>
...Plurals
Use an array of strings instead a string in t function as the following example: t(['text', 'text'], vars)
{{ [KEY] | [NUMBER] | [HIDE] }}
- KEY: from vars (the second parameter)
- NUMBER: if the value of KEY is greater or equal then NUMBER use that template
- HIDE: hide the value of KEY, useful when you want to use custom text
<p>
{t(
[
'no product', // default
'{{count|1}} product', // count >= 1
'{{count|2}} products', // count >= 2
'{{count|99|true}}to many products', // count >= 99 and hide value
],
{ count: 1 },
)}
</p>
// <p>1 product</p>L10n
L10n is a localization component to adapt your application based on country.
Once you set your country in a I18n wrapper, you can put a L10n component in places you want to opt to different components based on country setted.
This component MUST be used in an application that also set the I18n component.
- Props
interface L10nProps {
[key: string]: unknown;
components: { [key: string]: FC };
}- Usage
import { L10n, L18n, translate } from '@casipe/react-i18n'
const ComponentForMexico = ({ name }) => <p>Hola {name}</p>
// with template
const ComponentForBrasil = ({ name }) => (
<p>{translate('Hello <%= name %>', { name })}</p>
)
const LocalizedComponent = () => (
<I18n lang="es" assets={assets}>
<L10n
name="Name"
anotherProp="some"
components={{
mx: ComponentForMexico,
br: ComponentForBrasil,
}}
/>
</I18n>
)
// Additional props will be fowarded to the selected component