Skip to content

Commit 15eea98

Browse files
authored
Merge pull request #16 from PolymerLabs/variable
Add support for variables
2 parents cdea4bd + 6c4dcd9 commit 15eea98

File tree

13 files changed

+528
-150
lines changed

13 files changed

+528
-150
lines changed

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,19 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
<!-- ## Unreleased -->
8+
## Unreleased
9+
10+
- Add support for variables:
11+
12+
```typescript
13+
msg(
14+
'hello',
15+
(url: string, name: string) =>
16+
html`Hello ${name}, click <a href="${url}">here</a>!`,
17+
'World',
18+
'https://www.example.com/'
19+
);
20+
```
921

1022
## 0.1.1
1123

src/interfaces.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ export interface Message {
2828
}
2929

3030
/**
31-
* Extension to "Message" with additional properties that are needed when
32-
* extracting messages from the TypeScript program, but are not needed when
33-
* reading translated files.
31+
* Extension to "Message" with additional properties that are known only when
32+
* extracting from the TypeScript program, but not when reading translated
33+
* files.
3434
*/
3535
export interface ProgramMessage extends Message {
3636
/**
@@ -50,6 +50,24 @@ export interface ProgramMessage extends Message {
5050
* extracted. Used for generating "desc" attributes in our XLB file.
5151
*/
5252
descStack: string[];
53+
54+
/**
55+
* If this message was written as a function, the names of the parameters that
56+
* the function takes.
57+
*
58+
* E.g. given:
59+
* msg('foo', (bar: string, baz: number) => `foo ${bar} ${baz}`, 'a', 4)
60+
*
61+
* Then params is:
62+
* [ 'bar', 'baz' ]
63+
*/
64+
params?: string[];
65+
66+
/**
67+
* True if this message was tagged as a lit-html template, or was a function
68+
* that returned a lit-html template.
69+
*/
70+
isLitTemplate: boolean;
5371
}
5472

5573
/**

src/module-generation.ts

Lines changed: 69 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* rights grant found at http://polymer.github.io/PATENTS.txt
1010
*/
1111

12-
import {Bundle, Message, Placeholder} from './interfaces';
12+
import {Bundle, Message, ProgramMessage, Placeholder} from './interfaces';
1313
import {applyPatches, Patches} from './patches';
1414
import {Locale} from './locales';
1515

