Skip to content

Commit 372f9c2

Browse files
committed
Expand lit-localize runtime module
1 parent 6147f69 commit 372f9c2

File tree

2 files changed

+269
-15
lines changed

2 files changed

+269
-15
lines changed

README.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,109 @@
33
[![Published on npm](https://img.shields.io/npm/v/lit-localize.svg)](https://www.npmjs.com/package/lit-localize) [![Test Status](https://github.com/PolymerLabs/lit-localize/workflows/tests/badge.svg?branch=master)](https://github.com/PolymerLabs/lit-localize/actions?query=workflow%3Atests+branch%3Amaster+event%3Apush)
44

55
WIP
6+
7+
## API
8+
9+
The `lit-localize` module exports the following functions:
10+
11+
### `configureLocalization(configuration)`
12+
13+
Set runtime localization configuration.
14+
15+
In runtime mode, this function must be called once, before any calls to `msg()`.
16+
17+
The `configuration` object must have the following properties:
18+
19+
- `sourceLocale: string`: Required locale code in which source templates in this
20+
project are written, and the initial active locale.
21+
22+
- `targetLocales: Iterable<string>`: Required locale codes that are supported by
23+
this project. Should not include the `sourceLocale` code.
24+
25+
- `loadLocale: (locale: string) => Promise<LocaleModule>`: Required function
26+
that returns a promise of the localized templates for the given locale code.
27+
For security, this function will only ever be called with a `locale` that is
28+
contained by `targetLocales`.
29+
30+
Example:
31+
32+
```typescript
33+
configureLocalization({
34+
sourceLocale: 'en',
35+
targetLocales: ['es-419', 'zh_CN'],
36+
loadLocale: (locale) => import(`/${locale}.js`),
37+
});
38+
```
39+
40+
In transform mode, this function is not required, and calls to it will be
41+
replaced with `undefined`.
42+
43+
### `getLocale(): string`
44+
45+
Return the active locale code.
46+
47+
In transform mode, calls to this function are transformed into the static locale
48+
code string for each emitted locale.
49+
50+
### `setLocale(locale: string)`
51+
52+
Set the active locale code, and begin loading templates for that locale using
53+
the `loadLocale` function that was passed to `configureLocalization`.
54+
55+
In transform mode, calls to this function are replaced with `undefined`.
56+
57+
### `localeReady(): Promise`
58+
59+
Return a promise that is resolved when the next set of templates are loaded and
60+
available for rendering. Applications in runtime mode should always `await localeReady()` before rendering.
61+
62+
In transform mode, calls to this function are replaced with `undefined`.
63+
64+
### `msg(id: string, template, ...args): string|TemplateResult`
65+
66+
Make a string or lit-html template localizable.
67+
68+
The `id` parameter is a project-wide unique identifier for this template.
69+
70+
The `template` parameter can have any of these types:
71+
72+
- A plain string with no placeholders:
73+
74+
```typescript
75+
msg('greeting', 'Hello World!');
76+
```
77+
78+
- A lit-html
79+
[`TemplateResult`](https://lit-html.polymer-project.org/api/classes/_lit_html_.templateresult.html)
80+
that may contain embedded HTML:
81+
82+
```typescript
83+
msg('greeting', html`Hello <b>World</b>!`);
84+
```
85+
86+
- A function that returns a [template
87+
literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals)
88+
string that may contain placeholders. Placeholders may only reference
89+
parameters of the function, which will be called with the 3rd and onwards
90+
parameters to `msg`.
91+
92+
```typescript
93+
msg('greeting', (name) => `Hello ${name}!`, getUsername());
94+
```
95+
96+
- A function that returns a lit-html
97+
[`TemplateResult`](https://lit-html.polymer-project.org/api/classes/_lit_html_.templateresult.html)
98+
that may contain embedded HTML, and may contain placeholders. Placeholders may
99+
only reference parameters of the function, which will be called with the 3rd
100+
and onwards parameters to `msg`:
101+
102+
```typescript
103+
msg('greeting', (name) => html`Hello <b>${name}</b>!`, getUsername());
104+
```
105+
106+
In transform mode, calls to this function are replaced with the static localized
107+
template for each emitted locale. For example:
108+
109+
```typescript
110+
html`Hola <b>${getUsername()}!</b>`;
111+
```

src_client/index.ts

Lines changed: 163 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,159 @@ import {TemplateResult} from 'lit-html';
1313

1414
/* eslint-disable @typescript-eslint/no-explicit-any */
1515

16+
/**
17+
* Runtime configuration parameters for lit-localize.
18+
*/
19+
export interface Configuration {
20+
/**
21+
* Required locale code in which source templates in this project are written,
22+
* and the initial active locale.
23+
*/
24+
sourceLocale: string;
25+
26+
/**
27+
* Required locale codes that are supported by this project. Should not
28+
* include the `sourceLocale` code.
29+
*/
30+
targetLocales: Iterable<string>;
31+
32+
/**
33+
* Required function that returns a promise of the localized templates for the
34+
* given locale code. For security, this function will only ever be called
35+
* with a `locale` that is contained by `targetLocales`.
36+
*/
37+
loadLocale: (locale: string) => Promise<LocaleModule>;
38+
}
39+
40+
/**
41+
* The template-like types that can be passed to `msg`.
42+
*/
43+
export type TemplateLike =
44+
| string
45+
| TemplateResult
46+
| ((...args: any[]) => string)
47+
| ((...args: any[]) => TemplateResult);
48+
49+
/**
50+
* A mapping from template ID to template.
51+
*/
52+
export type TemplateMap = {[id: string]: TemplateLike};
53+
54+
/**
55+
* The expected exports of a locale module.
56+
*/
57+
export interface LocaleModule {
58+
templates: TemplateMap;
59+
}
60+
61+
class Deferred<T> {
62+
readonly promise: Promise<T>;
63+
private _resolve!: (value: T) => void;
64+
private _reject!: (error: Error) => void;
65+
settled = false;
66+
67+
constructor() {
68+
this.promise = new Promise<T>((resolve, reject) => {
69+
this._resolve = resolve;
70+
this._reject = reject;
71+
});
72+
}
73+
74+
resolve(value: T) {
75+
this.settled = true;
76+
this._resolve(value);
77+
}
78+
79+
reject(error: Error) {
80+
this.settled = true;
81+
this._reject(error);
82+
}
83+
}
84+
85+
let activeLocale = '';
86+
let sourceLocale: string | undefined;
87+
let validLocales: Set<string> | undefined;
88+
let loadLocale: ((locale: string) => Promise<LocaleModule>) | undefined;
89+
let templates: TemplateMap | undefined;
90+
let loading = new Deferred<void>();
91+
92+
/**
93+
* Set runtime configuration parameters for lit-localize. This function must be
94+
* called before using any other lit-localize function.
95+
*/
96+
export function configureLocalization(config: Configuration) {
97+
activeLocale = sourceLocale = config.sourceLocale;
98+
validLocales = new Set(config.targetLocales);
99+
validLocales.add(config.sourceLocale);
100+
loadLocale = config.loadLocale;
101+
}
102+
103+
/**
104+
* Return the active locale code. Returns empty string if lit-localize has not
105+
* yet been configured.
106+
*/
107+
export function getLocale(): string {
108+
return activeLocale;
109+
}
110+
111+
/**
112+
* Set the active locale code, and begin loading templates for that locale using
113+
* the `loadLocale` function that was passed to `configureLocalization`.
114+
*/
115+
export function setLocale(newLocale: string): void {
116+
if (
117+
newLocale === activeLocale ||
118+
!validLocales ||
119+
!validLocales.has(newLocale) ||
120+
!loadLocale
121+
) {
122+
return;
123+
}
124+
activeLocale = newLocale;
125+
templates = undefined;
126+
if (loading.settled) {
127+
loading = new Deferred();
128+
}
129+
if (newLocale === sourceLocale) {
130+
loading.resolve();
131+
} else {
132+
loadLocale(newLocale).then(
133+
(mod) => {
134+
if (newLocale === activeLocale) {
135+
templates = mod.templates;
136+
loading.resolve();
137+
}
138+
// Else another locale was requested in the meantime. Don't resolve or
139+
// reject, because the newer load call is going to use the same promise.
140+
// Note the user can call getLocale() after the promise resolves if they
141+
// need to check if the locale is still the one they expected to load.
142+
},
143+
(err) => {
144+
if (newLocale === activeLocale) {
145+
loading.reject(err);
146+
}
147+
}
148+
);
149+
}
150+
}
151+
152+
/**
153+
* Return a promise that is resolved when the next set of templates are loaded
154+
* and available for rendering.
155+
*/
156+
export function localeReady(): Promise<void> {
157+
return loading.promise;
158+
}
159+
160+
/**
161+
* Make a string or lit-html template localizable.
162+
*
163+
* @param id A project-wide unique identifier for this template.
164+
* @param template A string, a lit-html template, or a function that returns
165+
* either a string or lit-html template.
166+
* @param args In the case that `template` is a function, it is invoked with
167+
* the 3rd and onwards arguments to `msg`.
168+
*/
16169
export function msg(id: string, template: string): string;
17170

18171
export function msg(id: string, template: TemplateResult): TemplateResult;
@@ -29,24 +182,19 @@ export function msg<F extends (...args: any[]) => TemplateResult>(
29182
...params: Parameters<F>
30183
): TemplateResult;
31184

32-
/**
33-
* TODO(aomarks) This is a temporary stub implementation of the msg function. It
34-
* does not yet support actual localization, and is only used by the "transform"
35-
* output mode, since the user needs something to import.
36-
*
37-
* It may actually make more sense to move most of the generated code from
38-
* "runtime" mode into this library, so that users can
39-
* `import {msg} from 'lit-localize'` and tell it where to fetch translation
40-
* (this will make more sense after the planned revamp to support runtime async
41-
* locale loading and re-rendering).
42-
*/
43185
export function msg(
44-
_id: string,
45-
template: string | TemplateResult | (() => string | TemplateResult),
186+
id: string,
187+
template: TemplateLike,
46188
...params: any[]
47189
): string | TemplateResult {
48-
if (typeof template === 'function') {
49-
return (template as any)(...params);
190+
if (activeLocale !== sourceLocale && templates) {
191+
const localized = templates[id];
192+
if (localized) {
193+
template = localized;
194+
}
195+
}
196+
if (template instanceof Function) {
197+
return template(...params);
50198
}
51199
return template;
52200
}

0 commit comments

Comments
 (0)