Skip to content

Commit 0c6a455

Browse files
committed
Merge branch 'master' into transform-more
2 parents 1770253 + debcf30 commit 0c6a455

File tree

9 files changed

+258
-178
lines changed

9 files changed

+258
-178
lines changed

README.md

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,29 @@
22

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

5-
WIP
6-
75
## API
86

97
The `lit-localize` module exports the following functions:
108

9+
> Note that lit-localize relies on distinctive, annotated TypeScript type
10+
> signatures to identify calls to `msg` and other APIs during analysis of your
11+
> code. Casting a lit-localize function to a type that does not include its
12+
> annotation will prevent lit-localize from being able to extract and transform
13+
> templates from your application. For example, a cast like
14+
> `(msg as any)("greeting", "Hello")` will not be identified. It is safe to
15+
> re-assign lit-localize functions or pass them as parameters, as long as the
16+
> distinctive type signature is preserved. If needed, you can reference each
17+
> function's distinctive type with e.g. `typeof msg`.
18+
1119
### `configureLocalization(configuration)`
1220

13-
Set runtime localization configuration.
21+
Set configuration parameters for lit-localize when in runtime mode. Returns an
22+
object with functions:
23+
24+
- [`getLocale`](#getLocale): Return the active locale code.
25+
- [`setLocale`](#setLocale): Set the active locale code.
1426

15-
In runtime mode, this function must be called once, before any calls to `msg()`.
27+
Throws if called more than once.
1628

1729
The `configuration` object must have the following properties:
1830

@@ -30,15 +42,37 @@ The `configuration` object must have the following properties:
3042
Example:
3143

3244
```typescript
33-
configureLocalization({
45+
const {getLocale, setLocale} = configureLocalization({
3446
sourceLocale: 'en',
3547
targetLocales: ['es-419', 'zh_CN'],
3648
loadLocale: (locale) => import(`/${locale}.js`),
3749
});
3850
```
3951

40-
In transform mode, this function is not required, and calls to it will be
41-
replaced with `undefined`.
52+
### `configureTransformLocalization(configuration)`
53+
54+
Set configuration parameters for lit-localize when in transform mode. Returns an
55+
object with functions:
56+
57+
- [`getLocale`](#getLocale): Return the active locale code.
58+
59+
(Note that [`setLocale`](#setLocale) is not available, because changing locales
60+
at runtime is not supported in transform mode.)
61+
62+
Throws if called more than once.
63+
64+
The `configuration` object must have the following properties:
65+
66+
- `sourceLocale: string`: Required locale code in which source templates in this
67+
project are written, and the active locale.
68+
69+
Example:
70+
71+
```typescript
72+
const {getLocale} = configureLocalization({
73+
sourceLocale: 'en',
74+
});
75+
```
4276

4377
### `getLocale(): string`
4478

@@ -50,17 +84,17 @@ code string for each emitted locale.
5084
### `setLocale(locale: string)`
5185

5286
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`
87+
the `loadLocale` function that was passed to `configureLocalization`. Returns a
88+
promise that resolves when the next locale is ready to be rendered.
5889

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.
90+
Note that if a second call to `setLocale` is made while the first requested
91+
locale is still loading, then the second call takes precedence, and the promise
92+
returned from the first call will resolve when second locale is ready. If you
93+
need to know whether a particular locale was loaded, check `getLocale` after the
94+
promise resolves.
6195

62-
In transform mode, calls to this function are replaced with
63-
`Promise.resolve(undefined)`.
96+
Throws if the given locale is not contained by the configured `sourceLocale` or
97+
`targetLocales`.
6498

6599
### `msg(id: string, template, ...args): string|TemplateResult`
66100

src/outputters/runtime.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {applyPatches, Patches} from '../patches';
1414
import {Locale} from '../locales';
1515
import {Config} from '../config';
1616
import {KnownError} from '../error';
17-
import {escapeStringLiteral} from '../typescript';
17+
import {escapeStringToEmbedInTemplateLiteral} from '../typescript';
1818
import * as fs from 'fs';
1919
import * as pathLib from 'path';
2020

@@ -444,7 +444,7 @@ function makeMessageString(
444444
const fragments = [];
445445
for (const content of contents) {
446446
if (typeof content === 'string') {
447-
fragments.push(escapeStringLiteral(content));
447+
fragments.push(escapeStringToEmbedInTemplateLiteral(content));
448448
} else {
449449
fragments.push(content.untranslatable);
450450
}

src/outputters/transform.ts

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import {Message} from '../messages';
1313
import {Locale} from '../locales';
1414
import {Config} from '../config';
1515
import * as ts from 'typescript';
16-
import {isLitExpression, isMsgCall, isStaticString} from '../program-analysis';
16+
import {isLitTemplate, isMsgCall, isStaticString} from '../program-analysis';
1717
import {KnownError} from '../error';
18-
import {escapeStringLiteral} from '../typescript';
18+
import {escapeStringToEmbedInTemplateLiteral} from '../typescript';
1919
import * as pathLib from 'path';
2020

2121
/**
@@ -103,7 +103,7 @@ class Transformer {
103103
}
104104

105105
// html`<b>${msg('greeting', 'hello')}</b>` -> html`<b>hola</b>`
106-
if (isLitExpression(node)) {
106+
if (isLitTemplate(node)) {
107107
// If an html-tagged template literal embeds a msg call, we want to
108108
// collapse the result of that msg call into the parent template.
109109
return tagLit(
@@ -168,7 +168,7 @@ class Transformer {
168168
!(
169169
ts.isStringLiteral(arg1) ||
170170
ts.isTemplateLiteral(arg1) ||
171-
isLitExpression(arg1) ||
171+
isLitTemplate(arg1) ||
172172
ts.isArrowFunction(arg1)
173173
)
174174
) {
@@ -189,19 +189,19 @@ class Transformer {
189189
if (
190190
!ts.isStringLiteral(arg1.body) &&
191191
!ts.isTemplateLiteral(arg1.body) &&
192-
!isLitExpression(arg1.body)
192+
!isLitTemplate(arg1.body)
193193
) {
194194
throw new KnownError(
195195
'Expected function body to be a template or string'
196196
);
197197
}
198-
if (isLitExpression(arg1.body)) {
198+
if (isLitTemplate(arg1.body)) {
199199
isLitTagged = true;
200200
template = arg1.body.template;
201201
} else {
202202
template = arg1.body;
203203
}
204-
} else if (isLitExpression(arg1)) {
204+
} else if (isLitTemplate(arg1)) {
205205
isLitTagged = true;
206206
template = arg1.template;
207207
} else {
@@ -216,7 +216,7 @@ class Transformer {
216216
const templateLiteralBody = translation.contents
217217
.map((content) =>
218218
typeof content === 'string'
219-
? escapeStringLiteral(content)
219+
? escapeStringToEmbedInTemplateLiteral(content)
220220
: content.untranslatable
221221
)
222222
.join('');
@@ -228,6 +228,7 @@ class Transformer {
228228
// manipulation of placeholder contents. We should validate that the set
229229
// of translated placeholders is exactly equal to the set of original
230230
// source placeholders (order can change, but contents can't).
231+
// See https://github.com/PolymerLabs/lit-localize/issues/49
231232
template = parseStringAsTemplateLiteral(templateLiteralBody);
232233
}
233234
// TODO(aomarks) Emit a warning that a translation was missing.
@@ -339,7 +340,7 @@ class Transformer {
339340
fragments.push(expression.text);
340341
} else if (ts.isTemplateLiteral(expression)) {
341342
fragments.push(...this.recursivelyFlattenTemplate(expression, false));
342-
} else if (isLit && isLitExpression(expression)) {
343+
} else if (isLit && isLitTemplate(expression)) {
343344
fragments.push(
344345
...this.recursivelyFlattenTemplate(expression.template, true)
345346
);
@@ -377,18 +378,16 @@ class Transformer {
377378
const moduleSymbol = this.typeChecker.getSymbolAtLocation(
378379
node.moduleSpecifier
379380
);
380-
if (!moduleSymbol) {
381+
if (!moduleSymbol || !moduleSymbol.exports) {
381382
return false;
382383
}
383-
// TODO(aomarks) Is there a better way to reliably identify the lit-localize
384-
// module that doesn't require this cast? We could export a const with a
385-
// known name and then look through `exports`, but it doesn't seem good to
386-
// polute the module like that.
387-
const file = (moduleSymbol.valueDeclaration as unknown) as {
388-
identifiers: Map<string, unknown>;
389-
};
390-
for (const id of file.identifiers.keys()) {
391-
if (id === '_LIT_LOCALIZE_MSG_') {
384+
const exports = moduleSymbol.exports.values();
385+
for (const xport of exports as typeof exports & {
386+
[Symbol.iterator](): Iterator<ts.Symbol>;
387+
}) {
388+
const type = this.typeChecker.getTypeAtLocation(xport.valueDeclaration);
389+
const props = this.typeChecker.getPropertiesOfType(type);
390+
if (props.some((prop) => prop.escapedName === '_LIT_LOCALIZE_MSG_')) {
392391
return true;
393392
}
394393
}

src/program-analysis.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ function extractMsg(
107107
};
108108
}
109109

110-
if (isLitExpression(contentsArg)) {
110+
if (isLitTemplate(contentsArg)) {
111111
if (ts.isNoSubstitutionTemplateLiteral(contentsArg.template)) {
112112
// E.g. msg('foo', html`bar <b>baz</b>`)
113113
return {
@@ -186,7 +186,7 @@ function functionTemplate(
186186
if (
187187
!ts.isTemplateExpression(body) &&
188188
!ts.isNoSubstitutionTemplateLiteral(body) &&
189-
!isLitExpression(body)
189+
!isLitTemplate(body)
190190
) {
191191
return createDiagnostic(
192192
file,
@@ -195,7 +195,7 @@ function functionTemplate(
195195
`or a lit-html template, without braces`
196196
);
197197
}
198-
const template = isLitExpression(body) ? body.template : body;
198+
const template = isLitTemplate(body) ? body.template : body;
199199
const parts: Array<string | {identifier: string}> = [];
200200
if (ts.isTemplateExpression(template)) {
201201
const spans = template.templateSpans;
@@ -220,8 +220,8 @@ function functionTemplate(
220220
// A NoSubstitutionTemplateLiteral. No spans.
221221
parts.push(template.text);
222222
}
223-
const isLitTemplate = isLitExpression(body);
224-
const contents = isLitTemplate
223+
const isLit = isLitTemplate(body);
224+
const contents = isLit
225225
? replaceExpressionsAndHtmlWithPlaceholders(parts)
226226
: parts.map((part) =>
227227
typeof part === 'string'
@@ -235,7 +235,7 @@ function functionTemplate(
235235
file,
236236
descStack: descStack.map((desc) => desc.text),
237237
params,
238-
isLitTemplate,
238+
isLitTemplate: isLit,
239239
};
240240
}
241241

@@ -480,7 +480,7 @@ export function isStaticString(
480480
/**
481481
* E.g. html`foo` or html`foo${bar}`
482482
*/
483-
export function isLitExpression(
483+
export function isLitTemplate(
484484
node: ts.Node
485485
): node is ts.TaggedTemplateExpression {
486486
return (

0 commit comments

Comments
 (0)