Skip to content

Commit 935bb91

Browse files
authored
Merge pull request #42 from PolymerLabs/transform-more
Transform configureTransformLocalization
2 parents debcf30 + d89861c commit 935bb91

File tree

9 files changed

+164
-23
lines changed

9 files changed

+164
-23
lines changed

README.md

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,16 @@ The `lit-localize` module exports the following functions:
2121
Set configuration parameters for lit-localize when in runtime mode. Returns an
2222
object with functions:
2323

24-
- [`getLocale`](#getLocale): Return the active locale code.
25-
- [`setLocale`](#setLocale): Set the active locale code.
24+
- [`getLocale`](#getlocale-string): Return the active locale code.
25+
- [`setLocale`](#setlocalelocale-string-promise): Set the active locale code.
2626

2727
Throws if called more than once.
2828

29+
When in transform mode, the lit-localize CLI will error if this function is
30+
called. Use
31+
[`configureTransformLocalization`](#configuretransformlocalizationconfiguration)
32+
instead.
33+
2934
The `configuration` object must have the following properties:
3035

3136
- `sourceLocale: string`: Required locale code in which source templates in this
@@ -52,12 +57,13 @@ const {getLocale, setLocale} = configureLocalization({
5257
### `configureTransformLocalization(configuration)`
5358

5459
Set configuration parameters for lit-localize when in transform mode. Returns an
55-
object with functions:
60+
object with function:
5661

57-
- [`getLocale`](#getLocale): Return the active locale code.
62+
- [`getLocale`](#getlocale-string): Return the active locale code.
5863

59-
(Note that [`setLocale`](#setLocale) is not available, because changing locales
60-
at runtime is not supported in transform mode.)
64+
(Note that [`setLocale`](#setlocalelocale-string-promise) is not available from
65+
this function, because changing locales at runtime is not supported in transform
66+
mode.)
6167

6268
Throws if called more than once.
6369

@@ -74,14 +80,19 @@ const {getLocale} = configureLocalization({
7480
});
7581
```
7682

83+
In transform mode, calls to this function are transformed to an object with a
84+
`getLocale` implementation that returns the static locale code for each locale
85+
bundle. For example:
86+
87+
```typescript
88+
const {getLocale} = {getLocale: () => 'es-419'};
89+
```
90+
7791
### `getLocale(): string`
7892

7993
Return the active locale code.
8094

81-
In transform mode, calls to this function are transformed into the static locale
82-
code string for each emitted locale.
83-
84-
### `setLocale(locale: string)`
95+
### `setLocale(locale: string): Promise`
8596

8697
Set the active locale code, and begin loading templates for that locale using
8798
the `loadLocale` function that was passed to `configureLocalization`. Returns a

src/outputters/transform.ts

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function transformOutput(
5252
}
5353
opts.outDir = pathLib.join(outRoot, '/', locale);
5454
program.emit(undefined, undefined, undefined, undefined, {
55-
before: [litLocalizeTransform(translations, program)],
55+
before: [litLocalizeTransform(translations, locale, program)],
5656
});
5757
}
5858
}
@@ -62,10 +62,11 @@ export function transformOutput(
6262
*/
6363
export function litLocalizeTransform(
6464
translations: Map<string, Message> | undefined,
65+
locale: string,
6566
program: ts.Program
6667
): ts.TransformerFactory<ts.SourceFile> {
6768
return (context) => {
68-
const transformer = new Transformer(context, translations, program);
69+
const transformer = new Transformer(context, translations, locale, program);
6970
return (file) => ts.visitNode(file, transformer.boundVisitNode);
7071
};
7172
}
@@ -76,26 +77,32 @@ export function litLocalizeTransform(
7677
class Transformer {
7778
private context: ts.TransformationContext;
7879
private translations: Map<string, Message> | undefined;
80+
private locale: string;
7981
private typeChecker: ts.TypeChecker;
8082
boundVisitNode = this.visitNode.bind(this);
8183

8284
constructor(
8385
context: ts.TransformationContext,
8486
translations: Map<string, Message> | undefined,
87+
locale: string,
8588
program: ts.Program
8689
) {
8790
this.context = context;
8891
this.translations = translations;
92+
this.locale = locale;
8993
this.typeChecker = program.getTypeChecker();
9094
}
9195

9296
/**
9397
* Top-level delegating visitor for all nodes.
9498
*/
9599
visitNode(node: ts.Node): ts.VisitResult<ts.Node> {
100+
// msg('greeting', 'hello') -> 'hola'
96101
if (isMsgCall(node, this.typeChecker)) {
97102
return this.replaceMsgCall(node);
98103
}
104+
105+
// html`<b>${msg('greeting', 'hello')}</b>` -> html`<b>hola</b>`
99106
if (isLitTemplate(node)) {
100107
// If an html-tagged template literal embeds a msg call, we want to
101108
// collapse the result of that msg call into the parent template.
@@ -105,9 +112,49 @@ class Transformer {
105112
)
106113
);
107114
}
115+
116+
// import ... from 'lit-localize' -> (removed)
108117
if (this.isLitLocalizeImport(node)) {
109118
return undefined;
110119
}
120+
121+
// configureTransformLocalization(...) -> {getLocale: () => "es-419"}
122+
if (
123+
this.isCallToTaggedFunction(
124+
node,
125+
'_LIT_LOCALIZE_CONFIGURE_TRANSFORM_LOCALIZATION_'
126+
)
127+
) {
128+
return ts.createObjectLiteral(
129+
[
130+
ts.createPropertyAssignment(
131+
ts.createIdentifier('getLocale'),
132+
ts.createArrowFunction(
133+
undefined,
134+
undefined,
135+
[],
136+
undefined,
137+
ts.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
138+
ts.createStringLiteral(this.locale)
139+
)
140+
),
141+
],
142+
false
143+
);
144+
}
145+
146+
// configureLocalization(...) -> Error
147+
if (
148+
this.isCallToTaggedFunction(node, '_LIT_LOCALIZE_CONFIGURE_LOCALIZATION_')
149+
) {
150+
// TODO(aomarks) This error is not surfaced earlier in the analysis phase
151+
// as a nicely formatted diagnostic, but it should be.
152+
throw new KnownError(
153+
'Cannot use configureLocalization in transform mode. ' +
154+
'Use configureTransformLocalization instead.'
155+
);
156+
}
157+
111158
return ts.visitEachChild(node, this.boundVisitNode, this.context);
112159
}
113160

@@ -357,6 +404,22 @@ class Transformer {
357404
}
358405
return false;
359406
}
407+
408+
/**
409+
* Return whether the given node is call to a function which is is "tagged"
410+
* with the given special identifying property (e.g. "_LIT_LOCALIZE_MSG_").
411+
*/
412+
isCallToTaggedFunction(
413+
node: ts.Node,
414+
tagProperty: string
415+
): node is ts.CallExpression {
416+
if (!ts.isCallExpression(node)) {
417+
return false;
418+
}
419+
const type = this.typeChecker.getTypeAtLocation(node.expression);
420+
const props = this.typeChecker.getPropertiesOfType(type);
421+
return props.some((prop) => prop.escapedName === tagProperty);
422+
}
360423
}
361424

362425
/**

src/tests/transform.unit.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ function checkTransform(
3232
opts?: {
3333
messages?: Message[];
3434
autoImport?: boolean;
35+
locale?: string;
3536
}
3637
) {
3738
if (opts?.autoImport ?? true) {
@@ -57,7 +58,11 @@ function checkTransform(
5758
options.typeRoots = [];
5859
const result = compileTsFragment(inputTs, options, cache, (program) => ({
5960
before: [
60-
litLocalizeTransform(makeMessageIdMap(opts?.messages ?? []), program),
61+
litLocalizeTransform(
62+
makeMessageIdMap(opts?.messages ?? []),
63+
opts?.locale ?? 'en',
64+
program
65+
),
6166
],
6267
}));
6368

@@ -328,3 +333,35 @@ test('exclude different msg function', (t) => {
328333
{autoImport: false}
329334
);
330335
});
336+
337+
test('configureTransformLocalization() -> {getLocale: () => "es-419"}', (t) => {
338+
checkTransform(
339+
t,
340+
`import {configureTransformLocalization} from './lib_client/index.js';
341+
const {getLocale} = configureTransformLocalization({
342+
sourceLocale: 'en',
343+
});
344+
const locale = getLocale();`,
345+
`const {getLocale} = {getLocale: () => 'es-419'};
346+
const locale = getLocale();`,
347+
{locale: 'es-419'}
348+
);
349+
});
350+
351+
test('configureLocalization() throws', (t) => {
352+
t.throws(
353+
() =>
354+
checkTransform(
355+
t,
356+
`import {configureLocalization} from './lib_client/index.js';
357+
configureLocalization({
358+
sourceLocale: 'en',
359+
targetLocales: ['es-419'],
360+
loadLocale: (locale: string) => import(\`/\${locale}.js\`),
361+
});`,
362+
`undefined;`
363+
),
364+
undefined,
365+
'Cannot use configureLocalization in transform mode'
366+
);
367+
});

src_client/index.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,11 @@ let loading = new Deferred<void>();
111111
*
112112
* Throws if called more than once.
113113
*/
114-
export function configureLocalization(config: RuntimeConfiguration) {
114+
export const configureLocalization: ((
115+
config: RuntimeConfiguration
116+
) => {getLocale: typeof getLocale; setLocale: typeof setLocale}) & {
117+
_LIT_LOCALIZE_CONFIGURE_LOCALIZATION_?: never;
118+
} = (config: RuntimeConfiguration) => {
115119
if (configured === true) {
116120
throw new Error('lit-localize can only be configured once');
117121
}
@@ -121,7 +125,7 @@ export function configureLocalization(config: RuntimeConfiguration) {
121125
validLocales.add(config.sourceLocale);
122126
loadLocale = config.loadLocale;
123127
return {getLocale, setLocale};
124-
}
128+
};
125129

126130
/**
127131
* Set configuration parameters for lit-localize when in transform mode. Returns
@@ -131,21 +135,27 @@ export function configureLocalization(config: RuntimeConfiguration) {
131135
*
132136
* Throws if called more than once.
133137
*/
134-
export function configureTransformLocalization(config: TransformConfiguration) {
138+
export const configureTransformLocalization: ((
139+
config: TransformConfiguration
140+
) => {getLocale: typeof getLocale}) & {
141+
_LIT_LOCALIZE_CONFIGURE_TRANSFORM_LOCALIZATION_?: never;
142+
} = (config: TransformConfiguration) => {
135143
if (configured === true) {
136144
throw new Error('lit-localize can only be configured once');
137145
}
138146
configured = true;
139147
activeLocale = sourceLocale = config.sourceLocale;
140148
return {getLocale};
141-
}
149+
};
142150

143151
/**
144152
* Return the active locale code.
145153
*/
146-
function getLocale(): string {
154+
const getLocale: (() => string) & {
155+
_LIT_LOCALIZE_GET_LOCALE_?: never;
156+
} = () => {
147157
return activeLocale;
148-
}
158+
};
149159

150160
/**
151161
* Set the active locale code, and begin loading templates for that locale using
@@ -161,7 +171,9 @@ function getLocale(): string {
161171
* Throws if the given locale is not contained by the configured `sourceLocale`
162172
* or `targetLocales`.
163173
*/
164-
function setLocale(newLocale: string): Promise<void> {
174+
const setLocale: ((newLocale: string) => void) & {
175+
_LIT_LOCALIZE_SET_LOCALE_?: never;
176+
} = (newLocale: string) => {
165177
if (!configured || !validLocales || !loadLocale) {
166178
throw new Error('Must call configureLocalization before setLocale');
167179
}
@@ -202,7 +214,7 @@ function setLocale(newLocale: string): Promise<void> {
202214
);
203215
}
204216
return loading.promise;
205-
}
217+
};
206218

207219
/**
208220
* Make a string or lit-html template localizable.

testdata/transform/goldens/foo.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import {html} from 'lit-html';
2-
import {msg} from '../../../lib_client/index.js';
2+
import {
3+
msg,
4+
configureTransformLocalization,
5+
} from '../../../lib_client/index.js';
6+
7+
const {getLocale} = configureTransformLocalization({sourceLocale: 'en'});
8+
console.log(`Locale is ${getLocale()}`);
39

410
msg('string', 'Hello World!');
511

testdata/transform/goldens/tsout/en/foo.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {html} from 'lit-html';
2+
const {getLocale} = {getLocale: () => 'en'};
3+
console.log(`Locale is ${getLocale()}`);
24
('Hello World!');
35
html`Hello <b><i>World!</i></b>`;
46
`Hello World!`;

testdata/transform/goldens/tsout/es-419/foo.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {html} from 'lit-html';
2+
const {getLocale} = {getLocale: () => 'es-419'};
3+
console.log(`Locale is ${getLocale()}`);
24
`Hola Mundo!`;
35
html`Hola <b><i>Mundo!</i></b>`;
46
`Hola World!`;

testdata/transform/goldens/tsout/zh_CN/foo.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {html} from 'lit-html';
2+
const {getLocale} = {getLocale: () => 'zh_CN'};
3+
console.log(`Locale is ${getLocale()}`);
24
`\u4F60\u597D\uFF0C\u4E16\u754C\uFF01`;
35
html`你好, <b><i>世界!</i></b>`;
46
`Hello World!`;

testdata/transform/input/foo.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import {html} from 'lit-html';
2-
import {msg} from '../../../lib_client/index.js';
2+
import {
3+
msg,
4+
configureTransformLocalization,
5+
} from '../../../lib_client/index.js';
6+
7+
const {getLocale} = configureTransformLocalization({sourceLocale: 'en'});
8+
console.log(`Locale is ${getLocale()}`);
39

410
msg('string', 'Hello World!');
511

0 commit comments

Comments
 (0)