README
lit-localize
Features | Overview | Modes | Tutorial | API | Status event | Localized mixin | CLI | Config file | FAQ
lit-localize is a library and command-line tool for localizing web applications that are based on lit-html and LitElement.
Features
- 🌐 Localize your lit-html and LitElement applications
- 🔥 Safely embed HTML markup within localized templates
- 🍦 Write vanilla code that works in development with no new tooling
- 📄 Standard XLIFF interchange format
- 🆓 Generate a zero-overhead bundle for each locale
- 🔁 ... or dynamically load locales and automatically re-render
Overview
Wrap your template with the msg
function to make it localizable:
import {html} from 'lit-html';
import {msg} from 'lit-localize';
render(msg('greeting', html`Hello <b>World</b>!`), document.body);
Run lit-localize
to extract all localizable templates and generate an XLIFF
file, a format which is supported by many localization tools and services:
<trans-unit id="greeting">
<source>Hello <ph id="0"><b></ph>World<ph id="1"></b></ph>!</source>
<!-- target tag added by your localization process -->
<target>Hola <ph id="0"><b></ph>Mundo<ph id="1"></b></ph>!</target>
</trans-unit>
Use transform mode to generate an optimized bundle for each locale:
import {html} from 'lit-html';
render(html`Hola <b>Mundo</b>!`, document.body);
Alternatively, use runtime mode to dynamically switch locales without a page reload:
import {configureLocalization} from 'lit-localize';
const {setLocale} = configureLocalization({
sourceLocale: 'en',
targetLocales: ['es-419', 'zh_CN'],
loadLocale: (locale) => import(`/locales/${locale}.js`),
});
(async () => {
await setLocale('es-419');
renderApplication();
})();
See
examples/transform
and
examples/runtime
for full working examples.
Modes
lit-localize supports two output modes: transform and runtime.
Transform mode | Runtime mode | |
---|---|---|
Output | A full build of your application for each locale, with all msg calls replaced with static localized templates. |
A dynamically loadable template module for each target locale. |
Make template localizable | msg() |
msg() |
Configure | const {getLocale, setLocale} = configureLocalization(...); |
(Optional)const {getLocale} = configureTransformLocalization(...); |
Switch locales | Refresh page and load a different .js file |
Call setLocale() and re-render using any of:- lit-localize-status event- setLocale promise- Localized mixin for LitElement |
Advantages | - Fastest rendering - Fewer bytes for a single locale |
- Faster locale switching - Fewer marginal bytes when switching locales |
Tutorial
Install lit-localize. You get both a client library and a command-line tool. You'll always use both together.
npm install --save lit-localize
Set up a TypeScript lit-html project if you don't have one already:
npm install --save lit-html npm install --save-dev typescript npx tsc --init
You'll want these TypeScript settings:
"compilerOptions": { "target": "es2018", "module": "esnext", "moduleResolution": "node" }
Create an
index.ts
, and declare a localizable template using themsg
function. The first argument is a unique identifier for this template, and the second is a string or lit-html template.(Note that this code will directly compile and run, just as it would if you were rendering the lit template directly, so your build process doesn't need to change until you want to integrate localized templates.)
import {html, render} from 'lit-html'; import {msg} from 'lit-localize'; render( html`<p>${msg('greeting', html`Hello <b>World</b>!`)}</p>`, document.body );
Make a JSON config file at the root of your project called
lit-localize.json
. In this example we're using transform mode, but you can also use runtime mode. The$schema
property is optional, and lets editors like VSCode auto-complete and check for errors.{ "$schema": "https://raw.githubusercontent.com/PolymerLabs/lit-localize/master/config.schema.json", "sourceLocale": "en", "targetLocales": ["es-419"], "tsConfig": "tsconfig.json", "output": { "mode": "transform" }, "interchange": { "format": "xliff", "xliffDir": "xliff/" } }
Run the lit-localize CLI:
npx lit-localize
Take a look at the generated XLIFF file
xliff/es-419.xlf
. Note that we have a<source>
template extracted from your source code, but we don't have a localized version yet. Also note that embedded HTML markup has been encoded into<ph>
tags.<trans-unit id="greeting"> <source>Hello <ph id="0"><b></ph>World<ph id="1"></b></ph>!</source> </trans-unit>
Edit
xliff/es-419.xlf
to add a<target>
tag containing a localized version of the template. Usually you would use a tool or service to generate this tag by feeding it this XLIFF file.<trans-unit id="greeting"> <source>Hello <ph id="0"><b></ph>World<ph id="1"></b></ph>!</source> <target>Hola <ph id="0"><b></ph>Mundo<ph id="1"></b></ph>!</target> </trans-unit>
Run
lit-localize
again:npx lit-localize
Now take a look at the generated file
es-419/index.js
:import {html, render} from 'lit-html'; render(html`<p>Hola <b>Mundo</b>!</p>`, document.body);
and
en/index.js
:import {html, render} from 'lit-html'; render(html`<p>Hello <b>World</b>!</p>`, document.body);
Note that the localized template has been substituted into your code, the
msg
call has been removed, the lit-html template has been reduced to its simplest form, and thelit-localize
module is no longer imported.
API
The lit-localize
module exports the following functions:
Note that lit-localize relies on distinctive, annotated TypeScript type signatures to identify calls to
msg
and other APIs during analysis of your code. Casting a lit-localize function to a type that does not include its annotation will prevent lit-localize from being able to extract and transform templates from your application. For example, a cast like(msg as any)("greeting", "Hello")
will not be identified. It is safe to re-assign lit-localize functions or pass them as parameters, as long as the distinctive type signature is preserved. If needed, you can reference each function's distinctive type with e.g.typeof msg
.
configureLocalization(configuration)
Set configuration parameters for lit-localize when in runtime mode. Returns an object with functions:
Throws if called more than once.
When in transform mode, the lit-localize CLI will error if this function is
called. Use
configureTransformLocalization
instead.
The configuration
object must have the following properties:
sourceLocale: string
: Required locale code in which source templates in this project are written, and the initial active locale.targetLocales: Iterable<string>
: Required locale codes that are supported by this project. Should not include thesourceLocale
code.loadLocale: (locale: string) => Promise<LocaleModule>
: Required function that returns a promise of the localized templates for the given locale code. For security, this function will only ever be called with alocale
that is contained bytargetLocales
.
It is recommended to use the output.localeCodesModule
config file setting to
generate a module which exports a sourceLocale
string and targetLocales
array that can be passed to configureLocalization
, to ensure that your client
remains in sync with your config file.
Example:
const {getLocale, setLocale} = configureLocalization({
sourceLocale: 'en',
targetLocales: ['es-419', 'zh_CN'],
loadLocale: (locale) => import(`/${locale}.js`),
});
configureTransformLocalization(configuration)
Set configuration parameters for lit-localize when in transform mode. Returns an object with function:
getLocale
: Return the active locale code.
(Note that setLocale
is not available from
this function, because changing locales at runtime is not supported in transform
mode.)
Throws if called more than once.
The configuration
object must have the following properties:
sourceLocale: string
: Required locale code in which source templates in this project are written, and the active locale.
It is recommended to use the output.localeCodesModule
config file setting to
generate a module which exports a sourceLocale
string that can be passed to
configureTansformLocalization
, to ensure that your client remains in sync with
your config file.
Example:
const {getLocale} = configureLocalization({
sourceLocale: 'en',
});
In transform mode, calls to this function are transformed to an object with a
getLocale
implementation that returns the static locale code for each locale
bundle. For example:
const {getLocale} = {getLocale: () => 'es-419'};
getLocale() => string
Return the active locale code.
setLocale(locale: string) => Promise
Available only in runtime mode. Set the active locale code, and begin loading
templates for that locale using the loadLocale
function that was passed to
configureLocalization
. Returns a promise that resolves when the next locale is
ready to be rendered.
Note that if a second call to setLocale
is made while the first requested
locale is still loading, then the second call takes precedence, and the promise
returned from the first call will resolve when second locale is ready. If you
need to know whether a particular locale was loaded, check getLocale
after the
promise resolves.
Throws if the given locale is not contained by the configured sourceLocale
or
targetLocales
.
msg(id: string, template, ...args) => string|TemplateResult
Make a string or lit-html template localizable.
The id
parameter is a project-wide unique identifier for this template.
The template
parameter can have any of these types:
A plain string with no placeholders:
msg('greeting', 'Hello World!');
A lit-html
TemplateResult
that may contain embedded HTML:msg('greeting', html`Hello <b>World</b>!`);
A function that returns a template literal string that may contain placeholders. Placeholders may only reference parameters of the function, which will be called with the 3rd and onwards parameters to
msg
.msg('greeting', (name) => `Hello ${name}!`, getUsername());
A function that returns a lit-html
TemplateResult
that may contain embedded HTML, and may contain placeholders. Placeholders may only reference parameters of the function, which will be called with the 3rd and onwards parameters tomsg
:msg('greeting', (name) => html`Hello <b>${name}</b>!`, getUsername());
In transform mode, calls to this function are replaced with the static localized template for each emitted locale. For example:
html`Hola <b>${getUsername()}!</b>`;
LOCALE_STATUS_EVENT
Name of the lit-localize-status
event.
event lit-localize-status
In runtime mode, whenever a locale change starts, finishes successfully, or
fails, lit-localize will dispatch a lit-localize-status
event to window
.
You can listen for this event to know when your application should be
re-rendered following a locale change. See also the
Localized
mixin, which automatically re-renders
LitElement
classes using this event.
Event types
The detail.status
string property tells you what kind of status change has occured,
and can be one of: loading
, ready
, or error
:
loading
A new locale has started to load. The detail
object also contains:
loadingLocale: string
: Code of the locale that has started loading.
A loading
status can be followed by a ready
, error
, or loading
status.
In the case that a second locale is requested before the first one finishes
loading, a new loading
event is dispatched, and no ready
or error
event
will be dispatched for the first request, because it is now stale.
ready
A new locale has successfully loaded and is ready for rendering. The detail
object also contains:
readyLocale: string
: Code of the locale that has successfully loaded.
A ready
status can be followed only by a loading
status.
error
A new locale failed to load. The detail
object also contains the following
properties:
errorLocale: string
: Code of the locale that failed to load.errorMessage: string
: Error message from locale load failure.
An error
status can be followed only by a loading
status.
Event example
import {LIT_LOCALIZE_STATUS} from 'lit-localize';
// Show/hide a progress indicator whenever a new locale is loading,
// and re-render the application every time a new locale successfully loads.
window.addEventListener(LIT_LOCALIZE_STATUS, (event) => {
const spinner = document.querySelector('#spinner');
if (event.detail.status === 'loading') {
console.log(`Loading new locale: ${event.detail.loadingLocale}`);
spinner.removeAttribute('hidden');
} else if (event.detail.status === 'ready') {
console.log(`Loaded new locale: ${event.detail.readyLocale}`);
spinner.setAttribute('hidden', '');
renderApplication();
} else if (event.detail.status === 'error') {
console.error(
`Error loading locale ${event.detail.errorLocale}: ` +
event.detail.errorMessage
);
spinner.setAttribute('hidden', '');
}
});
mixin Localized
If you are using LitElement, then
you can use the Localized
mixin
from lit-localize/localized-element.js
to ensure that your elements
automatically re-render whenever the locale changes in runtime mode.
import {Localized} from 'lit-localize/localized-element.js';
import {msg} from 'lit-localize';
import {LitElement, html} from 'lit-element';
class MyElement extends Localized(LitElement) {
render() {
// Whenever setLocale() is called, and templates for that locale have
// finished loading, this render() function will be re-invoked.
return html`<p>${msg('greeting', html`Hello <b>World!</b>`)}</p>`;
}
}
In transform mode, applications of the Localized
mixin are removed.
CLI
Running the lit-localize
command-line program does the following:
Reads your config file according to the
--config
flag.Analyzes all TypeScript files covered by your
tsconfig.json
, and discovers all calls to the lit-localizemsg
function.Creates or updates an XLIFF (
.xlf
) file for each of your target locales, with a<source>
tag corresponding to eachmsg
call.Reads existing
<target>
tags from existing XLIFF files for eachmsg
call.When in transform mode, compiles your TypeScript project for each locale, where all
msg
calls are replaced with the corresponding static, localized version from that locale's XLIFF file.When in runtime mode, generates a
<locale>.ts
file for each locale, which can be dynamically loaded by thelit-localize
module.
It takes the following flags:
Flag | Description |
---|---|
--help |
Display this list of flags. |
--config |
Path to JSON config file. Defaults to ./lit-localize.json |
Config file
Property | Type | Description |
---|---|---|
sourceLocale |
string |
Required locale code that templates in the source code are written in. |
targetLocales |
string[] |
Required locale codes that templates will be localized to. |
tsConfig |
string |
Path to a tsconfig.json file that describes the TypeScript source files from which messages will be extracted. |
output.mode |
"transform" , "runtime" |
What kind of output should be produced. See modes. |
output.localeCodesModule |
string |
Optional filepath for a generated TypeScript module that exports sourceLocale , targetLocales , and allLocales using the locale codes from your config file. Use to keep your config file and client config in sync. |
interchange.format |
"xliff" , "xlb" |
Data format to be consumed by your localization process. Options: - "xliff" : XLIFF 1.2 XML format- "xlb" : Google-internal XML format |
Transform mode only |
||
output.outputDir |
string |
Output directory for generated TypeScript modules. Into this directory will be generated a <locale>.ts for each targetLocale , each a TypeScript module that exports the translations in that locale keyed by message ID. |
XLIFF only |
||
interchange.xliffDir |
string |
Directory on disk to read/write .xlf XML files. For each target locale, the file path "<xliffDir>/<locale>.xlf" will be used. |
FAQ
- How should I set the initial locale in transform mode?
- How should I switch locales in transform mode?
How should I set the initial locale in transform mode?
In transform mode, the locale is determined simply by the JavaScript bundle you load. How you determine which bundle to load when your page loads is up to you.
IMPORTANT: Take care to always validate your locale codes when dynamically choosing a script name! The example below is safe because a script can only be loaded if it matches one of the fixed locale codes in the regular expression, but if our matching logic was less precise, it could result in bugs or attacks that inject insecure JavaScript.
For example, if your application's locale is reflected in the URL, you can
include an inline script in your HTML file that checks the URL and inserts the
appropriate <script>
tag:
<!DOCTYPE html>
<script>
// If the subdomain matches one of our locale codes, load that bundle.
// Otherwise, load the default locale bundle.
//
// E.g. https://es-419.example.com/
// ^^^^^^
const match = window.location.href.match(
/^https?:\/\/(es-419|zh_CN|en|es)\./
);
const locale = match ? match[1] : 'en';
const script = document.createElement('script');
script.type = 'module';
script.src = `/${locale}.js`;
document.head.appendChild(script);
</script>
Implementing logic similar to this on your server so that the appropriate script tag is statically rendered into your HTML file will usually result in the best performance, because the browser will start downloading your script as early as possible.
How should I switch locales in transform mode?
In transform mode, the setLocale
function is not available. Instead, reload
the page so that the next load will pick a different locale bundle.
For example, this locale-picker
custom element loads a new subdomain whenever
a new locale is selected from a drop-down list:
import {LitElement, html} from 'lit-element';
import {getLocale} from './localization.js';
import {allLocales} from './locale-codes.js';
import {Localized} from 'lit-localize/localized-element.js';
export class LocalePicker extends Localized(LitElement) {
render() {
return html`
<select @change=${this.localeChanged}>
${allLocales.map(
(locale) =>
html`<option value=${locale} selected=${locale === getLocale()}>
${locale}
</option>`
)}
</select>
`;
}
localeChanged(event: Event) {
const newLocale = (event.target as HTMLSelectElement).value;
const newHostname = `${newLocale}.example.com`;
const url = new URL(window.location.href);
if (newHostname !== url.hostname) {
url.hostname = newHostname;
window.location.assign(url.toString());
}
}
}
customElements.define('locale-picker', LocalePicker);