Skip to content

Commit e3bfd9b

Browse files
committed
Use a window event instead of custom callback registry for locale changes
1 parent d45d73a commit e3bfd9b

File tree

5 files changed

+98
-138
lines changed

5 files changed

+98
-138
lines changed

README.md

+17-19
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ configureLocalization({
4040
In transform mode, this function is not required, and calls to it will be
4141
replaced with `undefined`.
4242

43-
### `getLocale(): string`
43+
### `getLocale() => string`
4444

4545
Return the active locale code.
4646

@@ -54,15 +54,15 @@ the `loadLocale` function that was passed to `configureLocalization`.
5454

5555
In transform mode, calls to this function are replaced with `undefined`.
5656

57-
### `localeReady(): Promise`
57+
### `localeReady() => Promise`
5858

5959
Return a promise that is resolved when the next set of templates are loaded and
6060
available for rendering. Applications in runtime mode should always `await localeReady()` before rendering.
6161

6262
In transform mode, calls to this function are replaced with
6363
`Promise.resolve(undefined)`.
6464

65-
### `msg(id: string, template, ...args): string|TemplateResult`
65+
### `msg(id: string, template, ...args) => string|TemplateResult`
6666

6767
Make a string or lit-html template localizable.
6868

@@ -111,27 +111,25 @@ template for each emitted locale. For example:
111111
html`Hola <b>${getUsername()}!</b>`;
112112
```
113113

114-
### `addLocaleChangeCallback(callback: () => void)`
114+
### `LOCALE_CHANGED_EVENT: string`
115115

116-
Add the given function to the set of callbacks that will be invoked whenever the
117-
locale changes and its localized messages are ready.
116+
Whenever the locale changes and templates have finished loading, an event by
117+
this name (`"lit-localize-locale-changed"`) is dispatched to `window`.
118118

119-
Use this function to re-render your application whenever the locale is changed.
119+
You can listen for this event to know when your application should be
120+
re-rendered following a locale change. See also the
121+
[`Localized`](#localized-mixin) mixin, which automatically re-renders
122+
`LitElement` classes using this event.
120123

121-
If you are using `LitElement`, consider using
122-
[`LocalizedLitElement`](#localizedlitelement), which performs this re-rendering
123-
automatically.
124-
125-
In transform mode, calls to this function are replaced with `undefined`.
126-
127-
### `removeLocaleChangeCallback(callback: () => void)`
128-
129-
Remove the given function from the set of callbacks that will be invoked
130-
whenever the locale changes and its localized messages are ready.
124+
```typescript
125+
import {LOCALE_CHANGED_EVENT} from 'lit-localize';
131126

132-
In transform mode, calls to this function are replaced with `undefined`.
127+
window.addEventListener(LOCALE_CHANGED_EVENT, () => {
128+
renderApplication();
129+
});
130+
```
133131

134-
## `LocalizedLitElement`
132+
## `Localized` mixin
135133

136134
If you are using [LitElement](https://lit-element.polymer-project.org/), then
137135
you can use the `Localized`

src/outputters/transform.ts

+54-57
Original file line numberDiff line numberDiff line change
@@ -118,62 +118,62 @@ class Transformer {
118118
return undefined;
119119
}
120120

121-
// configureLocalization(...) -> undefined
122-
if (
123-
this.isCallToTaggedFunction(node, '_LIT_LOCALIZE_CONFIGURE_LOCALIZATION_')
124-
) {
125-
return ts.createIdentifier('undefined');
126-
}
121+
if (ts.isCallExpression(node)) {
122+
// configureLocalization(...) -> undefined
123+
if (
124+
this.typeHasProperty(
125+
node.expression,
126+
'_LIT_LOCALIZE_CONFIGURE_LOCALIZATION_'
127+
)
128+
) {
129+
return ts.createIdentifier('undefined');
130+
}
127131

128-
// getLocale() -> "es-419"
129-
if (this.isCallToTaggedFunction(node, '_LIT_LOCALIZE_GET_LOCALE_')) {
130-
return ts.createStringLiteral(this.locale);
131-
}
132+
// getLocale() -> "es-419"
133+
if (this.typeHasProperty(node.expression, '_LIT_LOCALIZE_GET_LOCALE_')) {
134+
return ts.createStringLiteral(this.locale);
135+
}
132136

133-
// setLocale("es-419") -> undefined
134-
if (this.isCallToTaggedFunction(node, '_LIT_LOCALIZE_SET_LOCALE_')) {
135-
return ts.createIdentifier('undefined');
136-
}
137+
// setLocale("es-419") -> undefined
138+
if (this.typeHasProperty(node.expression, '_LIT_LOCALIZE_SET_LOCALE_')) {
139+
return ts.createIdentifier('undefined');
140+
}
137141

138-
// localeReady() -> Promise.resolve(undefined)
139-
if (this.isCallToTaggedFunction(node, '_LIT_LOCALIZE_LOCALE_READY_')) {
140-
return ts.createCall(
141-
ts.createPropertyAccess(ts.createIdentifier('Promise'), 'resolve'),
142-
[],
143-
[ts.createIdentifier('undefined')]
144-
);
145-
}
142+
// localeReady() -> Promise.resolve(undefined)
143+
if (
144+
this.typeHasProperty(node.expression, '_LIT_LOCALIZE_LOCALE_READY_')
145+
) {
146+
return ts.createCall(
147+
ts.createPropertyAccess(ts.createIdentifier('Promise'), 'resolve'),
148+
[],
149+
[ts.createIdentifier('undefined')]
150+
);
151+
}
146152

147-
// addLocaleChangeCallback(...) -> undefined
148-
if (
149-
this.isCallToTaggedFunction(
150-
node,
151-
'_LIT_LOCALIZE_ADD_LOCALE_CHANGE_CALLBACK_'
152-
)
153-
) {
154-
return ts.createIdentifier('undefined');
153+
// Localized(LitElement) -> LitElement
154+
if (this.typeHasProperty(node.expression, '_LIT_LOCALIZE_LOCALIZED_')) {
155+
if (node.arguments.length !== 1) {
156+
// TODO(aomarks) Surface as diagnostic instead.
157+
throw new KnownError(
158+
`Expected Localized mixin call to have one argument, ` +
159+
`got ${node.arguments.length}`
160+
);
161+
}
162+
return node.arguments[0];
163+
}
155164
}
156165

157-
// removeLocaleChangeCallback(...) -> undefined
166+
// LOCALE_CHANGED_EVENT -> "lit-localize-locale-changed"
167+
//
168+
// This is slightly odd, but by replacing the LOCALE_CHANGED_EVENT const
169+
// with its static string value, we don't have to be smart about deciding
170+
// when to remove the 'lit-localize' module import, since we can assume that
171+
// everything it exports will be transformed out.
158172
if (
159-
this.isCallToTaggedFunction(
160-
node,
161-
'_LIT_LOCALIZE_REMOVE_LOCALE_CHANGE_CALLBACK_'
162-
)
173+
ts.isIdentifier(node) &&
174+
this.typeHasProperty(node, '_LIT_LOCALIZE_LOCALE_CHANGED_EVENT_')
163175
) {
164-
return ts.createIdentifier('undefined');
165-
}
166-
167-
// Localized(LitElement) -> LitElement
168-
if (this.isCallToTaggedFunction(node, '_LIT_LOCALIZE_LOCALIZED_')) {
169-
if (node.arguments.length !== 1) {
170-
// TODO(aomarks) Surface as diagnostic instead.
171-
throw new KnownError(
172-
`Expected Localized mixin call to have one argument, ` +
173-
`got ${node.arguments.length}`
174-
);
175-
}
176-
return node.arguments[0];
176+
return ts.createStringLiteral('lit-localize-locale-changed');
177177
}
178178

179179
return ts.visitEachChild(node, this.boundVisitNode, this.context);
@@ -429,19 +429,16 @@ class Transformer {
429429
}
430430

431431
/**
432-
* Return whether the given node is call to a function which is is "tagged"
433-
* with the given special identifying property (e.g. "_LIT_LOCALIZE_MSG_").
432+
* Return whether the tpe of the given node is "tagged" with the given special
433+
* identifying property (e.g. "_LIT_LOCALIZE_MSG_").
434434
*/
435-
isCallToTaggedFunction(
435+
typeHasProperty(
436436
node: ts.Node,
437-
tagProperty: string
437+
propertyName: string
438438
): node is ts.CallExpression {
439-
if (!ts.isCallExpression(node)) {
440-
return false;
441-
}
442-
const type = this.typeChecker.getTypeAtLocation(node.expression);
439+
const type = this.typeChecker.getTypeAtLocation(node);
443440
const props = this.typeChecker.getPropertiesOfType(type);
444-
return props.some((prop) => prop.escapedName === tagProperty);
441+
return props.some((prop) => prop.escapedName === propertyName);
445442
}
446443
}
447444

src/tests/transform.unit.test.ts

+4-15
Original file line numberDiff line numberDiff line change
@@ -382,23 +382,12 @@ test('localeReady() -> Promise.resolve(undefined)', (t) => {
382382
);
383383
});
384384

385-
test('addLocaleChangeCallback -> undefined', (t) => {
385+
test('LOCALE_CHANGED_EVENT => "lit-localize-locale-changed"', (t) => {
386386
checkTransform(
387387
t,
388-
`import {addLocaleChangeCallback} from './lib_client/index.js';
389-
addLocaleChangeCallback(() => console.log('ok'));`,
390-
`undefined`,
391-
[],
392-
false
393-
);
394-
});
395-
396-
test('removeLocaleChangeCallback -> undefined', (t) => {
397-
checkTransform(
398-
t,
399-
`import {removeLocaleChangeCallback} from './lib_client/index.js';
400-
removeLocaleChangeCallback(() => console.log('ok'));`,
401-
`undefined`,
388+
`import {LOCALE_CHANGED_EVENT} from './lib_client/index.js';
389+
window.addEventListener(LOCALE_CHANGED_EVENT, () => console.log('ok'));`,
390+
`window.addEventListener('lit-localize-locale-changed', () => console.log('ok'));`,
402391
[],
403392
false
404393
);

src_client/index.ts

+14-37
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,18 @@ let validLocales: Set<string> | undefined;
8888
let loadLocale: ((locale: string) => Promise<LocaleModule>) | undefined;
8989
let templates: TemplateMap | undefined;
9090
let loading = new Deferred<void>();
91-
const changeLocaleCallbacks = new Set<() => void>();
91+
92+
/**
93+
* Whenever the locale changes and templates have finished loading, an event by
94+
* this name ("lit-localize-locale-changed") is dispatched to window.
95+
*
96+
* You can listen for this event to know when your application should be
97+
* re-rendered following a locale change. See also the Localized mixin, which
98+
* automatically re-renders LitElement classes using this event.
99+
*/
100+
export const LOCALE_CHANGED_EVENT: string & {
101+
_LIT_LOCALIZE_LOCALE_CHANGED_EVENT_?: never;
102+
} = 'lit-localize-locale-changed';
92103

93104
/**
94105
* Set runtime configuration parameters for lit-localize. This function must be
@@ -135,14 +146,14 @@ export const setLocale: ((newLocale: string) => void) & {
135146
}
136147
if (newLocale === sourceLocale) {
137148
loading.resolve();
138-
fireChangeLocaleCallbacks();
149+
window.dispatchEvent(new Event(LOCALE_CHANGED_EVENT));
139150
} else {
140151
loadLocale(newLocale).then(
141152
(mod) => {
142153
if (newLocale === activeLocale) {
143154
templates = mod.templates;
144155
loading.resolve();
145-
fireChangeLocaleCallbacks();
156+
window.dispatchEvent(new Event(LOCALE_CHANGED_EVENT));
146157
}
147158
// Else another locale was requested in the meantime. Don't resolve or
148159
// reject, because the newer load call is going to use the same promise.
@@ -166,40 +177,6 @@ export const localeReady: (() => Promise<void>) & {
166177
_LIT_LOCALIZE_LOCALE_READY_?: never;
167178
} = () => loading.promise;
168179

169-
/**
170-
* Add the given function to the set of callbacks that will be invoked whenever
171-
* the locale changes and its localized messages are ready.
172-
*
173-
* Use this function to re-render your application whenever the locale is changed.
174-
*
175-
* If you are using LitElement, consider using LocalizedLitElement, which performs
176-
* this re-rendering automatically.
177-
*/
178-
export const addLocaleChangeCallback: ((callback: () => void) => void) & {
179-
_LIT_LOCALIZE_ADD_LOCALE_CHANGE_CALLBACK_?: never;
180-
} = (callback: () => void) => {
181-
changeLocaleCallbacks.add(callback);
182-
};
183-
184-
/**
185-
* Remove the given function from the set of callbacks that will be invoked
186-
* whenever the locale changes and its localized messages are ready.
187-
*/
188-
export const removeLocaleChangeCallback: ((callback: () => void) => void) & {
189-
_LIT_LOCALIZE_REMOVE_LOCALE_CHANGE_CALLBACK_?: never;
190-
} = (callback: () => void) => {
191-
changeLocaleCallbacks.delete(callback);
192-
};
193-
194-
/**
195-
* Fire all of the registered change locale callback functions.
196-
*/
197-
const fireChangeLocaleCallbacks = () => {
198-
for (const callback of changeLocaleCallbacks) {
199-
callback();
200-
}
201-
};
202-
203180
/**
204181
* Make a string or lit-html template localizable.
205182
*

src_client/localized-element.ts

+9-10
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@
1010
*/
1111

1212
import {LitElement} from 'lit-element';
13-
import {
14-
addLocaleChangeCallback,
15-
removeLocaleChangeCallback,
16-
localeReady,
17-
} from './index.js';
13+
import {localeReady, LOCALE_CHANGED_EVENT} from './index.js';
1814

1915
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2016
type Constructor<T> = new (...args: any[]) => T;
@@ -42,17 +38,20 @@ type Constructor<T> = new (...args: any[]) => T;
4238
* }
4339
*/
4440
function _Localized<T extends Constructor<LitElement>>(Base: T): T {
45-
class LocalizedLitElement extends Base {
46-
private __boundRequestUpdate = this.requestUpdate.bind(this);
41+
class Localized extends Base {
42+
private __boundRequestUpdate = () => this.requestUpdate();
4743

4844
connectedCallback() {
4945
super.connectedCallback();
50-
addLocaleChangeCallback(this.__boundRequestUpdate);
46+
window.addEventListener(LOCALE_CHANGED_EVENT, this.__boundRequestUpdate);
5147
}
5248

5349
disconnectedCallback() {
5450
super.disconnectedCallback();
55-
removeLocaleChangeCallback(this.__boundRequestUpdate);
51+
window.removeEventListener(
52+
LOCALE_CHANGED_EVENT,
53+
this.__boundRequestUpdate
54+
);
5655
}
5756

5857
protected async performUpdate(): Promise<unknown> {
@@ -61,7 +60,7 @@ function _Localized<T extends Constructor<LitElement>>(Base: T): T {
6160
}
6261
}
6362

64-
return LocalizedLitElement;
63+
return Localized;
6564
}
6665

6766
export const Localized: typeof _Localized & {

0 commit comments

Comments
 (0)