Skip to content

Commit 79a14d7

Browse files
committed
Refactor to facilitate multiple output modes
1 parent 84de0b4 commit 79a14d7

File tree

4 files changed

+173
-142
lines changed

4 files changed

+173
-142
lines changed

src/cli.ts

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,14 @@
1010
*/
1111

1212
import * as path from 'path';
13-
import * as fs from 'fs';
1413
import * as minimist from 'minimist';
1514

1615
import {programFromTsConfig, printDiagnostics} from './typescript';
1716
import {extractMessagesFromProgram} from './program-analysis';
18-
import {generateMsgModule, generateLocaleModule} from './module-generation';
17+
import {runtimeOutput} from './outputters/runtime';
1918
import {makeFormatter} from './formatters';
2019
import {ProgramMessage, Message} from './messages';
21-
import {KnownError} from './error';
20+
import {KnownError, throwUnreachable} from './error';
2221
import {Config, readConfigFileAndWriteSchema} from './config';
2322
import {Locale} from './locales';
2423

@@ -93,39 +92,14 @@ async function runAndThrow(config: Config) {
9392
}
9493
await formatter.writeOutput(messages, translationMap);
9594

96-
// Write our "localization.ts" TypeScript module. This is the file that
97-
// implements the "msg" function for our TypeScript program.
98-
const ts = generateMsgModule(messages, config, config.output);
99-
const tsFilename = path.join(
100-
config.resolve(config.output.outputDir),
101-
'localization.ts'
102-
);
103-
try {
104-
fs.writeFileSync(tsFilename, ts);
105-
} catch (e) {
106-
throw new KnownError(
107-
`Error writing TypeScript file: ${tsFilename}\n` +
108-
`Does the parent directory exist, ` +
109-
`and do you have write permission?\n` +
110-
e.message
111-
);
112-
}
113-
114-
// For each translated locale, generate a "<locale>.ts" TypeScript module that
115-
// contains the mapping from message ID to each translated version. The
116-
// "localization.ts" file we generated earlier knows how to import and switch
117-
// between these maps.
118-
for (const locale of config.targetLocales) {
119-
const translations = translationMap.get(locale) || [];
120-
const ts = generateLocaleModule(
121-
locale,
122-
translations,
123-
messages,
124-
config.patches || {}
125-
);
126-
fs.writeFileSync(
127-
path.join(config.resolve(config.output.outputDir), `${locale}.ts`),
128-
ts
95+
if (config.output.mode === 'runtime') {
96+
runtimeOutput(messages, translationMap, config, config.output);
97+
} else {
98+
throwUnreachable(
99+
config.output.mode,
100+
`Internal error: unknown output mode ${
101+
(config.output as typeof config.output).mode
102+
}`
129103
);
130104
}
131105
}

src/config.ts

Lines changed: 1 addition & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import * as pathLib from 'path';
1515
import {Locale} from './locales';
1616
import {KnownError} from './error';
1717
import {FormatConfig} from './formatters';
18+
import {RuntimeOutputConfig} from './outputters/runtime';
1819

1920
interface ConfigFile {
2021
/**
@@ -71,105 +72,6 @@ interface ConfigFile {
7172
patches?: {[locale: string]: {[messageId: string]: Patch[]}};
7273
}
7374

74-
/**
75-
* Configuration specific to the `runtime` output mode.
76-
*/
77-
export interface RuntimeOutputConfig {
78-
mode: 'runtime';
79-
80-
/**
81-
* Output directory for generated TypeScript modules. After running
82-
* lit-localize, this directory will contain:
83-
*
84-
* 1. localization.ts -- A TypeScript module that exports the `msg` function,
85-
* along with other utilities.
86-
*
87-
* 2. <locale>.ts -- For each `targetLocale`, a TypeScript module that exports
88-
* the translations in that locale keyed by message ID. These modules are
89-
* used automatically by localization.ts and should not typically be
90-
* imported directly by user code.
91-
*/
92-
outputDir: string;
93-
94-
/**
95-
* The initial locale, if no other explicit locale selection has been made.
96-
* Defaults to the value of `sourceLocale`.
97-
*
98-
* @TJS-type string
99-
*/
100-
defaultLocale?: Locale;
101-
102-
/**
103-
* If true, export a `setLocale(locale: Locale)` function in the generated
104-
* `<outputDir>/localization.ts` module. Defaults to false.
105-
*
106-
* Note that calling this function will set the locale for subsequent calls to
107-
* `msg`, but will not automatically re-render existing templates.
108-
*/
109-
exportSetLocaleFunction?: boolean;
110-
111-
/**
112-
* Automatically set the locale based on the URL at application startup.
113-
*/
114-
setLocaleFromUrl?: {
115-
/**
116-
* Set locale based on matching a regular expression against the URL.
117-
*
118-
* The regexp will be matched against `window.location.href`, and the first
119-
* capturing group will be used as the locale. If no match is found, or if
120-
* the capturing group does not contain a valid locale code, then
121-
* `defaultLocale` is used.
122-
*
123-
* Optionally use the special string `:LOCALE:` to substitute a capturing
124-
* group into the regexp that will only match the currently configured
125-
* locale codes (`sourceLocale` and `targetLocales`). For example, if
126-
* sourceLocale=en and targetLocales=es,zh_CN, then the regexp
127-
* "^https?://:LOCALE:\\." becomes "^https?://(en|es|zh_CN)\\.".
128-
*
129-
* Tips: Remember to double-escape literal backslashes (once for JSON, once
130-
* for the regexp), and note that you can use `(?:foo)` to create a
131-
* non-capturing group.
132-
*
133-
* It is an error to set both `regexp` and `param`.
134-
*
135-
* Examples:
136-
*
137-
* 1. "^https?://[^/]+/:LOCALE:(?:$|[/?#])"
138-
*
139-
* Set locale from the first path component.
140-
*
141-
* E.g. https://www.example.com/es/foo
142-
* ^^
143-
*
144-
* 2. "^https?://:LOCALE:\\."
145-
*
146-
* Set locale from the first subdomain.
147-
*
148-
* E.g. https://es.example.com/foo
149-
* ^^
150-
*/
151-
regexp?: string;
152-
153-
/**
154-
* Set locale based on the value of a URL query parameter.
155-
*
156-
* Finds the first matching query parameter from `window.location.search`.
157-
* If no such URL query parameter is set, or if it is not a valid locale
158-
* code, then `defaultLocale` is used.
159-
*
160-
* It is an error to set both `regexp` and `param`.
161-
*
162-
* Examples:
163-
*
164-
* 1. "lang"
165-
*
166-
* https://example.com?foo&lang=es&bar
167-
* ^^
168-
*/
169-
param?: string;
170-
};
171-
}
172-
17375
/**
17476
* A validated config file, plus any extra properties not present in the file
17577
* itself.

src/error.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,12 @@
1414
* a concise error instead of a stacktrace.
1515
*/
1616
export class KnownError extends Error {}
17+
18+
/**
19+
* Fail type checking if the first argument is not `never`, and throw a
20+
* `KnownError` with the given message if this function is somehow called
21+
* anyway.
22+
*/
23+
export function throwUnreachable(_x: never, msg: string) {
24+
throw new KnownError(msg);
25+
}

src/module-generation.ts renamed to src/outputters/runtime.ts

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

12-
import {Message, ProgramMessage, Placeholder} from './messages';
13-
import {applyPatches, Patches} from './patches';
14-
import {Locale} from './locales';
15-
import {Config, RuntimeOutputConfig} from './config';
16-
import {KnownError} from './error';
12+
import {Message, ProgramMessage, Placeholder} from '../messages';
13+
import {applyPatches, Patches} from '../patches';
14+
import {Locale} from '../locales';
15+
import {Config} from '../config';
16+
import {KnownError} from '../error';
17+
import * as fs from 'fs';
18+
import * as pathLib from 'path';
19+
20+
/**
21+
* Configuration specific to the `runtime` output mode.
22+
*/
23+
export interface RuntimeOutputConfig {
24+
mode: 'runtime';
25+
26+
/**
27+
* Output directory for generated TypeScript modules. After running
28+
* lit-localize, this directory will contain:
29+
*
30+
* 1. localization.ts -- A TypeScript module that exports the `msg` function,
31+
* along with other utilities.
32+
*
33+
* 2. <locale>.ts -- For each `targetLocale`, a TypeScript module that exports
34+
* the translations in that locale keyed by message ID. These modules are
35+
* used automatically by localization.ts and should not typically be
36+
* imported directly by user code.
37+
*/
38+
outputDir: string;
39+
40+
/**
41+
* The initial locale, if no other explicit locale selection has been made.
42+
* Defaults to the value of `sourceLocale`.
43+
*
44+
* @TJS-type string
45+
*/
46+
defaultLocale?: Locale;
47+
48+
/**
49+
* If true, export a `setLocale(locale: Locale)` function in the generated
50+
* `<outputDir>/localization.ts` module. Defaults to false.
51+
*
52+
* Note that calling this function will set the locale for subsequent calls to
53+
* `msg`, but will not automatically re-render existing templates.
54+
*/
55+
exportSetLocaleFunction?: boolean;
56+
57+
/**
58+
* Automatically set the locale based on the URL at application startup.
59+
*/
60+
setLocaleFromUrl?: {
61+
/**
62+
* Set locale based on matching a regular expression against the URL.
63+
*
64+
* The regexp will be matched against `window.location.href`, and the first
65+
* capturing group will be used as the locale. If no match is found, or if
66+
* the capturing group does not contain a valid locale code, then
67+
* `defaultLocale` is used.
68+
*
69+
* Optionally use the special string `:LOCALE:` to substitute a capturing
70+
* group into the regexp that will only match the currently configured
71+
* locale codes (`sourceLocale` and `targetLocales`). For example, if
72+
* sourceLocale=en and targetLocales=es,zh_CN, then the regexp
73+
* "^https?://:LOCALE:\\." becomes "^https?://(en|es|zh_CN)\\.".
74+
*
75+
* Tips: Remember to double-escape literal backslashes (once for JSON, once
76+
* for the regexp), and note that you can use `(?:foo)` to create a
77+
* non-capturing group.
78+
*
79+
* It is an error to set both `regexp` and `param`.
80+
*
81+
* Examples:
82+
*
83+
* 1. "^https?://[^/]+/:LOCALE:(?:$|[/?#])"
84+
*
85+
* Set locale from the first path component.
86+
*
87+
* E.g. https://www.example.com/es/foo
88+
* ^^
89+
*
90+
* 2. "^https?://:LOCALE:\\."
91+
*
92+
* Set locale from the first subdomain.
93+
*
94+
* E.g. https://es.example.com/foo
95+
* ^^
96+
*/
97+
regexp?: string;
98+
99+
/**
100+
* Set locale based on the value of a URL query parameter.
101+
*
102+
* Finds the first matching query parameter from `window.location.search`.
103+
* If no such URL query parameter is set, or if it is not a valid locale
104+
* code, then `defaultLocale` is used.
105+
*
106+
* It is an error to set both `regexp` and `param`.
107+
*
108+
* Examples:
109+
*
110+
* 1. "lang"
111+
*
112+
* https://example.com?foo&lang=es&bar
113+
* ^^
114+
*/
115+
param?: string;
116+
};
117+
}
118+
119+
/**
120+
* Write output for the `runtime` output mode.
121+
*/
122+
export function runtimeOutput(
123+
messages: ProgramMessage[],
124+
translationMap: Map<Locale, Message[]>,
125+
config: Config,
126+
runtimeConfig: RuntimeOutputConfig
127+
) {
128+
// Write our "localization.ts" TypeScript module. This is the file that
129+
// implements the "msg" function for our TypeScript program.
130+
const ts = generateMsgModule(messages, config, runtimeConfig);
131+
const tsFilename = pathLib.join(
132+
config.resolve(runtimeConfig.outputDir),
133+
'localization.ts'
134+
);
135+
try {
136+
fs.writeFileSync(tsFilename, ts);
137+
} catch (e) {
138+
throw new KnownError(
139+
`Error writing TypeScript file: ${tsFilename}\n` +
140+
`Does the parent directory exist, ` +
141+
`and do you have write permission?\n` +
142+
e.message
143+
);
144+
}
145+
// For each translated locale, generate a "<locale>.ts" TypeScript module that
146+
// contains the mapping from message ID to each translated version. The
147+
// "localization.ts" file we generated earlier knows how to import and switch
148+
// between these maps.
149+
for (const locale of config.targetLocales) {
150+
const translations = translationMap.get(locale) || [];
151+
const ts = generateLocaleModule(
152+
locale,
153+
translations,
154+
messages,
155+
config.patches || {}
156+
);
157+
fs.writeFileSync(
158+
pathLib.join(config.resolve(runtimeConfig.outputDir), `${locale}.ts`),
159+
ts
160+
);
161+
}
162+
}
17163

18164
/**
19165
* Generate a TypeScript module which exports:
@@ -30,7 +176,7 @@ import {KnownError} from './error';
30176
* generator can be referenced in `msg` calls, and only our supported locales
31177
* can be switched to by `setLocale`.
32178
*/
33-
export function generateMsgModule(
179+
function generateMsgModule(
34180
msgs: Message[],
35181
config: Config,
36182
runtime: RuntimeOutputConfig
@@ -215,7 +361,7 @@ function genLocaleInitialization(
215361
* Generate a "<locale>.ts" TypeScript module from the given bundle of
216362
* translated messages.
217363
*/
218-
export function generateLocaleModule(
364+
function generateLocaleModule(
219365
locale: Locale,
220366
translations: Message[],
221367
canonMsgs: ProgramMessage[],

0 commit comments

Comments
 (0)