ddLocale is a fairly easy-to-use, yet flexible, JavaScript library for multilingual web apps and sites. It's similar to i18n, but without (too many) clutter. It supports both script and inline translations for text, simplified plurals, (ordinal) numbers, and dates from nestable JSON sources. It also includes functionality for a fully customizable language menu.
- Just a simple yet comprehensive library with no dependencies.
- Nested language files for easy maintanance.
- Multiple syntax options, such as attributes, arrays, or objects.
- Extensive inline translation options, including an easy-to-use tag maker.
- Supports language switching of static elements without reloading the page.
- Optionally extends the Javascript
String,Number,DateandElementfunctionality. - Support for Right To Left (RTL) languages.
- Support for simple word-by-word pluralization in the JSON language files.
- Optional support for ordinal numbers with supporting rules defined for each language in external scripts.
- Fully functional language menu with behavior settings (WCAG-compliant) and styling options.
- Optional use of the
ddMenulibrary for seamless integration with other navigation menus with similar functionality and styling. - Free (a coffee would be much appriciated).
Download and double-click the latest version. and include the Javascript in your project. You can also use this link for the latest version.
<script src="./.../ddLocale.js"></script>
Add a language folder to your project and include the JSON files with the necessary tranlations per language. Also include the formater script files in the ordinalRules folder if you are planning to use ordinal number translations.
Always call ddLocale.init({}) with the options of your choice. Normally, you'll probably load your settings first, which store the user's language choice. You can also use your browser's local date/time settings to specify ddLocale. By default, ddLocale is set to "en", regardless of whether that translation exists. To change the language later on you may use ddLocale.set({}) which is just an alternative to .init(...).
This example includes all the option available:
ddLocale.init({
cultures: [ // all available languages
{
culture: "en",
title: "English"
},
{
culture: "nl-NL",
title: "Nederlands",
direction: "ltr" // optional ltr or rtl
}
],
path: "lang/", // path from the root to the language files, default "lang/"
language: "", // optional if culture is used, default "en" (lowercase)
country: "", // optional, default "" (uppercase)
culture: "nl-NL", // optional, overrules language and country if set
replacement: "__", // optional, default __
menu: {
domId: "language", // id of dom-node where language menu will be triggered
text: "", // "long" title, "short" language code or some translation "key"
className: "center" // optional, className for the dropdown menu
autoOpen: 200, // delay in ms
autoClose: 200 // delay in ms (if used, must be long enough to reach the menu from the butten)
toggle: true // use toggle instead of built-in menu
engine: 'ddMenu', // optional, use ddMenu instead of built-in menu
},
ready: function (object) {
/*
new language loaded
you can use object to save settings for example
object = {
language: "nl",
country: "NL",
culture: "nl-NL",
title: "Nederlands"
}
*/
},
success: function (err) { // ddLocale is now ready for use
// resume initialisation scripts
},
failed: function (err) { // something went wrong
},
stringFormats: { // your collection of option for use with toLocaleString
eur: { // name your options
style: "currency", // known options for number translations
currency: "EUR",
minimumFractionDigits: 1,
maximumFractionDigits: 2
},
shortDate: {
year: "numeric", // or known options for date translations
month: "short",
day: "numeric",
},
ordinal: { format: "ordinal" },
wordShort: { format: "word", short: true },
ordinalValue: { format: "value" },
ordinalWord: { format: "word" },
ordinalDecimalWord: { format: "word", decimals: 2 },
ordinalZeroWord: { format: "word", zero: "none" },
ordinalAlt: { format: "alt" },
ordinalSuffix: { format: "suffix", gender: "f" },
bytes: { format: "bytes" }
},
autoInline: false, // optional, default true, start inline translation after load
ordinalRules: false, // optional, default false, true or path-string
log: false, // optional, default true
nocache: false, // if true language files will be loaded using timestamps
minified: true, // default false, load minified ordinal scripts (i.e. en.min.js)
usePrototypes: true // default false, extends existing prototypes ```String```, ````Number``` and ````Date```.
});
Define a list of cultures to let ddLocale know which languages are available. A cultures object has a culture, title and optional direction.
culture: this is a standard country or language code like "nl-NL" or "en". Make sure that culture matches the corresponding JSON language file name, for example nl-NL.json or en.json.
title: the name of language in it's own language for use in the drop down language menu.
direction: optional "ltr" or "rtl". If available the direction will be set as style and attribute to the body.
If your JSON-language files are not in a ./languages/ folder you can specify the new path. Just leave out ./ but include / at the end.
language and country define culture. Make sure that culture matches one of cultures objects.
The optional replacement character is used as a filler for non-found placeholder expressions.
domId: ddLocale only needs a DOM element with an id to place a button and a menu in.
text: button determines the button text:
"long"will placecultures[].titleinside the button."short"will place the propertyddLocale.languageinside the button.- An empty string
""will leave button empty. - Any other string is considered a
keyto be translated. menu: without themenuoption, the button of the built-in button cycles through the available languages. With themenuoption, the button opens a popup menu with all available languages fromcultures. The value ofmenuis used as the className to style the popup menu.
engine: With ddMenu, the only engine currently available, a more flexible system is used. Of course, ddMenu must be installed, but then you can share functionality and styling.
The ready() function will be called each time a new language is set and loaded. Use this function to save user settings with the new language for example.
If other scripts or libraries use ddLocale you can start their initialisation from the success() function. success() will only be called once per (page) load.
Your set of existing options for use with toLocaleString() which allows dates and numeric values to be represented in locale format. The number and date formats standard javascript options. The string formats look like this:
stringFormats: { // your collection of options
keyName: { // name your options
format: "word", // value | ordinal | word | suffix | alt | bytes
zero: "none", // optional replacement for value 0. Value may be a translation key
decimals: 2, // optional number of decimals (defualt 0) used to round the value
gender: 'f' // optional gender (default 'm') used in some countries
}
}
By default all elements with t attribute will be translatesd after load. If you don't want that you can set autoInline: false.
With ordinalRules: true, ddLocale attempts to add additional rules for ordinal numbers. They describe adjustments for numerical values (1→1st) according to local rules. The available scripts should be placed in the {path/}ordinalRules/ folder. Or, if ordinalRules is a string-path ddLocale will look in {path/}{ordinalRules/}. As you can see, both pathandordinalRulesshould include the/``` at the end.
ddLocale always attempts to load culture.js. If it's not available, a second attempt is made with country.js. If ordinalRules is not found, it is disabled for the selected language.
Unfortunately, the set of rules is far from complete. Current support is limited to:
- nl
- en (in progress)
Please also feel free to add or request missing ordinalRules for your language.
By default, ddLocale works independently. You can optionally add the same functionality to the existing String, Number, and Date prototypes. A function call like ddLocale.t(key, options) would then also work like (key).t(options), where key can be a String, Number, or Date.
Language files are json files. Make sure the culture matches the JSON language file name, for example nl-BE.json or en.json. Please note the following rules:
- All language files should have the same
keys. keysare case sensitive.- non-existing
keys, orkeyswithoutvaluewill be returned unaltered. - You can use nested
keys. - You can also use placeholders as numbers
{0}(Array-style) or as{names}(JSON-style). - If you plan to use the
inlinetranslation feature, it is important to use only lowercase letters for placeholdernames, because the standarddata-*attributes used for inline data storage do not support uppercase letters.
{
"bitcoin": "bitcoin->+s",
"bitcoins": "On {0} I had {1} {2} worth {3}.",
"cheddar": "cheddar->+s",
"cheese": "cheese->cheeses",
"exit": {
"goodbye": "A presto {0} {1} {2}."
},
"fullname": "{firstname} {middlename} {lastname}",
"hello": "Hello {name} {lastname}.",
"hi": "Hi {fullname}.",
"ordinalTest": "Word: {0}, ordinal: {2}, bytes: {3}",
"plurals": "{0} {1} and a {2}.",
"tool name": "ddLocale",
"currentdate": "Current date"
}
You can put ddLocal to work via scripting.
ddLocalis semi-recursive. The name of a placeholder is translated if a key with the same name exists and theformatteris not equal tos. However, going deeper than one level is not fully tested (and actually not necessary). The following example make use of the JSON above. Just pass thekeytoddLocale.t()translation function.
let myTranslation = ddLocale.t("tool name");
Or use the extended String.t() function:
let myTranslation = "tool name".t();
let key = "tool name";
myTranslation = key.t();
Nested values can be accessed by using a period between the keys.
let bye = "exit.goodbye".t();
This wil return "A presto __ __ __." because the values for the placeholders are missing. You can pass values for placeholders in 3 different ways (per attribute or array only works with sequential numbered placeholders, such as {0}):
let byAttributes = ddLocale.t( "exit.goodbye", "Benicio", "del", "Toro" );
let byArray = ddLocale.t( "exit.goodbye", ["Benicio", "del", "Toro"] );
let byObject = ddLocale.t("exit.goodbye", {0:"Benicio", 1:"del", 2:"Toro"} );
You may use {names} instead of numbers {0} as placeholders when using the object notation. Fill empty values for placeholders with an empty string, like middlename in the second example below. Multiple spaces are reduced to one.
Note: If you plan to use the
inlinetranslation feature, it is important to use only lowercase letters for placeholdernames, because the standarddata-*attributes used for inline data storage do not support uppercase letters!
let myName = "my name is " + "fullname".t( {"firstname":"Benicio", "middlename":"del", "lastname":"Toro"} );
let myName = "my name is " + ddLocale.t("fullname", {firstname:"Benicio", middlename:"", lastname:"Toro, del"} );
To localize numbers and dates you can pass ddLocale.culture to the standard toLocaleString and/or toLocaleDateString.
let dateString = new Date().toLocaleString(ddLocale.culture, {
year: "numeric",
month: "short",
day: "numeric",
});
To make things a bit easier and shorter, you can store your options in stringFormats (see Initialisation above) and then use the extended functions Number.t() and Date.t() with the option name you want to use.
let dateString = new Date().t('shortDate');
let currency = (12345.678).t('eur');
Or you can use ddLocale to pass a date or a number for localisation.
let dateString = ddLocale.t(new Date(), 'shortDate');
let currency = ddLocale.t(12345.678, 'eur');
Passing timestamps is allowed, but without specific date options it will be treated as a number.
let dateString1 = ddLocale.t(1760565600000, 'shortDate');
let dateString2 = (1760565600000).t('shortDate');
Similarly, you can pass the option name to display ordinals and/or bytes as long as the formatting option is present in stringFormats.
let default = (1345236).t('ordinalValue')); → 1345236ste
let ordinal1 = (1345236).t('ordinal')); → 1.345.236ste
let ordinal2 = (300000).t('ordinal')); → 300 duizendste
let word = (3).t('ordinalWord')); → three
let short = (1345236).t('wordShort')); → 1 mln
let alternative = (300000).t('ordinalAlt')); → 300.000ᵉ (availability depends on language)
let zero = (0).t('ordinalZeroWord')); → 'none';
let decimal = (1345236).t('ordinalDecimalWord')); → 1,35 miljoen
let suffix = (345236).t('ordinalSuffix')); → ste
let bytes = (1536000).t('bytes')); → 1 MB
You can fill placeholders with dates and numbers too, but you can only do this using arrays or objects, because you have to pass an additional array or object with special binders per placeholder. Dates can be passed as a Date object or as a timestamp.
Datebinders start with adordatefor that matter, optionally followed by a|and the date optionnamefromstringFormats.Numberbinders start with an,numornumber, optionally followed by a|and the number optionnamefromstringFormats.
let bitcoins1 = ddLocale.t("bitcoins", {0:1760620872050, 1:2.58, 2:'bitcoins', 3:(2.58*95339.54)}, { 0:'d|shortDate', 1:'n', 3:'n|eur' });
let bitcoins2 = "bitcoins".t([new Date(), 2.56, 'bitcoins', (2.56*95339.54)], ['date|shortDate', 'number', 'string', 'num|eur']);
let ordinalTest = "ordinalTest".t({ 0: 12542334567, 1: 1345236, 2: 1536000 }, {0:'n|ordinalWord', 1:'n|ordinalSuffix', 2:'n|bytes'}));
The values in the language file can be expanded with plurals in three ways.
- Simply add a
key+valuepair for both the singular and plural. - Add
->=along with the plural form. - Add
->, followed by-with the letter(s) you want to remove. Add+followed by everything else you want to add. Useful for adding single letters and for long words.
{
"car": "MG",
"cars": "Nissan and Porsche",
"cheese": "cheese->cheeses",
"child": "child->+ren",
"man": "man->-an+en",
}
In normal use, these extensions are ignored. By sending a number, you can test whether that number results in singular or plural form in the selected language.
let normal = 'One car is a ' + ddLocale.t("car") + 'two cars are ' + ddLocale.t("cars");
let noCheese = 'No ' + 'cheese'.t(0);
let oneCheese = 'One ' + 'cheese'.t(1);
let twoCheese = 'Two ' + 'cheese'.t(2);
let oneChild = (1).t('word') + ' ' + 'cheese'.t(1);
let oneChild = (11).t('word') + ' ' + 'cheese'.t(11);
let oneChild = ddLocale.t(1, 'word') + ' ' + ddLocale.t('cheese', 1);
let man = ddLocale.t(1, 'word') + ' ' + ddLocale.t('man', 1);
let men = ddLocale.t(100, 'word') + ' ' + ddLocale.t('man', 100);
let plurals = "plurals".t({0: 2, 1: 'man', 2: 'bird' }, { 0:'n|ordinalWord', 1:2}));
Static elements can be translatable too.
The inline solution is more or less recursive, as long as the necessary placeholders are defined! Recursive values deeper than one level haven't really been tested very intensively. With
autoInline: true(default all inlinetattributew will be translated automatically. You can force the inline translations withddLocale.html()for the entire document orddLocale.html(domId)for partial translations. Alltattributes with de element withid=domidwill be checked.
Place the key inside a t attribute and ddLocale will place the translation in the innerHTML.
<h1 t="tool name"></h1>
Inline attributes for long phrases with placeholders or even recursive keys can be complex. The best practice is to retrieve the translation via scripting, including the attributes. You do this by appending the ddLocale.t() function with an additional option true. You will then receive an array with two values. The first is simply the result of the translation. The second value is an object containing all the necessary attribute names and their values. This allows you to provide the DOM element in question with attributes. With the option usePrototypes: true, the JavaScript Element is extended with the setAttributes() function, which can be fed with the returned object.
let toolname = ddLocale.t("tool name"); → "ddLocale"
let toolnameAndAttributes = ddLocale.t("tool name", true); → ["ddLocale", { t:"tool name" }];
<h1 id="header"></h1>
let h1 = document.getElementById('header');
h1.setAttributes(toolnameAndAttributes[1]).innerHTML = toolnameAndAttributes[0];
→ <h1 id="header" t="tool name">ddLocale</h1>
There is now also a shorter method. Use ddLocale.setAttributes('key', element|'elementId');. This function will place all the necessary attributes in element or the object with elementId and return the translation value.
headerTitle.innerHTML = ddLocale.setAttributes(title, headerTitle);
If the inline key has placeholders the corrresponding values must be present in data-t_* attributes, where * is a corresponding placeholders.
It is important to use only lowercase letters for placeholder
names, because the standarddata-*attributes used for inline data storage do not support uppercase letters!
<h1 t="hello" data-t_name="Master" data-t_lastname="Mek"></h1>
If the inline placeholders are themselves a key, you must specify an additional attribute by adding the placeholder names in the names attribute with an underscore _.
<h1 t="hi" data-t_fullname="fullname" data-t_fullname_firstname="Benicio" data-t_fullname_middlename="del" data-t_fullname_lastname="Toro"></h1>
Of course this also works with numbered placeholders.
<span t="exit.goodbye" data-t_0="Benicio" data-t_1="del" data-t_2="Toro"></span>
Dates must be passed as timestamps. Because timestamps are just numbers, you need to tell ddLocale what kind of number it is with binders. This is done with an additional attribute with the same attribute name, appended with an additional _ sign.
Datebinders start with adordatefor that matter, optionally followed by a|and the date optionnamefromstringFormats.Numberbinders start with an,numornumber, optionally followed by a|and the number optionnamefromstringFormats.
<div t="1760565600000" data-t_="date"></div>
<div t="176056.45" data-t_="num|eur"></div>
<div t="ordinalTest" data-t_0="12542334567" data-t_1="1345236" data-t_2="1536000" data-t_0_="n|ordinalWord" data-t_1_="n|ordinalSuffix" data-t_2_="n|bytes"></div>
Because inline translations are recursive, the result of a placeholder translation cannot be an existing key. To prevent the translation from being incorrectly translated further, you can explicitly pass the placeholder as a string.
Stringbinders start with as,strorstring.
<div t="bitcoins" data-t_0="1760565600000" data-t_0_="d|shortDate" data-t_1="2.56" data-t_1_="num" data-t_2="bitcoins" data-t_2_="s" data-t_3="244069.222" data-t_3_="n|eur"></div>
For pluralisation you can pass a number-string in the _ attribute.
<div t="plurals" data-t_0="100" data-t_0_="n|ordinalWord" data-t_1="man" data-t_1_="100" data-t_2="bird"></div>
Dates and numbers can also be static, but only if any options are stored in stringFormats.
Numbers...
<div id="container">
<span t="1760565600000" data-t-num="eur"></span>
</div>
For dates, the static representation must be a timestamp new Date().getTime() and the option name must be in the attribute data-t-date. The option name is cumpulory because with it the timestamp is just a number.
<div id="container">
<span t="1760565600000" data-t-date="shortDate"></span>
</div>
After the t attribute is translated, if the element in question also contains an emtpy t-title attribute, the title will also be populated with the translation.
<div id="container">
<span t="1760565600000" data-t-date="shortDate" t-title></span>
</div>
If the t-title attribute is not empty, it's value will be translated non-recursively and placed into the title attribute.
<div id="container">
<span t="1760565600000" data-t-date="shortDate" t-title="currentdate"></span>
</div>
To use the ddLocale menu you will need a placeholder in your HTML document.
<div id="language"></div>
This is an example of what the built-in functional menu looks like after initialisation with domId="language" and menu.align=left. With css you can style the menu, using id, class and atrributes.
<div id="language" culture="nl-NL" class="open">
<a id="language-menu-button"></a>
<div class="left" id="language-menu-container">
<a culture="en">English</a>
<a class="selected" culture="nl-NL">Nederlands</a>
</div>
</div>
Use the autoOpen and autoClose options to open and close the menu without clicking. These option values are in milliseconds. The autoClose timing should be long enough, depending on the distance between the button and the popup menu, otherwise the menu will close before the mouse reaches menu.
To use ddLMenu add engine: 'ddMenu' to the menu settings, along with the addtional ddMenu options.
menu: {
domId: 'language',
text: '', // long, short or translation key
align: 'center', // className
autoOpen: null,
autoClose: 500,
engine: 'ddMenu', // or false/null/not set
className: 'language',
multilingual: true, // use ddLocale
updateTitleOnSelect: true,
autoOpen: 250,
autoClose: 500,
animation: 100,
badge: 0,
extraHeight: 20, // necessary for some browsers
triggerIcon: 'icon-user', // CSS class for your icon
iconPosition: 'right',
change: (e, trigger) => {
ddLocale.set({
culture: trigger.item.value
});
},
items: {
iconPosition: 'right'
}
}
Below is a simple css example to style the menu.
An important note about the main div with id="language" though. Because the popup menu is inside the main div with id="language", this main div must have an explicit absolute or relative position and the menu's container with id="language-menu-container" should be absolute!
#language {
position: absolute;
width: 16px;
height: 16px;
border: 0px solid #1D6F42;
top: 25px;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
cursor: hand;
cursor: pointer;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
box-shadow: 0 2px 2px rgba(0,0,0,0.5);
z-index: 10;
}
#language[culture=nl-NL] {
background-image: url('../images/flags/nl.svg');
}
#language[culture=en] {
background-image: url('../images/flags/en.svg');
}
#language.open {}
#language-menu-container {
position: absolute;
background-color: white;
z-index: 100;
top: 100%;
left: 0;
transform: translate(0, 10px);
}
#language-menu-container.left {
left: 0;
transform: translate(0, 10px);
}
body[direction=rtl] #language-menu-container.left {
left: 100%;
transform: translate(-100%, 10px);
}
#language-menu-container.right
{
left: 100%;
transform: translate(-100%, 10px);
}
#language-menu-container.center {
left: 50%;
transform: translate(-50%, 10px);
}
#language-menu-button {
display: inline-block;
width: 100%;
height: 100%;
}
#language-menu-container a {
display: block;
line-height: 16px;
border-bottom: 1px solid black;
padding: 8px 10px;
}
#language-menu-container.center a {
text-align: center;
}
#language-menu-container a.selected {
background-color: lightgrey;
cursor: default;
text-decoration: none;
color: black;
}
#language-menu-container a:not(.selected):hover {
background-color: grey;
color: white;
}
#language-menu-container a:last-child {
border-bottom: 0;
}
If you like ddLocale you may consider buying me a coffee.
Thank you for using ddLocale.
Mek