i18nextify
v5.0.0
Published
enables localization of any page with zero effort
Readme
i18nextify
Just drop this script on your page and you're ready to deliver your pages in any language.
Checkout this video to see i18nextify in action.
i18nextify uses Virtual DOM to update your page with translations based on the current content. MutationObserver is used to trigger translations on newly added content.
i18nextify comes bundled with i18next.
Should play well with any static or dynamic page not using its own Virtual DOM.
Getting started
The easiest way for guaranteed success is using locizify on locize.com.
Alternatively:
Drop this script on your page.
<!DOCTYPE html>
<html>
<head>
<script src="/i18nextify.min.js"></script>
<!-- or: <script src="https://unpkg.com/i18nextify@^5"></script> -->
</head>
...
</html>Optionally you can also define your fallback language directly in the script tag:
<!DOCTYPE html>
<html>
<head>
<script src="/i18nextify.min.js" id="i18nextify" fallbacklng="en"></script>
<!-- or: <script src="https://unpkg.com/i18nextify@^5" id="i18nextify" fallbacklng="en"></script> -->
</head>
...
</html>Request your page with querystring params ?debug=true&saveMissing=true and open the browser console to see i18nextify in action. It will output all missing translations - start serving them from /locales/{{lng}}/translation.json.
See the example for details.
Initialize with options
<!DOCTYPE html>
<html>
<head>
<script src="/i18nextify.min.js"></script>
<script>
window.i18nextify.init({
// defaults that are set
autorun: true, // setting to false init will return an object with start function
ele: document.body, // pass in another element if you like to translate another html element
ignoreTags: ['SCRIPT'], // tags to ignore
// using keys instead of content as keys
keyAttr: 'i18next-key', // node attribute to use as key
ignoreWithoutKey: false, // set to true to only support nodes having a key
// per default not set
ignoreIds: ['ignoreMeId'],
ignoreClasses: ['ignoreMeClass'],
// attributes to translate
translateAttributes: ['placeholder', 'title', 'alt', 'value#input.type=button', 'value#input.type=submit'],
// merging content (eg. a tags in p tags)
mergeTags: [], // tags to merge innerHtml to one key
inlineTags: [], // tags to inline (eg. a, span, abbr, ...)
ignoreInlineOn: [], // tags to ignore inlining tags under inlineTags
// cleanup for keys
cleanIndent: true, // removes indent, eg. if a p tag spans multiple lines
ignoreCleanIndentFor: ['PRE', 'CODE'], // ignores cleaning up of indent for those tags needing that extra spaceing
cleanWhitespace: true, // removes surrounding whitespace from key
namespace: false, // set a filename - default namespace will be translation
namespaceFromPath: false // set true will use namepace based on window.location.pathname
ns: ['common'] // -> only set if accessing more then one namepace
// optional: sanitize hook invoked on every translated HTML body before
// it is rendered into the DOM. Default is pass-through. See "Security
// considerations" below for when to wire this up (e.g. DOMPurify).
sanitize: (html, ctx) => html,
// + all options available in i18next
});
</script>
</head>
...
</html>Delay initial translation
const translation = i18nextify.init({
autorun: false,
});
setTimeout(function () {
translation.start();
}, 1000);Merge content / using html in translations
Just set translated attribute:
<p merge>
all inside will be used as on segment, even if having other
<a>elements inside</a>
</p>
// key = all inside will be used as on segment, even if having other
<a>elements inside</a>Same could be done using options:
mergeTags: [], // tags to merge innerHtml to one key
inlineTags: [], // tags to inline (eg. a, span, abbr, ...)
ignoreInlineOn: [], // tags to ignore inlining tags under inlineTagsFragment replacement for links and images
<img src="/images/{{a.png}}" alt="big A" />You will find a.png to be a key in your translation files - it's value can be replaced to eg. a-de.png for German (all other languages will fallback to a.png)
<a href="/{{statistic}}">Open my statistics</a>statistic will be a regular key that can be translated. But be aware you will need to provide that routes - eg. using localized routes on the server
Avoid translating
an element
Just set translated attribute:
<div translated>this won't get translated - nor this elements children</div>By ignoring tag, class, id
Just add needed items to the specific array:
window.i18nextify.init({
ignoreTags: ['SCRIPT'], // need to be uppercased
ignoreIds: ['ignoreMeId'],
ignoreClasses: ['ignoreMeClass']
});<script>
// this won't get translated - nor this elements children
</script>
<div id="ignoreMeId">
this won't get translated - nor this elements children
</div>
<div class="ignoreMeClass">
this won't get translated - nor this elements children
</div>Just add translated-attribute
Translating an element with options
For extended translations like plurals, interpolation, ... you need to add options to the element
<div i18next-options='{"foo": "bar"}'>
foo {{bar}}
<p i18next-options='{"foo2": "bar2"}'>foo {{foo}}; foo2 {{foo2}}</p>
</div>Translating JavaScript code
You can use the i18next instance used to provide the translation functionality directly. Just make sure the instance is initialized already:
<script>
// use t function of i18next
// https://www.i18next.com/translation-function/essentials
function useI18next() {
var translated = i18nextify.i18next.t('some key');
}
if (i18nextify.i18next.isInitialized) {
useI18next();
} else {
i18nextify.i18next.on('initialized', function(options) {
useI18next();
})
}
</script>Options get inherited from parent to child nodes.
Set different namespaces
Default would be translation.
Set a different one:
window.i18nextify.init({
namespace: 'myNamespace'
});autogenerate one per route:
window.i18nextify.init({
namespaceFromPath: true
});Access different namespaces
This is useful for reused elements that are on every page, eg. like footer,... and you're using namespaceFromPath. So you can avoid having that segments on every routes namespace file.
window.i18nextify.init({
namespace: 'translation', // -> set the default namespace
ns: ['common'] // -> add additional namespaces to load
});<div i18next-options='{"ns": "common"}'>
<p>different namespace: i18next-options='{"ns": "common"}'</p>
<p>
set it on i18next options and assert to add it to
<strong>i18next.options.ns array on init</strong>
</p>
</div>Avoid flickering on initial load
<!DOCTYPE html>
<html>
<head>
<script src="/i18nextify.min.js"></script>
</head>
<body style="display: none">
...
</body>
</html>Just set the element style display to none. i18nextify will change it to block when ready.
Change File to use
You can change the namespace after loading to some other file (eg. before transitioning to another page).
window.i18nextify.changeNamespace('newNamespace');force a retranslation:
window.i18nextify.forceRerender();Security considerations
i18nextify's core job is to localise a live HTML page by replacing text and
certain attribute values with translations. Because formatting tags inside
translations (<b>, <em>, <a href="…">, …) are a supported feature,
translation strings are rendered as HTML rather than as plain text.
That power comes with a trust assumption:
The translation source must be fully controlled by the developer. Treat every translation value as if it were code shipped in your bundle.
Concretely that means:
- Serve the translation backend over HTTPS (MITM on plain HTTP could swap translations for attacker HTML).
- Do not accept user-contributed translations into the live backend without an explicit sanitisation step.
- If translations are authored by multiple people, treat the authoring workflow like code review — the same way you'd review any other source that ends up executed in the browser.
The sanitize hook
Since v4.0.8, you can pass a sanitize(html, ctx) function via init options
(or via i18next.init({ ... })). It is invoked on every translated HTML
body before it is parsed into the virtual DOM. This is the right place to
wire DOMPurify or a similar sanitizer
if your translation sources are partially trusted (third-party CDN,
user-contributed locales, content owned by a different team):
import DOMPurify from 'dompurify';
window.i18nextify.init({
sanitize: (html, ctx) => DOMPurify.sanitize(html, { ADD_ATTR: ['target'] }),
// …other options
});ctx currently contains { key, attribute } so you can apply stricter
rules for specific keys if needed. The hook defaults to pass-through — do
not enable sanitisation blindly, as it will strip the very HTML markup
this library is designed to render.
Blocked URL schemes in href / src
Since v4.0.8, values substituted into translated href / src attributes
are dropped if they begin with javascript:, data:, vbscript:, or
file: (case-insensitive, leading whitespace ignored). Legitimate
translation use cases never need these schemes, so the protection is on by
default and cannot be disabled.
Reporting a vulnerability
Please do not open a public GitHub issue for security problems. Send reports privately via the GitHub Security Advisories flow on the repository.