@@ -50,21 +50,14 @@ export function generateMsgModule(
5050
)}Messages} from './${locale}.js';`
5151
)
5252
.join('\n');
53-
const localeSwitchCases = targetLocales.map((locale) => {
54-
return `case '${locale}':
55-
value = ${locale.replace('-', '')}Messages[name];
56-
break;`;
57-
});
5853
return `
5954
// Do not modify this file by hand!
6055
// Re-generate this file by running lit-localize
6156
6257
import {TemplateResult} from 'lit-html';
6358
${localeImports}
6459
65-
export const getLocale = () => {
66-
return locale;
67-
};
60+
/* eslint-disable @typescript-eslint/no-explicit-any */
6861
6962
export const supportedLocales = [${localesArray}] as const;
7063
@@ -91,23 +84,52 @@ export function generateMsgModule(
9184
9285
const locale = getLocaleFromUrl();
9386
94-
export const msg = (name: MessageName, defaultValue: string|TemplateResult): string|TemplateResult => {
95-
let value;
87+
export const getLocale = () => {
88+
return locale;
89+
};
90+
91+
export function msg(name: MessageName, str: string): string;
92+
93+
export function msg(name: MessageName, tmpl: TemplateResult): TemplateResult;
94+
95+
export function msg<F extends (...args: any) => string>(
96+
name: MessageName,
97+
fn: F,
98+
...params: Parameters<F>
99+
): string;
100+
101+
export function msg<F extends (...args: any) => TemplateResult>(
102+
name: MessageName,
103+
fn: F,
104+
...params: Parameters<F>
105+
): TemplateResult;
106+
107+
export function msg(
108+
name: MessageName,
109+
source: string|TemplateResult|(() => string|TemplateResult),
110+
...params: unknown[]): string|TemplateResult {
111+
let resolved;
96112
switch (locale) {
97113
case defaultLocale:
98-
return defaultValue;
99-
${localeSwitchCases}
114+
resolved = source;
115+
break;
116+
${targetLocales.map(
117+
(locale) => `
118+
case '${locale}':
119+
resolved = ${locale.replace('-', '')}Messages[name];
120+
break;`
121+
)}
100122
default:
101-
// TODO unreachable
102123
console.warn(\`\${locale} is not a supported locale\`);
103-
return defaultValue;
104124
}
105-
if (value !== undefined) {
106-
return value;
125+
if (!resolved) {
126+
console.warn(\`Could not find \${locale} string for \${name}\`);
127+
resolved = source;
107128
}
108-
console.warn(\`Could not find \${locale} string for \${name}\`);
109-
return defaultValue;
110-
};
129+
return typeof resolved === 'function'
130+
? (resolved as any)(...params)
131+
: resolved;
132+
}
111133
112134
type MessageName = ${messageNamesUnion};
113135
`;
@@ -119,15 +141,15 @@ export function generateMsgModule(
119141
*/
120142
export function generateLocaleModule(
121143
{locale, messages}: Bundle,
122-
canonMsgs: Message[],
144+
canonMsgs: ProgramMessage[],
123145
patches: Patches
124146
): string {
125147
messages = copyMessagesSortedByName(messages);
126148
// The unique set of message names in the canonical messages we extracted from
127149
// the TypeScript program.
128-
const canonMsgNames = new Set<string>();
150+
const canonMsgsByName = new Map<string, ProgramMessage>();
129151
for (const canon of canonMsgs) {
130-
canonMsgNames.add(canon.name);
152+
canonMsgsByName.set(canon.name, canon);
131153
}
132154

133155
// The unique set of message names we found in this XLB translations file.
@@ -138,15 +160,16 @@ export function generateLocaleModule(
138160

139161
const entries = [];
140162
for (const msg of messages) {
141-
if (!canonMsgNames.has(msg.name)) {
163+
const canon = canonMsgsByName.get(msg.name);
164+
if (canon === undefined) {
142165
console.warn(
143166
`${locale} message ${msg.name} does not exist in canonical messages, skipping`
144167
);
145168
continue;
146169
}
147170
translatedMsgNames.add(msg.name);
148-
const {msgStr, usesLit} = makeMessageString(msg.contents);
149-
if (usesLit) {
171+
const msgStr = makeMessageString(msg.contents, canon);
172+
if (canon.isLitTemplate) {
150173
importLit = true;
151174
}
152175
const patchedMsgStr = applyPatches(patches, locale, msg.name, msgStr);
@@ -159,15 +182,19 @@ export function generateLocaleModule(
159182
console.warn(
160183
`${locale} message ${msg.name} is missing, using canonical text as fallback`
161184
);
162-
const {msgStr} = makeMessageString(msg.contents);
185+
const msgStr = makeMessageString(msg.contents, msg);
163186
entries.push(`${msg.name}: ${msgStr},`);
164187
}
165188
return `
166189
// Do not modify this file by hand!
167190
// Re-generate this file by running lit-localize
191+
168192
${importLit ? "import {html} from 'lit-html';" : ''}
169193
170194
/* eslint-disable no-irregular-whitespace */
195+
/* eslint-disable @typescript-eslint/camelcase */
196+
/* eslint-disable @typescript-eslint/no-explicit-any */
197+
171198
export const messages = {
172199
${entries.join('\n')}
173200
};
@@ -188,23 +215,28 @@ function copyMessagesSortedByName(messages: Message[]): Message[] {
188215
* possibly using lit-html if there is embedded HTML.
189216
*/
190217
function makeMessageString(
191-
contents: Array<string | Placeholder>
192-
): {msgStr: string; usesLit: boolean} {
193-
let hasPlaceholders = false;
218+
contents: Array<string | Placeholder>,
219+
canon: ProgramMessage
220+
): string {
194221
const fragments = [];
195222
for (const content of contents) {
196223
if (typeof content === 'string') {
197224
fragments.push(escapeStringLiteral(content));
198225
} else {
199-
fragments.push(escapeStringLiteral(content.untranslatable));
200-
hasPlaceholders = true;
226+
fragments.push(content.untranslatable);
201227
}
202228
}
203-
// We use <ph> placeholders to safely pass embedded HTML markup through
204-
// translation. If we encounter a placeholder, then this translated string
205-
// needs to use the lit-html "html" function.
206-
const msgStr = `${hasPlaceholders ? 'html' : ''}\`${fragments.join('')}\``;
207-
return {msgStr, usesLit: hasPlaceholders};
229+
// We use <ph> placeholders to safely pass embedded HTML markup and
230+
// template expressions through translation.
231+
const tag = canon.isLitTemplate ? 'html' : '';
232+
const msgStr = `${tag}\`${fragments.join('')}\``;
233+
if (canon.params !== undefined && canon.params.length > 0) {
234+
return `(${canon.params
235+
.map((param) => `${param}: any`)
236+
.join(', ')}) => ${msgStr}`;
237+
} else {
238+
return msgStr;
239+
}
208240
}
209241

210242
/**

0 commit comments

Comments
 (0)