title | eleventyNavigation | versionLinks | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
Localization |
|
|
Localization is the process of supporting multiple languages and regions in your
apps and components. Lit has first-party support for localization through the
@lit/localize
library, which has a number of advantages that can make it a
good choice over third-party localization libraries:
-
Native support for expressions and HTML markup inside localized templates. No need for a new syntax and interpolation runtime for variable substitution—just use the templates you already have.
-
Automatic re-rendering of Lit components when the locale switches.
-
Only 1.27 KiB (minified + compressed) of extra JavaScript.
-
Optionally compile for each locale, reducing extra JavaScript to 0 KiB.
Install the @lit/localize
client library and the @lit/localize-tools
command-line interface.
npm i @lit/localize
npm i -D @lit/localize-tools
- Wrap a string or template in the
msg
function (details). - Create a
lit-localize.json
config file (details). - Run
lit-localize extract
to generate an XLIFF file (details). - Edit the generated XLIFF file to add a
<target>
translation tag (details). - Run
lit-localize build
to output a localized version of your strings and templates (details).
To make a string or Lit template localizable, wrap it in the msg
function. The
msg
function returns a version of the given string or template in whichever
locale is currently active.
Before you have any translations available, msg
simply returns the original
string or template, so it's safe to use even if you're not yet ready to actually
localize.
{% switchable-sample %}
import {html, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';
import {msg} from '@lit/localize';
@customElement('my-greeter')
class MyGreeter extends LitElement {
@property()
who = 'World';
render() {
return msg(html`Hello <b>${this.who}</b>`);
}
}
import {html, LitElement} from 'lit';
import {msg} from '@lit/localize';
class MyGreeter extends LitElement {
static properties = {
who: {},
};
constructor() {
super();
this.who = 'World';
}
render() {
return msg(html`Hello <b>${this.who}</b>`);
}
}
customElements.define('my-greeter', MyGreeter);
{% endswitchable-sample %}
Any string or template that you would normally render with Lit can be localized, including ones with dynamic expressions and HTML markup.
Plain string:
msg('Hello World');
Plain string with expression (see strings with
expressions for details on str
):
msg(str`Hello ${name}`);
HTML template:
msg(html`Hello <b>World</b>`);
HTML template with expression:
msg(html`Hello <b>${name}</b>`);
Localized messages can also be nested inside HTML templates:
html`<button>${msg('Hello World')}</button>`;
While template expressions in your component code use named variables (e.g., ${name}
, ${count}
),
templates within locale files use positional parameters (${0}
, ${1}
, etc.) rather than variable names.
When a template with an id
is processed, variables are passed to the localized template in the
order they appear in the original template, and are referenced by their index:
/* In your component */
const address = 'user@host';
msg(str`checking...${address}`, {id: 'check_email'});
/* In your locale file (e.g., locales/en.js) */
export const templates = {
check_email: html`checking ${0}`
}
This positional referencing allows translators to reorder variables when needed for different languages:
/* In your component */
const name = 'Maria';
const count = 42;
msg(str`${name} has ${count} messages`, {id: 'user_messages'});
/* In your locale file */
export const templates = {
user_messages: html`${0} has ${1} messages`,
// For languages where order might need to change:
user_messages_reordered: html`Messages (${1}) for ${0}`
}
Note that the numeric references (${0}, ${1}, etc.) correspond to the order of variables in the original template, not their position in the resulting string.
For more examples of localized templates, see the runtime mode page.
Strings that contain an expression must be tagged with either html
or str
in
order to be localizable. You should prefer str
over html
when your string
doesn't contain any HTML markup, because it has slightly less performance
overhead. An error will be raised when you run the lit-localize
command if you
forget the html
or str
tag on a string with an expression.
Incorrect:
import {msg} from '@lit/localize';
msg(`Hello ${name}`);
Correct:
import {msg, str} from '@lit/localize';
msg(str`Hello ${name}`);
The str
tag is required in these cases because untagged template string
literals are evaluated to regular strings before they are received by the msg
function, which means dynamic expression values could not otherwise be captured
and substituted into the localized versions of the string.
A locale code is a string that identifies a human language, and sometimes also includes a region, script, or other variation.
Lit Localize does not mandate use any particular system of locale codes, though it is strongly recommended to use the BCP 47 language tag standard. Some examples of BCP 47 language tags are:
- en: English
- es-419: Spanish spoken in Latin America
- zh-Hans: Chinese written in Simplified script
Lit Localize defines a few terms that refer to locale codes. These terms are used in this documentation, in the Lit Localize config file, and in the Lit Localize API:
- Source locale
-
The locale that is used to write strings and templates in your source code.
- Target locales
-
The locales that your strings and templates can be translated into.
- Active locale
-
The global locale that is currently being displayed.
Lit Localize supports two output modes:
-
Runtime mode uses Lit Localize's APIs to load localized messages at runtime.
-
Transform mode eliminates the Lit Localize runtime code by building a separate JavaScript bundle for each locale.
Unsure which mode to use? Start with runtime mode. It's easy to switch modes
later because the core msg
API is identical.
In runtime mode, one JavaScript or TypeScript module is generated for each of your locales. Each module contains the localized templates for that locale. When the active locale switches, the module for that locale is imported, and all localized components are re-rendered.
Runtime mode makes switching locales very fast because a page reload is not required. However, there is a slight performance cost to rendering performance compared to transform mode.
// locales/es-419.ts
export const templates = {
// These IDs are automatically generated from template content
hf71d669027554f48: html`Hola <b>Mundo</b>`,
saed7d3734ce7f09d: html`Hola ${0}`, // Note: Variable references use numeric positions
};
See the runtime mode page for full details about runtime mode.
In transform mode, a separate folder is generated for each locale. Each folder
contains a complete standalone build of your application in that locale, with
msg
wrappers and all other Lit Localize runtime code completely removed.
Transform mode requires 0 KiB of extra JavaScript and is extremely fast to render. However, switching locales requires re-loading the page so that a new JavaScript bundle can be loaded.
// locales/en/my-element.js
render() {
return html`Hello <b>World</b>`;
}
// locales/es-419/my-element.js
render() {
return html`Hola <b>Mundo</b>`;
}
See the transform mode page for full details about transform mode.
Runtime mode | Transform mode | |
---|---|---|
Output | A dynamically loaded module for each target locale. | A standalone app build for each locale. |
Switch locales | Call setLocale() |
Reload page |
JS bytes | 1.27 KiB (minified + compressed) | 0 KiB |
Make template localizable | msg() |
msg() |
Configure | configureLocalization() |
configureTransformLocalization() |
Advantages |
|
|
The lit-localize
command-line tool looks for a config file called
lit-localize.json
in the current directory. Copy-paste the example below for a
quick start, and see the CLI and config
page for a full reference of all options.
If you're writing JavaScript, set the inputFiles
property to the location of
your .js
source files. If you're writing TypeScript, set the tsConfig
property to the location of your tsconfig.json
file, and leave inputFiles
blank.
{% switchable-sample %}
{
"$schema": "https://raw.githubusercontent.com/lit/lit/main/packages/localize-tools/config.schema.json",
"sourceLocale": "en",
"targetLocales": ["es-419", "zh-Hans"],
"tsConfig": "./tsconfig.json",
"output": {
"mode": "runtime",
"outputDir": "./src/generated/locales",
"localeCodesModule": "./src/generated/locale-codes.ts"
},
"interchange": {
"format": "xliff",
"xliffDir": "./xliff/"
}
}
{
"$schema": "https://raw.githubusercontent.com/lit/lit/main/packages/localize-tools/config.schema.json",
"sourceLocale": "en",
"targetLocales": ["es-419", "zh-Hans"],
"inputFiles": [
"src/**/*.js"
],
"output": {
"mode": "runtime",
"outputDir": "./src/generated/locales",
"localeCodesModule": "./src/generated/locale-codes.js"
},
"interchange": {
"format": "xliff",
"xliffDir": "./xliff/"
}
}
{% endswitchable-sample %}
Run lit-localize extract
to generate an XLIFF file for each target locale. XLIFF is an XML format
supported by most localization tools and services. XLIFF files will be written
to the directory specified by the interchange.xliffDir
config
option.
lit-localize extract
For example, given the source:
msg('Hello World');
msg(str`Hello ${name}`);
msg(html`Hello <b>World</b>`);
Then a <xliffDir>/<locale>.xlf
file will be generated for each target locale:
<!-- xliff/es-419.xlf -->
<trans-unit id="s3d58dee72d4e0c27">
<source>Hello World</source>
</trans-unit>
<trans-unit id="saed7d3734ce7f09d">
<source>Hello <x equiv-text="${name}"/></source>
</trans-unit>
<trans-unit id="hf71d669027554f48">
<source>Hello <x equiv-text="<b>"/>World<x equiv-text="</b>"/></source>
</trans-unit>
XLIFF files can be edited manually, but more typically they are sent to a third-party translation service where they are edited by language experts using specialized tools.
After uploading your XLIFF files to your chosen translation service, you will
eventually receive new XLIFF files in response. The new XLIFF files will look
just like the ones you uploaded, but with <target>
tags inserted into each
<trans-unit>
.
When you receive new translation XLIFF files, save them to your configured
interchange.xliffDir
directory, overwriting the original versions.
<!-- xliff/es-419.xlf -->
<trans-unit id="s3d58dee72d4e0c27">
<source>Hello World</source>
<target>Hola Mundo</target>
</trans-unit>
<trans-unit id="saed7d3734ce7f09d">
<source>Hello <x equiv-text="${name}"/></source>
<target>Hola <x equiv-text="${name}"/></target>
</trans-unit>
<trans-unit id="hf71d669027554f48">
<source>Hello <x equiv-text="<b>"/>World<x equiv-text="</b>"/></source>
<target>Hola <x equiv-text="<b>"/>Mundo<x equiv-text="</b>"/></target>
</trans-unit>
Use the lit-localize build
command to incorporate translations back into your
application. The behavior of this command depends on the output mode
you have configured.
lit-localize build
See the runtime mode and transform mode pages for details of how building in each mode works.
Use the desc
option to the msg
function to provide human-readable
descriptions for your strings and templates. These descriptions are shown to
translators by most translation tools, and are highly recommended to help
explain and contextualize the meaning of messages.
render() {
return html`<button>
${msg("Launch", {
desc: "Button that begins rocket launch sequence.",
})}
</button>`;
}
Descriptions are represented in XLIFF files using <note>
elements.
<trans-unit id="s512957aa09384646">
<source>Launch</source>
<note from="lit-localize">Button that begins rocket launch sequence.</note>
</trans-unit>
Lit Localize automatically generates an ID for every msg
call using a hash of
the string.
If two msg
calls share the same ID, then they are treated as the same message,
meaning they will be translated as a single unit and the same translations will
be substituted in both places.
For example, these two msg
calls are in two different files, but since they
have the same content they will be treated as one message:
// file1.js
msg('Hello World');
// file2.js
msg('Hello World');
The following content affects ID generation:
- String content
- HTML markup
- The position of expressions
- Whether the string is tagged with
html
The following content does not affect ID generation:
- The code inside an expression
- The computed value of an expression
- File location
For example, all of these messages share the same ID:
msg(html`Hello <b>${name}</b>`);
msg(html`Hello <b>${this.name}</b>`);
But this message has a different ID:
msg(html`Hello <i>${name}</i>`);
Note, while providing a description does not affect ID generation, multiple messages with the same ID but different description will produce an error during analysis to avoid ambiguity in the extracted translation unit. The following is considered invalid:
msg(html`Hello <b>${name}</b>`);
msg(html`Hello <b>${name}</b>`, {desc: 'A friendly greeting'});
Make sure that all messages with the same ID also have the same description.
Message IDs can be overridden by specifying the id
option to the msg
function. In some cases this may be necessary, such as when an identical string
has multiple meanings, because each might be written differently in another
language:
msg('Buffalo', {id: 'buffalo-animal-singular'});
msg('Buffalo', {id: 'buffalo-animal-plural'});
msg('Buffalo', {id: 'buffalo-city'});
msg('Buffalo', {id: 'buffalo-verb'});