• Stars
    star
    113
  • Rank 310,115 (Top 7 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created almost 5 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A small library to provide the I18n translations on the JavaScript.

i18n.js

A small library to provide the i18n translations on the JavaScript.

Tests npm version npm downloads License: MIT

Installation

  • Yarn: yarn add i18n-js@latest
  • NPM: npm install i18n-js@latest

Usage

Setting up

First, you need to instantiate I18n with the translations' object, the main class of this library.

import { I18n } from "i18n-js";
import translations from "./translations.json";

const i18n = new I18n(translations);

The translations object is a direct export of translations defined by Ruby on Rails. To export the translations, you can use i18n-js, a Ruby gem that's completely disconnected from Rails and that can be used for the solely purpose of exporting the translations, even if your project is written in a different language. If all you care about is some basic translation mechanism, then you can set the object like this:

const i18n = new I18n({
  en: {
    hello: "Hi!",
  },
  "pt-BR": {
    hello: "Olรก!",
  },
});

Each root key is a different locale that may or may not have the script code. This library also supports locales with region code, like zh-Hant-TW.

Once everything is set up, you can then define the locale. en is both the current and default locale. To override either values, you have to use I18n#defaultLocale and I18n#locale.

i18n.defaultLocale = "pt-BR";
i18n.locale = "pt-BR";

Base translations

This library comes bundled with all base translations made available by rails-i18n. Base translations allow formatting date, numbers, and sentence connectors, among other things.

To load the base translations, use something like the following:

import { I18n } from "i18n-js";
import ptBR from "i18n-js/json/pt-BR.json";
import en from "i18n-js/json/en.json";

const i18n = new I18n({
  ...ptBR,
  ...en,
});

Updating translation store

Updating the translation store is trivial. All you have to do is calling I18n#store with the translations that need to be merged. Let's assume you've exported all your app's translations using i18n-js CLI, using a separate file for each language, like this:

  • translations/en.json
  • translations/pt-BR.json

This is how you could update the store:

import { I18n } from "i18n-js";
import ptBR from "translations/pt-BR.json";
import en from "translations/en.json";

const i18n = new I18n();

i18n.store(en);
i18n.store(ptBR);

This method will allow you to lazy load translations and them updating the store as needed.

import { I18n } from "i18n-js";

async function loadTranslations(i18n, locale) {
  const response = await fetch(`/translations/${locale}.json`);
  const translations = await response.json();

  i18n.store(translations);
}

const i18n = new I18n();
loadTranslations(i18n, "es");

Events

A change event is triggered whenever I18n#store or I18n#update is called, or when I18n#locale/I18n#defaultLocale is set. To subscribe to these changes, use the method I18n#onChange(i18n: I18n).

const i18n = new I18n();
i18n.onChange(() => {
  console.log("I18n has changed!");
});

Every change will increment the property I18n#version, so you can use it as a cache key. Also, when you subscribe to change events, I18n#onChange(i18n: I18n) will return another function that can be used to remove the event handler.

useEffect(() => {
  const unsubscribe = i18n.onChange(() => {
    // do something
  });

  return unsubscribe;
}, []);

useEffect(() => {
  console.log("I18n has been updated!");
}, [i18n.version]);

Translating messages

To translate messages, you have to use the I18n#translate, or its I18n#t alias.

i18n.locale = "en";
i18n.t("hello"); //=> Hi!

i18n.locale = "pt-BR";
i18n.t("hello"); //=> Olรก!

You can also provide an array as scope. Both calls below are equivalent.

i18n.t(["greetings", "hello"]);
i18n.t("greetings.hello");

Your translations may have dynamic values that should be interpolated. Here's a greeting message that takes a name:

const i18n = new I18n({
  en: { greetings: "Hi, %{name}!" },
  "pt-BR": { greetings: "Olรก, %{name}!" },
});

i18n.t("greetings", { name: "John" });

If the translation is an array and the entry is a string, values will be interpolated in a shallow way.

const i18n = new I18n({
  en: { messages: ["Hello there!", "Welcome back, %{name}!"] },
});

i18n.t("messages", { name: "John" });
//=> ["Hello there!", "Welcome back, John!"]

You may want to override the default interpolate function with your own, if for instance you want these dynamic values to be React elements:

const i18n = new I18n({
  en: { greetings: "Hi, %{name}!" },
  "pt-BR": { greetings: "Olรก, %{name}!" },
});

i18n.interpolate = (i18n, message, options) => {
  // ...
};

return <Text>{i18n.t("greetings", { name: <BoldText>John</BoldText> })}</Text>;

Missing translations

A translation may be missing. In that case, you may set the default value that's going to be returned.

i18n.t("missing.scope", { defaultValue: "This is a default message" });

Default messages can also have interpolation.

i18n.t("noun", { defaultValue: "I'm a {{noun}}", noun: "Mac" });

Alternatively, you can define a list of scopes that will be searched instead.

// As a scope
i18n.t("some.missing.scope", { defaults: [{ scope: "some.existing.scope" }] });

// As a simple translation
i18n.t("some.missing.scope", { defaults: [{ message: "Some message" }] });

Default values must be provided as an array of objects where the key is the type of desired translation, a scope or a message. The returned translation will be either the first scope recognized, or the first message defined.

The translation will fall back to the defaultValue translation if no scope in defaults matches and if no message default is found.

You can enable translation fallback with I18n#enableFallback.

i18n.enableFallback = true;

By default missing translations will first be looked for in less specific versions of the requested locale and if that fails by taking them from your I18n#defaultLocale.

// if i18n.defaultLocale = "en" and translation doesn't exist
// for i18n.locale = "de-DE" this key will be taken from "de" locale scope
// or, if that also doesn't exist, from "en" locale scope
i18n.t("some.missing.scope");

Custom fallback rules can also be specified for a specific language. There are three different ways of doing it so. In any case, the locale handler must be registered using i18n.locales.register().

// Using an array
i18n.locales.register("no", ["nb", "en"]);

// Using a string
i18n.locales.no.register("nb");

// Using a function.
i18n.locales.no.register((locale) => ["nb"]);

By default a missing translation will be displayed as [missing "name of scope" translation]. You can override this behavior by setting i18n.missingBehavior to "guess".

i18n.missingBehavior = "guess";

The "guess" behavior will take the last section of the scope and apply some replace rules; camel case becomes lower case and underscores are replaced with space. In practice, it means that a scope like questionnaire.whatIsYourFavorite_ChristmasPresent becomes what is your favorite Christmas present.

There's also a strategy called error, which will throw an exception every time you fetch a missing translation. This is great for development. It'll even end up on your error tracking!

i18n.missingBehavior = "error";

To detect missing translations, you can also set i18n.missingTranslationPrefix.

i18n.missingTranslationPrefix = "EE: ";

The same questionnaire.whatIsYourFavorite_ChristmasPresent scope would converted into EE: what is your favorite Christmas present. This is helpful if you want to add a check to your automated tests.

If you need to specify a missing behavior just for one call, you can provide a custom missingBehavior option.

i18n.t("missing.key", { missingBehavior: "error" });

You can completely override the missing translation strategy by setting it to a function. The following example will return null for every missing translation.

i18n.missingTranslation = () => null;

Finally, you can also create your own missing translation behavior. The example below registers a new behavior that returns an empty string in case a translation is missing.

i18n.missingTranslation.register("empty", (i18n, scope, options) => "");

Pluralization

This library has support for pluralization and by default works with English, and similar pluralized languages like Portuguese.

First, you have to define your translations with special keywords defined by the pluralization handler. The default keywords are zero, one, and other.

const i18n = new I18n({
  en: {
    inbox: {
      zero: "You have no messages",
      one: "You have one message",
      other: "You have %{count} messages",
    },
  },

  "pt-BR": {
    inbox: {
      zero: "Vocรช nรฃo tem mensagens",
      one: "Vocรช tem uma mensagem",
      other: "Vocรช tem %{count} mensagens",
    },
  },
});

To retrieve the pluralized translation you must provide the count option with a numeric value.

i18n.t("inbox", { count: 0 }); //=> You have no messages
i18n.t("inbox", { count: 1 }); //=> You have one message
i18n.t("inbox", { count: 2 }); //=> You have 2 messages

You may need to define new rules for other languages like Russian. This can be done by registering a handler with i18n.pluralization.register(). The following example defines a Russian pluralizer.

i18n.pluralization.register("ru", (_i18n, count) => {
  const mod10 = count % 10;
  const mod100 = count % 100;
  let key;

  const one = mod10 === 1 && mod100 !== 11;
  const few = [2, 3, 4].includes(mod10) && ![12, 13, 14].includes(mod100);
  const many =
    mod10 === 0 ||
    [5, 6, 7, 8, 9].includes(mod10) ||
    [11, 12, 13, 14].includes(mod100);

  if (one) {
    key = "one";
  } else if (few) {
    key = "few";
  } else if (many) {
    key = "many";
  } else {
    key = "other";
  }

  return [key];
});

You can find all rules on http://www.unicode.org/.

You can also leverage make-plural, rather than writing all your pluralization functions. For this, you must wrap make-plural's function by using useMakePlural({ pluralizer, includeZero, ordinal }):

import { ru } from "make-plural";
import { useMakePlural } from "i18n-js";

i18n.pluralization.register("ru", useMakePlural({ pluralizer: ru }));

Other options

If you're providing the same scope again and again, you can reduce the boilerplate by setting the scope option.

const options = { scope: "activerecord.attributes.user" };

i18n.t("name", options);
i18n.t("email", options);
i18n.t("username", options);

Number Formatting

Similar to Rails helpers, you can have localized number and currency formatting.

i18n.l("currency", 1990.99);
// $1,990.99

i18n.l("number", 1990.99);
// 1,990.99

i18n.l("percentage", 123.45);
// 123.450%

To have more control over number formatting, you can use the I18n#numberToHuman, I18n#numberToPercentage, I18n#numberToCurrency, I18n#numberToHumanSize, I18n#numberToDelimited and I18n#numberToRounded functions.

I18n#numberToCurrency

Formats a number into a currency string (e.g., $13.65). You can customize the format in the using an options object.

The currency unit and number formatting of the current locale will be used unless otherwise specified in the provided options. No currency conversion is performed. If the user is given a way to change their locale, they will also be able to change the relative value of the currency displayed with this helper.

Options
  • precision - Sets the level of precision (defaults to 2).
  • roundMode - Determine how rounding is performed (defaults to default.)
  • unit - Sets the denomination of the currency (defaults to "$").
  • separator - Sets the separator between the units (defaults to ".").
  • delimiter - Sets the thousands delimiter (defaults to ",").
  • format - Sets the format for non-negative numbers (defaults to "%u%n"). Fields are %u for the currency, and %n for the number.
  • negativeFormat - Sets the format for negative numbers (defaults to prepending a hyphen to the formatted number given by format). Accepts the same fields than format, except %n is here the absolute value of the number.
  • stripInsignificantZeros - If true removes insignificant zeros after the decimal separator (defaults to false).
  • raise - If true, raises exception for non-numeric values like NaN and Infinite values.
Examples
i18n.numberToCurrency(1234567890.5);
// => "$1,234,567,890.50"

i18n.numberToCurrency(1234567890.506);
// => "$1,234,567,890.51"

i18n.numberToCurrency(1234567890.506, { precision: 3 });
// => "$1,234,567,890.506"

i18n.numberToCurrency("123a456");
// => "$123a456"

i18n.numberToCurrency("123a456", { raise: true });
// => raises exception ("123a456" is not a valid numeric value)

i18n.numberToCurrency(-0.456789, { precision: 0 });
// => "$0"

i18n.numberToCurrency(-1234567890.5, { negativeFormat: "(%u%n)" });
// => "($1,234,567,890.50)"

i18n.numberToCurrency(1234567890.5, {
  unit: "&pound;",
  separator: ",",
  delimiter: "",
});
// => "&pound;1234567890,50"

i18n.numberToCurrency(1234567890.5, {
  unit: "&pound;",
  separator: ",",
  delimiter: "",
  format: "%n %u",
});
// => "1234567890,50 &pound;"

i18n.numberToCurrency(1234567890.5, { stripInsignificantZeros: true });
// => "$1,234,567,890.5"

i18n.numberToCurrency(1234567890.5, { precision: 0, roundMode: "up" });
// => "$1,234,567,891"

I18n#numberToPercentage

Formats a number as a percentage string (e.g., 65%). You can customize the format in the options hash.

Options
  • precision - Sets the level of precision (defaults to 3).
  • roundMode - Determine how rounding is performed (defaults to default.)
  • separator - Sets the separator between the units (defaults to ".").
  • delimiter - Sets the thousands delimiter (defaults to "").
  • format - Sets the format for non-negative numbers (defaults to "%n%"). The number field is represented by %n.
  • negativeFormat - Sets the format for negative numbers (defaults to prepending a hyphen to the formatted number given by format). Accepts the same fields than format, except %n is here the absolute value of the number.
  • stripInsignificantZeros - If true removes insignificant zeros after the decimal separator (defaults to false).
Examples
i18n.numberToPercentage(100);
// => "100.000%"

i18n.numberToPercentage("98");
// => "98.000%"

i18n.numberToPercentage(100, { precision: 0 });
// => "100%"

i18n.numberToPercentage(1000, { delimiter: ".", separator: "," });
// => "1.000,000%"

i18n.numberToPercentage(302.24398923423, { precision: 5 });
// => "302.24399%"

i18n.numberToPercentage(1000, { precision: null });
// => "1000%"

i18n.numberToPercentage("98a");
// => "98a%"

i18n.numberToPercentage(100, { format: "%n  %" });
// => "100.000  %"

i18n.numberToPercentage(302.24398923423, { precision: 5, roundMode: "down" });
// => "302.24398%"

I18n#numberToDelimited

Formats a number with grouped thousands using delimiter (e.g., 12,324). You can customize the format in the options object.

Options
  • delimiter - Sets the thousands delimiter (defaults to ",").
  • separator - Sets the separator between the fractional and integer digits (defaults to ".").
  • delimiterPattern - Sets a custom regular expression used for deriving the placement of delimiter. Helpful when using currency formats like INR. The regular expression must be global (i.e. it has the g flag).
Examples
i18n.numberToDelimited(12345678);
// => "12,345,678"

i18n.numberToDelimited("123456");
// => "123,456"

i18n.numberToDelimited(12345678.05);
// => "12,345,678.05"

i18n.numberToDelimited(12345678, { delimiter: "." });
// => "12.345.678"

i18n.numberToDelimited(12345678, { delimiter: "," });
// => "12,345,678"

i18n.numberToDelimited(12345678.05, { separator: " " });
// => "12,345,678 05"

i18n.numberToDelimited("112a");
// => "112a"

i18n.numberToDelimited(98765432.98, { delimiter: " ", separator: "," });
// => "98 765 432,98"

i18n.numberToDelimited("123456.78", {
  delimiterPattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/g,
});
// => "1,23,456.78"

I18n#numberToRounded

Formats a number with the specified level of precision (e.g., 112.32 has a precision of 2 if significant is false, and 5 if significant is true). You can customize the format in the options object.

Options
  • locale - Sets the locale to be used for formatting (defaults to current locale).
  • precision - Sets the precision of the number (defaults to 3). Keeps the number's precision if null.
  • RoundMode - Determine how rounding is performed (defaults to :default).
  • significant - If true, precision will be the number of significant_digits. If false, the number of fractional digits (defaults to false).
  • separator - Sets the separator between the fractional and integer digits (defaults to ".").
  • delimiter - Sets the thousands delimiter (defaults to "").
  • stripInsignificantZeros - If true removes insignificant zeros after the decimal separator (defaults to false).
Examples
i18n.numberToRounded(111.2345);
// => "111.235"

i18n.numberToRounded(111.2345, { precision: 2 });
// => "111.23"

i18n.numberToRounded(13, { precision: 5 });
// => "13.00000"

i18n.numberToRounded(389.32314, { precision: 0 });
// => "389"

i18n.numberToRounded(111.2345, { significant: true });
// => "111"

i18n.numberToRounded(111.2345, { precision: 1, significant: true });
// => "100"

i18n.numberToRounded(13, { precision: 5, significant: true });
// => "13.000"

i18n.numberToRounded(13, { precision: null });
// => "13"

i18n.numberToRounded(389.32314, { precision: 0, roundMode: "up" });
// => "390"

i18n.numberToRounded(13, {
  precision: 5,
  significant: true,
  stripInsignificantZeros: true,
});
// => "13"

i18n.numberToRounded(389.32314, { precision: 4, significant: true });
// => "389.3"

i18n.numberToRounded(1111.2345, {
  precision: 2,
  separator: ",",
  delimiter: ".",
});
// => "1.111,23"

I18n#numberToHumanSize

Formats the bytes in number into a more understandable representation (e.g., giving it 1500 yields 1.46 KB). This method is useful for reporting file sizes to users. You can customize the format in the options object.

See I18n#numberToHuman if you want to pretty-print a generic number.

Options
  • precision - Sets the precision of the number (defaults to 3).
  • roundMode - Determine how rounding is performed (defaults to default)
  • significant - If true, precision will be the number of significant_digits. If false, the number of fractional digits (defaults to true)
  • separator - Sets the separator between the fractional and integer digits (defaults to ".").
  • delimiter - Sets the thousands delimiter (defaults to "").
  • stripInsignificantZeros - If true removes insignificant zeros after the decimal separator (defaults to true)
Examples
i18n.numberToHumanSize(123)
// => "123 Bytes"

i18n.numberToHumanSize(1234)
// => "1.21 KB"

i18n.numberToHumanSize(12345)
// => "12.1 KB"

i18n.numberToHumanSize(1234567)
// => "1.18 MB"

i18n.numberToHumanSize(1234567890)
// => "1.15 GB"

i18n.numberToHumanSize(1234567890123)
// => "1.12 TB"

i18n.numberToHumanSize(1234567890123456)
// => "1.1 PB"

i18n.numberToHumanSize(1234567890123456789)
// => "1.07 EB"

i18n.numberToHumanSize(1234567, {precision: 2})
// => "1.2 MB"

i18n.numberToHumanSize(483989, precision: 2)
// => "470 KB"

i18n.numberToHumanSize(483989, {precision: 2, roundMode: "up"})
// => "480 KB"

i18n.numberToHumanSize(1234567, {precision: 2, separator: ","})
// => "1,2 MB"

i18n.numberToHumanSize(1234567890123, {precision: 5})
// => "1.1228 TB"

i18n.numberToHumanSize(524288000, {precision: 5})
// => "500 MB"

I18n#numberToHuman

Pretty prints (formats and approximates) a number in a way it is more readable by humans (e.g.: 1200000000 becomes "1.2 Billion"). This is useful for numbers that can get very large (and too hard to read).

See I18n#numberToHumanSize if you want to print a file size.

You can also define your own unit-quantifier names if you want to use other decimal units (e.g.: 1500 becomes "1.5 kilometers", 0.150 becomes "150 milliliters", etc). You may define a wide range of unit quantifiers, even fractional ones (centi, deci, mili, etc).

Options
  • precision - Sets the precision of the number (defaults to 3).
  • roundMode - Determine how rounding is performed (defaults to default).
  • significant - If true, precision will be the number of significant_digits. If false, the number of fractional digits (defaults to true)
  • separator - Sets the separator between the fractional and integer digits (defaults to ".").
  • delimiter - Sets the thousands delimiter (defaults to "").
  • stripInsignificantZeros - If true removes insignificant zeros after the decimal separator (defaults to true)
  • units - A Hash of unit quantifier names. Or a string containing an i18n scope where to find this hash. It might have the following keys:
    • integers: unit, ten, hundred, thousand, million, billion, trillion, quadrillion
    • fractionals: deci, centi, mili, micro, nano, pico, femto
  • format - Sets the format of the output string (defaults to "%n %u"). The field types are:
    • %u - The quantifier (ex.: 'thousand')
    • %n - The number
Examples
i18n.numberToHuman(123);
// => "123"

i18n.numberToHuman(1234);
// => "1.23 Thousand"

i18n.numberToHuman(12345);
// => "12.3 Thousand"

i18n.numberToHuman(1234567);
// => "1.23 Million"

i18n.numberToHuman(1234567890);
// => "1.23 Billion"

i18n.numberToHuman(1234567890123);
// => "1.23 Trillion"

i18n.numberToHuman(1234567890123456);
// => "1.23 Quadrillion"

i18n.numberToHuman(1234567890123456789);
// => "1230 Quadrillion"

i18n.numberToHuman(489939, { precision: 2 });
// => "490 Thousand"

i18n.numberToHuman(489939, { precision: 4 });
// => "489.9 Thousand"

i18n.numberToHuman(489939, { precision: 2, roundMode: "down" });
// => "480 Thousand"

i18n.numberToHuman(1234567, { precision: 4, significant: false });
// => "1.2346 Million"

i18n.numberToHuman(1234567, {
  precision: 1,
  separator: ",",
  significant: false,
});
// => "1,2 Million"

i18n.numberToHuman(500000000, { precision: 5 });
// => "500 Million"

i18n.numberToHuman(12345012345, { significant: false });
// => "12.345 Billion"

Non-significant zeros after the decimal separator are stripped out by default (set stripInsignificantZeros to false to change that):

i18n.numberToHuman(12.00001);
// => "12"

i18n.numberToHuman(12.00001, { stripInsignificantZeros: false });
// => "12.0"

You can also use your own custom unit quantifiers:

i18n.numberToHuman(500000, units: { unit: "ml", thousand: "lt" })
// => "500 lt"

If in your I18n locale you have:

---
en:
  distance:
    centi:
      one: "centimeter"
      other: "centimeters"
    unit:
      one: "meter"
      other: "meters"
    thousand:
      one: "kilometer"
      other: "kilometers"
    billion: "gazillion-distance"

Then you could do:

i18n.numberToHuman(543934, { units: "distance" });
// => "544 kilometers"

i18n.numberToHuman(54393498, { units: "distance" });
// => "54400 kilometers"

i18n.numberToHuman(54393498000, { units: "distance" });
// => "54.4 gazillion-distance"

i18n.numberToHuman(343, { units: "distance", precision: 1 });
// => "300 meters"

i18n.numberToHuman(1, { units: "distance" });
// => "1 meter"

i18n.numberToHuman(0.34, { units: "distance" });
// => "34 centimeters"

Date Formatting

The I18n#localize (or its alias I18n#l) can accept a string, epoch time integer or a Date object. You can see below the accepted formats:

// yyyy-mm-dd
i18n.l("date.formats.short", "2009-09-18");

// yyyy-mm-dd hh:mm:ss
i18n.l("time.formats.short", "2009-09-18 23:12:43");

// JSON format with local Timezone (part of ISO-8601)
i18n.l("time.formats.short", "2009-11-09T18:10:34");

// JSON format in UTC (part of ISO-8601)
i18n.l("time.formats.short", "2009-11-09T18:10:34Z");

// Epoch time
i18n.l("date.formats.short", 1251862029000);

// mm/dd/yyyy
i18n.l("date.formats.short", "09/18/2009");

// Date object
i18n.l("date.formats.short", new Date());

You can also add placeholders to the date format:

const i18n = new I18n({
  date: {
    formats: {
      ordinalDay: "%B %{day}",
    },
  },
});

i18n.l("date.formats.ordinalDay", "2009-09-18", { day: "18th" }); // Sep 18th

If you prefer, you can use the I18n#toTime and I18n#strftime functions directly to format dates.

var date = new Date();
i18n.toTime("date.formats.short", "2009-09-18");
i18n.toTime("date.formats.short", date);
i18n.strftime(date, "%d/%m/%Y");

The accepted formats for i18n.strftime are:

%a  - The abbreviated weekday name (Sun)
%A  - The full weekday name (Sunday)
%b  - The abbreviated month name (Jan)
%B  - The full month name (January)
%c  - The preferred local date and time representation
%d  - Day of the month (01..31)
%-d - Day of the month (1..31)
%H  - Hour of the day, 24-hour clock (00..23)
%-H - Hour of the day, 24-hour clock (0..23)
%k  - Hour of the day, 24-hour clock (0..23)
%I  - Hour of the day, 12-hour clock (01..12)
%-I - Hour of the day, 12-hour clock (1..12)
%l  - Hour of the day, 12-hour clock (1..12)
%m  - Month of the year (01..12)
%-m - Month of the year (1..12)
%M  - Minute of the hour (00..59)
%-M - Minute of the hour (0..59)
%p  - Meridian indicator (AM  or  PM)
%P  - Meridian indicator (am  or  pm)
%S  - Second of the minute (00..60)
%-S - Second of the minute (0..60)
%w  - Day of the week (Sunday is 0, 0..6)
%y  - Year without a century (00..99)
%-y - Year without a century (0..99)
%Y  - Year with century
%z  - Timezone offset (+0545)
%Z  - Timezone offset (+0545)

Check out __tests__/strftime.test.ts file for more examples!

Finally, you can also diplay relative time strings using I18n#timeAgoInWords.

const to = new Date();
const from = to.getTime() - 60 * 60 * 1000; // ~1h ago.

i18n.timeAgoInWords(from, to);
//=> about 1 hour

Using pluralization and number formatting together

Sometimes you might want to display translation with formatted number, like adding thousand delimiters to displayed number You can do this:

const i18n = new I18n({
  en: {
    points: {
      one: "1 Point",
      other: "{{points}} Points",
    },
  },
});

const points = 1234;

i18n.t("points", {
  count: points,
  points: i18n.formatNumber(points),
});

Output should be 1,234 points.

Other helpers

I18n#toSentence(list, options)

i18n.toSentence(["apple", "banana", "pineapple"]);
//=> apple, banana, and pineapple.

Troubleshooting

I'm getting an error like Unable to resolve "make-plural" from "node modules/i18n-js/dist/import/Pluralization.js"

make-plural uses .mjs files. You need to change your build pipeline to also consider these files.

If you're using react-native, you need to change your metro config to consider .mjs. Try doing something like this (you may need to adapt your code based on existing changes).

const { getDefaultConfig } = require("metro-config");

module.exports = (async () => {
  const {
    resolver: { assetExts, sourceExts },
  } = await getDefaultConfig();

  return {
    resolver: {
      sourceExts: [...sourceExts, "mjs"],
    },
  };
})();

I'm getting an error like SyntaxError: Unexpected end of JSON input or Uncaught SyntaxError: Unexpected token ;

You may get such error if you're trying to load empty JSON files with import data from "file.json". This has nothing to do with I18n and is related to how your JSON file is loaded. JSON files must contain valid JSON data.

Similarly, make sure you're writing valid JSON, and not JavaScript. For instance, if you write something like {};, you'll get an error like Uncaught SyntaxError: Unexpected token ;.

My JSON contains a flat structure. How can I load and use it with I18n.js?

I18n.js expects a nested object to represent the translation tree. For this reason, you cannot use an object like the following by default:

{
  "en.messages.hello": "hello",
  "pt-BR.messages.hello": "olรก"
}

One solution is using something like the following to transform your flat into a nested object:

const { set } = require("lodash");

const from = {
  "en.messages.hello": "hello",
  "pt-BR.messages.hello": "olรก",
};

function flatToNestedObject(target) {
  const nested = {};

  Object.keys(target).forEach((path) => set(nested, path, target[path]));

  return nested;
}

console.log(flatToNestedObject(from));
// {
//   en: { messages: { hello: 'hello' } },
//   'pt-BR': { messages: { hello: 'olรก' } }
// }

You can also use something like flat to perform the same transformation.

Maintainer

Contributors

Contributing

For more details about how to contribute, please read https://github.com/fnando/i18n/blob/main/CONTRIBUTING.md.

License

The gem is available as open source under the terms of the MIT License. A copy of the license can be found at https://github.com/fnando/i18n/blob/main/LICENSE.md.

Code of Conduct

Everyone interacting in the i18n project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

More Repositories

1

i18n-js

It's a small library to provide the I18n translations on the Javascript. It comes with Rails support.
Ruby
3,702
star
2

browser

Do some browser detection with Ruby. Includes ActionController integration.
Ruby
2,388
star
3

kitabu

A framework for creating e-books from Markdown using Ruby. Using the Prince PDF generator, you'll be able to get high quality PDFs. Also supports EPUB, Mobi, Text and HTML generation.
Ruby
659
star
4

recurrence

A simple library that handles recurring events.
Ruby
556
star
5

sparkline

Generate SVG sparklines with JavaScript without any external dependency.
JavaScript
501
star
6

paypal-recurring

PayPal Express Checkout API Client for recurring billing.
Ruby
257
star
7

cpf_cnpj

๐Ÿ‡ง๐Ÿ‡ท Validate, generate and format CPF/CNPJ numbers. Include command-line tools.
Ruby
220
star
8

password_strength

Check password strength against several rules. Includes ActiveRecord/ActiveModel support.
JavaScript
182
star
9

cpf

๐Ÿ‡ง๐Ÿ‡ท Validate, generate and format CPF numbers
TypeScript
161
star
10

pagseguro

Um plugin para o Ruby on Rails que permite utilizar o PagSeguro
Ruby
141
star
11

coupons

Coupons is a Rails engine for creating discount coupons.
Ruby
140
star
12

cpf_cnpj.js

Validate, generate and format CPF/CNPJ numbers
JavaScript
118
star
13

uptime_checker

Check if your sites are online for $7/mo.
112
star
14

module

Define namespaces as constructor functions (or any object).
JavaScript
105
star
15

qe

A simple interface over several background job libraries like Resque, Sidekiq and DelayedJob.
Ruby
102
star
16

has_calendar

A view helper that creates a calendar using a table. You can easily add events with any content.
Ruby
101
star
17

sinatra-subdomain

Separate routes for subdomains in Sinatra apps
Ruby
96
star
18

validators

Some ActiveModel/ActiveRecord validators
Ruby
96
star
19

dotfiles

My dotfiles
Shell
88
star
20

rack-api

Create web app APIs that respond to one or more formats using an elegant DSL.
Ruby
86
star
21

notifier

Send system notifications on several platforms with a simple and unified API. Currently supports Notification Center, Libnotify, OSD, KDE (Knotify and Kdialog) and Snarl
Ruby
84
star
22

factory_bot-preload

Preload factories (factory_bot) just like fixtures. It will be easy and, probably, faster!
Ruby
83
star
23

cnpj

๐Ÿ‡ง๐Ÿ‡ท Validate, generate and format CNPJ numbers
TypeScript
81
star
24

dispatcher-js

Simple jQuery dispatcher for web apps.
JavaScript
75
star
25

gem-open

Open gems into your favorite editor by running a specific gem command
Ruby
69
star
26

test_notifier

Display system notifications (dbus, growl and snarl) after running tests. It works on Mac OS X, Linux and Windows. Powerful when used with Autotest ZenTest gem for Rails apps.
Ruby
63
star
27

test_squad

Running JavaScript tests on your Rails app, the easy way.
Ruby
56
star
28

vscode-linter

Extension for code linting, all in one package. New linters can be easily added through an extension framework.
TypeScript
56
star
29

has_friends

Add friendship support to Rails apps with this plugin
Ruby
55
star
30

breadcrumbs

Breadcrumbs is a simple Rails plugin that adds a breadcrumbs object to controllers and views.
Ruby
55
star
31

minitest-utils

Some utilities for your Minitest day-to-day usage.
Ruby
52
star
32

photomatic

Your photography is what matters.
Ruby
50
star
33

normalize_attributes

Sometimes you want to normalize data before saving it to the database like down casing e-mails, removing spaces and so on. This Rails plugin allows you to do so in a simple way.
Ruby
49
star
34

burgundy

A simple wrapper for objects (think of Burgundy as a decorator/presenter) in less than 150 lines.
Ruby
49
star
35

ar-uuid

Override migration methods to support UUID columns without having to be explicit about it.
Ruby
46
star
36

rubygems_proxy

Rack app for caching RubyGems files. Very useful in our build server that sometimes fails due to our network or rubygems.org timeout.
Ruby
43
star
37

post_commit

Post commit allows you to notify several services with a simple and elegant DSL. Five services are supported for now: Basecamp, Campfire, FriendFeed, LightHouse and Twitter.
Ruby
42
star
38

permalink

Add permalink support to Rails apps with this plugin
Ruby
40
star
39

keyring-node

Simple encryption-at-rest with key rotation support for Node.js.
JavaScript
39
star
40

superconfig

Access environment variables. Also includes presence validation, type coercion and default values.
Ruby
38
star
41

sublime-better-ruby

Sublime Text Ruby package (snippets, builder, syntax highlight)
Ruby
37
star
42

simple_presenter

A simple presenter/facade/decorator/whatever implementation.
Ruby
33
star
43

voltage

A simple observer implementation on POROs (Plain Old Ruby Object) and ActiveRecord objects.
Ruby
31
star
44

redis-settings

Store application and user settings on Redis. Comes with ActiveRecord support.
Ruby
31
star
45

tokens

Add token support to Rails apps with this plugin
Ruby
31
star
46

boppers

A simple bot framework for individuals.
Ruby
29
star
47

babel-schmooze-sprockets

Add Babel support to sprockets using Schmooze.
JavaScript
29
star
48

streamdeck

A lean framework for developing Elgato Stream Deck plugins.
TypeScript
28
star
49

sublime-text

My SublimeText settings
26
star
50

has_ratings

Add rating support to Rails apps with this plugin
Ruby
25
star
51

commentable

Add comment support to Rails apps with this plugin
Ruby
23
star
52

sinatra-basic-auth

Authentication with BasicAuth that can require different credentials for different realms.
Ruby
23
star
53

rails-routes

Enable config/routes/*.rb on your Rails application.
Ruby
23
star
54

swiss_knife

Here's my swiss-knife Rails helpers.
Ruby
21
star
55

aitch

A simple HTTP client.
Ruby
21
star
56

messages-app

Use alert messages in your README.
HTML
19
star
57

rails-env

Avoid environment detection on Rails
Ruby
19
star
58

svg_optimizer

Some SVG optimization based on Node's SVGO
Ruby
18
star
59

defaults

Add default value for ActiveRecord attributes
Ruby
18
star
60

ar-check

Enable PostgreSQL's CHECK constraints on ActiveRecord migrations
Ruby
17
star
61

email_data

This project is a compilation of datasets related to emails. Includes disposable emails, disposable domains, and free email services.
Ruby
17
star
62

simple_auth

SimpleAuth is an authentication library to be used when everything else is just too complicated.
Ruby
17
star
63

attr_keyring

Simple encryption-at-rest with key rotation support for Ruby.
Ruby
17
star
64

has_bookmarks

Add bookmark support to Rails apps with this plugin
Ruby
17
star
65

sublime-text-screencasts

Screencasts sobre Sublime Text
HTML
17
star
66

paginate

Paginate collections using SIZE+1 to determine if there is a next page. Includes ActiveRecord and ActionView support.
Ruby
16
star
67

pry-meta

Meta package that requires several pry extensions.
Ruby
15
star
68

react-starter-pack

Starter-pack for react + webpack + hot reload + mocha + enzyme + production build
JavaScript
15
star
69

check_files

Check non-reloadable files changes on Rails apps.
Ruby
15
star
70

url_signature

Create and verify signed urls. Supports expiration time.
Ruby
15
star
71

activities

Activities is a gem that enables social activities in ActiveRecord objects.
Ruby
14
star
72

using-es6-with-asset-pipeline-on-ruby-on-rails

Example for my article about ES6 + Asset Pipeline
Ruby
14
star
73

whiteboard

A small app using Canvas + Socket.IO to provide a shared whiteboard.
JavaScript
14
star
74

sublime-better-rspec

Better RSpec syntax highlighting, with matchers for v3. Also includes implementation/spec toggling command.
Python
13
star
75

ar-sequence

Add support for PostgreSQL's SEQUENCE on ActiveRecord migrations.
Ruby
13
star
76

page_meta

Easily define <meta> and <link> tags. I18n support for descriptions, keywords and titles.
Ruby
13
star
77

boppers-uptime

A bopper to check if your sites are online.
Ruby
13
star
78

has_versions

A simple plugin to version ActiveRecord objects
Ruby
13
star
79

csr

Generate CSR (Certificate Signing Request) using Ruby and OpenSSL.
Ruby
13
star
80

haikunate

Generate Heroku-like memorable random names like adorable-ox-1234.
Ruby
13
star
81

module-component

Define auto-discoverable HTML UI components using Module.js.
JavaScript
12
star
82

alfred-workflows

Alfred workflows
12
star
83

twitter_cleanup

Remove old tweets periodically using Github Actions
Ruby
12
star
84

storage

This gem provides a simple API for multiple storage backends. Supported storages: Amazon S3 and FileSystem.
Ruby
12
star
85

has_layout

Add conditional layouts with ease
Ruby
11
star
86

kalendar

A view helper that creates a calendar using a table. You can easily add events with any content.
Ruby
11
star
87

stellar-paperwallet

Make paper wallets to keep your Stellar addresses safe.
JavaScript
10
star
88

tagger

Tagging plugin for Ruby on Rails apps
Ruby
10
star
89

page_title

Set the page title on Rails apps.
Ruby
10
star
90

dogo

A simple URL shortener service backed by Redis.
Ruby
10
star
91

has_notifications

This plugin was created to act as a proxy between different notification systems (Mail, Jabber, etc) based on the user's preferences.
Ruby
10
star
92

formatter

has_markup is an ActiveRecord plugin that integrates Tidy, Markdown, Textile and sanitize helper method into a single plugin.
Ruby
10
star
93

ar-enum

Add support for creating `ENUM` types in PostgreSQL with ActiveRecord
Ruby
9
star
94

shortcuts

Because mouse is for noobies.
Ruby
9
star
95

ar-timestamptz

Make ActiveRecord's PostgreSQL adapter use timestamptz as datetime columns.
Ruby
9
star
96

parsel-js

Encrypt and decrypt data with a given key.
JavaScript
8
star
97

ember-and-rails

Ruby
8
star
98

access_token

Access token for client-side and API authentication.
Ruby
8
star
99

sinatra-oauth-twitter

Sample app used on Guru-SP meetup
Ruby
8
star
100

need_help

A simple help/FAQ system using Rails Engine.
Ruby
8
star