npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@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:

  1. initialLanguage (component prop)
  2. stored cookie (is only store if cookieAttributes are given)
  3. browser language
  4. 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()} />
    </>
  );
};