Eleventy plugin which adds i18n support with Gettext string translation and moment.js date and times localization.
Gettext is commonly used in Linux C and WordPress worlds. It comes with a few handy features:
- Can extract translation keys from source code. Poedit configuration for translations extraction.
- Supports pluralization.
- Translation keys are their own translation fallback value. It means that if you don't have a translation file or didn't translate some keys, the value of the key itself is used as the translated value.
- PO files editors exists, like Poedit.
In addition to Gettext features, this plugin:
- Integrates
printf()
for enhanced string formatting capabilities - Integrates
moment.js
for date and time localization - Integrates Moment Timezone Plugin for timezone management
Available on npm.
npm install eleventy-plugin-i18n-gettext --save
Create directories at the site root you can use either a simple language code (e.g. en
) or language code with country code suffix (e.g. en-us
).
Locale folder casing must be exactly the same in locales
and src
. In this example I choose lowercase in order to stick to Eleventy slugification configuration.
├─ locales
└─ fr-fr
├─ messages.mo
└─ messages.po
└─ pt
├─ messages.mo
└─ messages.po
├─ src
└─ fr-fr
└─ fr-fr.11tydata.js
└─ pt
└─ pt.11tydata.js
└─ en
└─ en.11tydata.js
The easiest way to create messages.po
files, is to copy them from the demo code source.
messages.po
files store translations in plain text.messages.mo
files are compiled frommessages.po
. Poedit handle the creation of these files automatically, pushing them into your code repository is recommended.
In these files we enhance the Eleventy directory data object with i18n.enhance11tydata(obj, locale, dir?)
.
// xx.11tydata.js
const i18n = require('eleventy-plugin-i18n-gettext')
module.exports = () => {
return i18n.enhance11tydata({}, __dirname)
}
Open up your Eleventy config file (probably .eleventy.js
), import the plugin and use addPlugin
.
// .eleventy.js
const i18n = require('eleventy-plugin-i18n-gettext')
module.exports = function (eleventyConfig) {
eleventyConfig.addPlugin(i18n, {
localesDirectory: 'locales',
parserMode: 'po',
javascriptMessages: 'messages.js',
tokenFilePatterns: [
'src/**/*.njk',
'src/**/*.js'
],
localeRegex: /^(?<lang>.{2})(?:-(?<country>.{2}))*$/
})
}
On activation, the plugin:
- load custom options
- attaches to
beforeBuild
andbeforeWatch
Eleventy events
When beforeBuild
and beforeWatch
events are raised, the plugin:
- browses every folders of the
localesDirectory
- loads/reloads translations from
messages.po
files (ormessages.mo
) - finds files depending on
tokenFilePatterns
- searches in these files for translations keys
- creates or update the
javascriptMessages
file with these translation keys
Type: string
| Default: locales
Name of the directory where messages.po
, messages.mo
and messages.js
files are located.
It's relative to the Node process current working directory, usually the directory where is located package.json
and from where npm run
commands are executed.
Type: string
| Default: po
| AllowedValues: po
, mo
By default, gettext-parser
is configured to parse messages.po
text files. For large translations it may be more efficient to parse messages.mo
binary files.
Type: string
| Default: messages.js
Name of the file where this plugin stores translation keys found in code source files. It's a path relative to localesDirectory
.
Type: string[]
| Default: ['src/**/*.njk', 'src/**/*.js']
Glob patterns used to know which code source files to search for translation keys.
It's relative to the Node process current working directory, usually the directory where is located package.json
and from where npm run
commands are executed.
Type: Regex
| Default: /^(?<lang>.{2})(?:-(?<country>.{2}))*$/
This Regex is used to identify which part of locale directories are the language and the country.
The default value identifies standard locales values: nl-be
, nl
, fr-be
, fr
, ...
For example, /^(?:(?<country>.{2}))*(?<lang>.{2})$/
use the same locales pattern as used by Apple: benl
, nl
, befr
, fr
, ...
📌 In both Regex, country part can be omitted but not the language part.
Returns: object
Attaches additional properties and methods to obj
and returns it:
Type | Name |
---|---|
Property | lang |
Property | langDir |
Property | locale |
Method | _(key, ...args) |
Method | _i(key, obj) |
Method | _n(singular, plural, count, ...args) |
Method | _ni(singular, plural, count, obj) |
Method | _d(format, date, timezone?) |
Method | _p(basePath) |
lang
and langDir
properties are meant to be used in the <html>
tag.
locale
property is meant to be used in custom filters and shortcodes.
Type: object
Contains the custom data you want to use in your templates.
Type: string
The locale as a simple language code (e.g. en
) or language code with country code suffix (e.g. en-us
).
Type: string
| Default: ltr
| AllowedValues: ltr
, rtl
The locale direction, left-to-right or right-to-left.
Returns: string
Retrieve a Gettext translated string then apply printf()
on it.
Type: string
The locale as a simple language code (e.g. en
) or language code with country code suffix (e.g. en-us
).
Type: string
The translation key to translate.
Type: string[]
Arguments sent to printf()
.
Returns: string
Retrieve a Gettext translated string then apply template literals/string interpolation
on it.
Type: string
The locale as a simple language code (e.g. en
) or language code with country code suffix (e.g. en-us
).
Type: string
The translation key to translate.
Type: obj
A simple object which properties will be used as variables for template literals/string interpolation
.
Returns: string
Retrieve a Gettext singular or plural translated string then apply printf()
on it.
Type: string
The locale as a simple language code (e.g. en
) or language code with country code suffix (e.g. en-us
).
Type: string
The singular form of the translation key to translate.
Type: string
The plural form of the translation key to translate.
Type: int
The quantity which helps Gettext to determine whether to return the singular or the plural translated value.
Type: string[]
Arguments sent to printf()
.
Returns: string
Retrieve a Gettext singular or plural translated string then apply template literals/string interpolation
on it.
Type: string
The locale as a simple language code (e.g. en
) or language code with country code suffix (e.g. en-us
).
Type: string
The singular form of the translation key to translate.
Type: string
The plural form of the translation key to translate.
Type: int
The quantity which helps Gettext to determine whether to return the singular or the plural translated value.
Type: obj
A simple object which properties will be used as variables for template literals/string interpolation
.
Returns: string
Return the localized form of a date using moment.js
localized formats.
Type: string
The locale as a simple language code (e.g. en
) or language code with country code suffix (e.g. en-us
).
Type: string
A moment.js
localized format
Time | LT | 8:30 PM |
Time with seconds | LTS | 8:30:25 PM |
Month numeral, day of month, year | L | 09/04/1986 |
l | 9/4/1986 | |
Month name, day of month, year | LL | September 4, 1986 |
ll | Sep 4, 1986 | |
Month name, day of month, year, time | LLL | September 4, 1986 8:30 PM |
lll | Sep 4, 1986 8:30 PM | |
Month name, day of month, day of week, year, time | LLLL | Thursday, September 4, 1986 8:30 PM |
llll | Thu, Sep 4, 1986 8:30 PM |
Type: string
Any type of string that moment()
can use.
Type: string
A timezone string listed in tz database time zones
Returns: string
Returns basePath
prefixed with locale
.
The main intent of this method is to be used in the permalink
parameter of templates.
Type: string
The locale as a simple language code (e.g. en
) or language code with country code suffix (e.g. en-us
).
Type: string
Path which will be prefixed with the locale.
In this context, locale
parameter is useless.
It's set by i18n.enhance11tydata(obj, locale, dir?)
in xx.11tydata.js
data directory files.
<!-- index.njk -->
<html lang="{{ lang }}" dir="{{ langDir }}">
<body>
<div>{{ _('I like Gettext') }}</div>
<div>{{ _('%s and I like Gettext as much as %s', 'Bob', 'John') }}</div>
<div>{{ _i('${friend} and I like Gettext as much as ${chief}', { friend: 'Bob', chief: 'John' }) }}</div>
<div>{{ _n('I like Gettext', 'They like Gettext', peopleCount) }}</div>
<div>{{ _n('I like Gettext as much as %s', 'They like Gettext as much as %s', peopleCount, 'John') }}</div>
<div>{{ _ni('I like Gettext as much as ${chief}', 'They like Gettext as much as ${chief}', peopleCount, { chief: 'John' }) }}</div>
<div>{{ _d('LL', page.date) }}</div>
<div>{{ _d('LL', '2020-08-01T21:57:00.000Z', 'Asia/Bangkok') }}</div>
<div>{{ _d('LL', 1596319020000, 'UTC') }}</div>
<div>{{ _p('/') | url }}</div>
</body>
</html>
In this context, locale
parameter is mandatory.
<!-- index.njk -->
<html lang="{{ lang }}" dir="{{ langDir }}">
<body>
<div>{% _ locale, 'I like Gettext' %}</div>
<div>{% _ locale, '%s and I like Gettext as much as %s', 'Bob', 'John' %}</div>
<div>{% _i locale, '${friend} and I like Gettext as much as ${chief}', { friend: 'Bob', chief: 'John' } %}</div>
<div>{% _n locale, 'I like Gettext', 'They like Gettext', peopleCount %}</div>
<div>{% _n locale, 'I like Gettext as much as %s', 'They like Gettext as much as %s', peopleCount, 'John' %}</div>
<div>{% _ni locale, 'I like Gettext as much as ${chief}', 'They like Gettext as much as ${chief}', peopleCount, { chief: 'John' } %}</div>
<div>{% _d locale, 'LL', page.date %}</div>
<div>{% _d locale, 'LL', '2020-08-01T21:57:00.000Z', 'Asia/Bangkok' %}</div>
<div>{% _d locale, 'LL', 1596319020000, 'UTC' %}</div>
<!-- `_p` shortcode applies the `url` built-in filter by itself -->
<div>{% _p locale, '/' %}</div>
</body>
</html>
In this context, locale
parameter is mandatory.
// .eleventy.js
const i18n = require('eleventy-plugin-i18n-gettext')
module.exports = eleventyConfig => {
eleventyConfig.addShortcode("custom_shortcode", (locale, fruit) => {
return i18n._(locale, fruit.name)
})
...
}
When i18n.enhance11tydata(obj, locale, dir?)
is used in xx.11tydata.js
data directory files, it adds a property named locale
which can be used in templates and passed to filters and shortcodes.
<!-- index.njk -->
<div>{%- custom_shortcode locale, fruit -%}</div>
Returns: string
The intent of this shortcode is to construct language selectors.
It replaces the locale part of the current url with the targeted locale then it applies the url
built-in filter.
// _data/locales.json
[
{ "path": "fr-fr", "name": "Français" },
{ "path": "nl-nl", "name": "Nederlands" },
{ "path": "pt-pt", "name": "Português" },
{ "path": "en-us", "name": "English" },
{ "path": "ar", "name": "عربى" }
]
<!-- _includes/layout.njk -->
{%- for locale in locales -%}
<a href="{%- relocalizePath locale.path, page.url -%}">{{ locale.name }}</a>
{%- endfor -%}
Type: string
The target locale as a simple language code (e.g. en
) or language code with country code suffix (e.g. en-us
).
Type: string
The path on which the current locale will be replaced with targetedLocale
.
This method is able to handle pathPrefix
when set in .eleventy.js
configuration file.
But when pathPrefix
is set by the commandline eleventy --pathprefix=eleventy-base-blog
, its value in the Config object is '/' instead of '/eleventy-base-blog/'.
- The Format of PO Files
- Language-COUNTRY codes
- ISO 639-1 Language codes
- ISO 3166-1 Country codes
- List of tz database time zones
Inspired by adamduncan work on eleventy-plugin-i18n