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

@esgettext/runtime

v1.3.4

Published

A gettext-like translation runtime for JavaScript aka EcmaScript

Downloads

71

Readme

@esgettext/runtime

GNU gettext-alike translation runtime library.

Table of Contents

API Documentation

If you are already familiar with the concepts of the esgettext runtime library, you can go straight to the API documentation.

Internationalizing Hello World

You have written this little piece of JavaScript:

console.log('Hello, world!');

What are the steps needed to internationalize it with this library?

Choosing a Textdomain

First, you have to choose a unique identifier for your project so that the translation catalogs will have a unique name. You are almost free in what you are choosing but you have to keep in mind that the textdomain will be part of a URI or filename and therefore a couple of rules apply:

  • A textdomain must not contain a slash ("/").
  • A textdomain should not contain a colon (":"), because of Windows.
  • A textdomain should not contain a backslash (""), because of Windows.
  • A textdomain should not contain binary characters, because of common sense.

In general, you should only use lowercase characters that are valid inside hostnames, namely "a-z", "0-9", the hyphen "-", and the dot ".".

If possible, follow this advice:

  1. If your organization has a domain, use the reverse(!) domain name followed by the name of your product for example "com.example.hello"
  2. Otherwise, if your project sources are publicly hosted use the reverse domain name of your hoster followed by an identifier of your project. For example, the textdomain for https://github.com/gflohr/esgettext would then be "com.github.gflohr.esgettext".
  3. Otherwise, use common sense.

We will use the the third rule and pick the textdomain "hello".

Install the Library

You normally install the library with npm or yarn.

With npm:

$ npm install --save @esgettext/runtime

Or with yarn:

$ yarn add @esgettext/runtime

Import the Library

How to import the library, depends on your environment.

If you have the import keyword (TypeScript or ES module syntax):

import { Textdomain } from '@esgettext/runtime';

If you can use require:

const { Textdomain } = require('@esgettext/runtime');

If you are writing javascript loaded by a browser:

<script src="https://cdn.jsdelivr.net/npm/@esgettext/runtime/dist/esgettext.min.js"></script>
<script>
	var Textdomain = esgettext.Textdomain;
	// ... your code follows.
</script>

If you want a specific version, you can do like this:

<script src="https://cdn.jsdelivr.net/npm/@esgettext/[email protected]/_bundles/runtime.min.js"></script>
<script>
	var Textdomain = esgettext.Textdomain;
	// ... your code follows.
</script>

Prepare Your Sources

Change your hello.js to read like this:

import { Textdomain } from '@esgettext/runtime';

Textdomain.locale = 'fr';
const gtx = Textdomain.getInstance('hello');
gtx.bindtextdomain('/assets/locale');
gtx.resolve().then(function () {
	console.log(gtx._('Hello, world!'));
});

Hint: If you cannot use import, use one of the other techniques shown above to make the Textdomain class available in your source!

What is happening here?

First, you set the locale (resp. language) to the desired value. Here we choose "fr" for French. See the section Selecting the Preferred Language with selectLocale() for a more flexible way to select the user's locale.

You then get an instance of a Textdomain object. You cannot use the regular constructor because it is private! The argument to the getInstance() class method is the textdomain you have chosen.

You then have to tell the library where to find translations for the "hello" textdomain. You do that with the instance method bindtextdomain() that receives a (base) directory as its argument. The actual translation catalog would then be searched at /assets/locale/fr/LC_MESSAGES/hello.mo.json (or hello.mo or hello.json, depending on your environment).

Don't worry that there are no translations at the moment. Failure is handled gracefully by the library falling back to using the original, untranslated strings. See the docs for tools for instructions on how to create translation catalogs.

You then have to resolve() the translations. The method returns a promise, and your actual code should be moved into the promise's then() method.

Now that everything is loaded you can translate all messages by replacing 'some string' with gtx._('some string'). That's it!

Translation Methods

The method _() is the simplest but by far not the only translation method that esgettext has to offer.

Simple Translations With _()

The method _() has already been introduced:

console.log(gtx._('Hello, world!'));

It returns the translation of its argument or just the argument if no translation can be found.

Variable Interpolation With _x()

Imagine you want to express RGB colors in human-readable form. The esgettext way to do that goes like this:

console.log(gtx._x(
	'red: {r}, green: {g}, blue: {b}', {
		r: red,
		g: green,
		b: blue
	}
);

You use placeholders surrounded by curly braces ({}), and provide an object as an additional argument where the keys are the placeholder names and the values, the respective values to be interpolated.

Placeholder names must be valid C identifiers: They must begin with a lower- or uppercase letter ("a" to "z", "A" to "Z") or an underscore ("_") followed by an arbitrary number of characters from the same set or decimal digits ("0" to "9"). In regular espression syntax: /^[_a-zA-Z][_a-zA-Z0-9]*$/.

Plural Forms With _nx()

Translating a string with plural expressions is unfortunately somewhat convoluted:

console.log(
	gtx._nx('One file copied', '{count} files copied.', count, {
		count: count,
	}),
);

Count, count, count, count.

If the variable count has the value 42, the above would yield in English: "42 files copied.". If count had the value 1, it would yield: "One file copied.".

The method _nx() has this signature:

_nx(
	(msgid: string),
	(msgidPlural: string),
	(numberOfItems: number),
	(placeholders: Object),
);

Many times, the placeholder name will equal the variable name, so that you see that name once inside the plural string as the placeholder, then as the argument numberOfItems, then as the key in the placeholder hash, and again as the value for that key.

Note that there is also a method _n() that does the same as _nx() but without inpterpolation but it only exists for completeness and is useless for practical purposes.

Message Context With _p()

It is sometimes possible that one English sentence can have multiple, different meanings in another language. For example "Sun" can mean our planet's star or the abbreviation of "Sunday". In order to allow translators to provide accurate translations for each meaning, you can distinguish them by message context:

weekday = gtx._p('wday', 'Sun');
star = gtx._p('star', 'Sun');

The first argument is the context (a free-form string), the second is the string to translate.

It is actually sufficient to use a context just for one of the two cases.

There are also methods _px(), when you need placeholders or _npx(), when you need placeholders and plural forms in addition to a message context.

Specific Locale with _l

Most of the time, the locale resp. language is set just once by setting the static property locale:

Textdomain.locale = 'fr';
console.log(gtx._('Hello, world!'));

Alternatively, you can pass the locale with every call to a translation method:

console.log(gtx._l('fr', 'Hello, world!'));

There is not just _l(), but actually all of the above mentioned methods have versions with a leading _l, like _lx() or as complicated as _lnpx().

One use-case for the _l() family of methods would be a web server. A web server handles requests asynchronously, and a global locale doesn't make sense. Instead, the language is typically bound to the request or response and should be taken from there.

TODO: Gender-Specific Translations

This is on the todo list for a future version. You will be able to do something like:

msg = gtx._g(
	user.gender,
	'They have liked your photo.',
	'She has liked your photo.',
);

Selecting the Preferred Language with selectLocale()

Negotiating the preferred locale can be performed with the help of esgettext. If your application supports the locales "en-US", "en-GB", "fr-FR", and "de-DE", you can select a suitable locale for the current user by calling Textdomain.selectlocale

Textdomain.locale = Textdomain.selectLocale(['en-US', 'en-GB', 'fr-FR']);

Textdomain.selectLocale() will return the most suitable locale for the current user. For browser code, the browser will be queried for the user language preferences, for server code the environment variables LANGUAGE, LC_ALL, LANG, and LC_MESSAGES will be queried in that order.

You can also explicitly specify, which locales the user has requested by passing a second argument:

Textdomain.locale = Textdomain.selectLocale(
	['en-US', 'en-GB', 'fr-FR'], // Supported by the application.
	['de-DE', 'fr-FR'],
); // Requested by the user.

Internationalizing a Library

Internationalizing a library is very simple. Take this sample library:

function greet(name) {
	return `Hello, ${name}`;
}

Internationalized, it would look like this:

import { Textdomain } from '@esgettext/runtime';

const gtx = Textdomain.getInstance('hello-library');
gtx.bindtextdomain('/assets/locale');

function greet(name) {
	return gtx._x('Hello, {name}', { name: name });
}

The main differences to an internationalized application are:

  • You do not set Textdomain.locale because the main application (the application that loads your library) does it.
  • You do not call resolve() because whenever the main application calls resolve(), the catalogs for your library will also be loaded.

Frequently-Asked Questions

Why do Template Strings not Work?

A common error is to use template strings with interpolations as arguments to the translation functions, for example:

console.log(gtx._(`red: ${red}, green: ${green}, blue: ${blue}`);

That seems to work for English but the string never gets translated.

The reason is that the argument to _() is the lookup key into the translation database but that key has to be constant. The method receives an argument like "red: 127, green: 63, blue: 31" because the JavaScript engine has already interpolated the variables into the string. But that string does not exist in the database.

You have to use _x() instead:

console.log(gtx._x(
	'red: {r}, green: {g}, blue: {b}', {
		r: red,
		g: green,
		b: blue
	}
);

What Does the Error "template literals with embedded expressions are not allowed as arguments to gettext functions because they are not constant" Mean?

See Why do Template Strings not Work? above! The extractor esgettext-xgettext complains that you are using a template string with interpolated expressions.

Copyright

Copyright (C) 2020-2024 Guido Flohr [email protected], all rights reserved.

This software is available under the terms and conditions of the WTFPL.