Skip to content

Commit 68c29ab

Browse files
feat: failOnMissing & failOnCompileError for loaders + compiler (#2198)
Co-authored-by: Kevin Hahn <kevin_hahn@sil.org>
1 parent 583dd05 commit 68c29ab

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+842
-195
lines changed

packages/cli/src/api/__snapshots__/catalog.test.ts.snap

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,20 @@
22

33
exports[`Catalog POT Flow Should get translations from template if locale file not presented 1`] = `
44
{
5-
2ZeN02: Test String,
6-
mY42CM: Hello World,
5+
messages: {
6+
2ZeN02: Test String,
7+
mY42CM: Hello World,
8+
},
9+
missing: [
10+
{
11+
id: mY42CM,
12+
source: Hello World,
13+
},
14+
{
15+
id: 2ZeN02,
16+
source: Test String,
17+
},
18+
],
719
}
820
`;
921

packages/cli/src/api/catalog.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -638,11 +638,11 @@ describe("writeCompiled", () => {
638638
])(
639639
"Should save namespace $namespace in $extension extension",
640640
async ({ namespace, extension }) => {
641-
const compiledCatalog = createCompiledCatalog("en", {}, { namespace })
641+
const { source } = createCompiledCatalog("en", {}, { namespace })
642642
// Test that the file extension of the compiled catalog is `.mjs`
643-
expect(
644-
await catalog.writeCompiled("en", compiledCatalog, namespace)
645-
).toMatch(extension)
643+
expect(await catalog.writeCompiled("en", source, namespace)).toMatch(
644+
extension
645+
)
646646
}
647647
)
648648
})

packages/cli/src/api/catalog.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { CompiledCatalogNamespace } from "./compile"
1212
import {
1313
getTranslationsForCatalog,
1414
GetTranslationsOptions,
15+
TranslationMissingEvent,
1516
} from "./catalog/getTranslationsForCatalog"
1617
import { mergeCatalog } from "./catalog/mergeCatalog"
1718
import { extractFromFiles } from "./catalog/extractFromFiles"
@@ -177,8 +178,23 @@ export class Catalog {
177178
)
178179
}
179180

180-
async getTranslations(locale: string, options: GetTranslationsOptions) {
181-
return await getTranslationsForCatalog(this, locale, options)
181+
async getTranslations(
182+
locale: string,
183+
options: Omit<GetTranslationsOptions, "onMissing">
184+
) {
185+
const missing: TranslationMissingEvent[] = []
186+
187+
const messages = await getTranslationsForCatalog(this, locale, {
188+
...options,
189+
onMissing: (event) => {
190+
missing.push(event)
191+
},
192+
})
193+
194+
return {
195+
missing,
196+
messages,
197+
}
182198
}
183199

184200
async write(

packages/cli/src/api/compile.test.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ describe("createCompiledCatalog", () => {
190190
{
191191
namespace,
192192
}
193-
)
193+
).source
194194

195195
it("should compile with json", () => {
196196
expect(getCompiledCatalog("json")).toMatchSnapshot()
@@ -229,7 +229,7 @@ describe("createCompiledCatalog", () => {
229229
{
230230
strict,
231231
}
232-
)
232+
).source
233233

234234
it("should return message key as a fallback translation", () => {
235235
expect(getCompiledCatalog(false)).toMatchSnapshot()
@@ -250,7 +250,7 @@ describe("createCompiledCatalog", () => {
250250
{
251251
pseudoLocale,
252252
}
253-
)
253+
).source
254254

255255
it("should return catalog with pseudolocalized messages", () => {
256256
expect(getCompiledCatalog("ps")).toMatchSnapshot()
@@ -269,7 +269,7 @@ describe("createCompiledCatalog", () => {
269269
Hello: "Alohà",
270270
},
271271
opts
272-
)
272+
).source
273273

274274
it("by default should return catalog without ASCII chars", () => {
275275
expect(getCompiledCatalog()).toMatchSnapshot()
@@ -287,4 +287,30 @@ describe("createCompiledCatalog", () => {
287287
).toMatchSnapshot()
288288
})
289289
})
290+
291+
it("should return list of compile errors", () => {
292+
const res = createCompiledCatalog(
293+
"ru",
294+
{
295+
Hello: "{plural, }",
296+
Second: "{bla, }",
297+
},
298+
{}
299+
)
300+
301+
expect(res.errors).toHaveLength(2)
302+
expect(res.errors[0]).toMatchObject({
303+
id: "Hello",
304+
source: "{plural, }",
305+
})
306+
307+
expect(res.errors[0].error.message).toContain("invalid syntax at line")
308+
309+
expect(res.errors[1]).toMatchObject({
310+
id: "Second",
311+
source: "{bla, }",
312+
})
313+
314+
expect(res.errors[1].error.message).toContain("invalid syntax at line")
315+
})
290316
})

packages/cli/src/api/compile.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import * as t from "@babel/types"
22
import generate, { GeneratorOptions } from "@babel/generator"
33
import {
4-
compileMessage,
54
CompiledMessage,
5+
compileMessageOrThrow,
66
} from "@lingui/message-utils/compileMessage"
77
import pseudoLocalize from "./pseudoLocalize"
88

@@ -19,11 +19,26 @@ export type CreateCompileCatalogOptions = {
1919
compilerBabelOptions?: GeneratorOptions
2020
}
2121

22+
export type MessageCompilationError = {
23+
/**
24+
* ID of the message in the Catalog
25+
*/
26+
id: string
27+
/**
28+
* Message itself
29+
*/
30+
source: string
31+
/**
32+
* Error associated with this message
33+
*/
34+
error: Error
35+
}
36+
2237
export function createCompiledCatalog(
2338
locale: string,
2439
messages: CompiledCatalogType,
2540
options: CreateCompileCatalogOptions
26-
): string {
41+
): { source: string; errors: MessageCompilationError[] } {
2742
const {
2843
strict = false,
2944
namespace = "cjs",
@@ -32,18 +47,29 @@ export function createCompiledCatalog(
3247
} = options
3348
const shouldPseudolocalize = locale === pseudoLocale
3449

50+
const errors: MessageCompilationError[] = []
51+
3552
const compiledMessages = Object.keys(messages).reduce<{
3653
[msgId: string]: CompiledMessage
3754
}>((obj, key: string) => {
3855
// Don't use `key` as a fallback translation in strict mode.
3956
const translation = (messages[key] || (!strict ? key : "")) as string
4057

41-
obj[key] = compile(translation, shouldPseudolocalize)
58+
try {
59+
obj[key] = compile(translation, shouldPseudolocalize)
60+
} catch (e) {
61+
errors.push({
62+
id: key,
63+
source: translation,
64+
error: e as Error,
65+
})
66+
}
67+
4268
return obj
4369
}, {})
4470

4571
if (namespace === "json") {
46-
return JSON.stringify({ messages: compiledMessages })
72+
return { source: JSON.stringify({ messages: compiledMessages }), errors }
4773
}
4874

4975
const ast = buildExportStatement(
@@ -63,7 +89,7 @@ export function createCompiledCatalog(
6389
...compilerBabelOptions,
6490
}).code
6591

66-
return "/*eslint-disable*/" + code
92+
return { source: "/*eslint-disable*/" + code, errors }
6793
}
6894

6995
function buildExportStatement(
@@ -138,7 +164,7 @@ export function compile(
138164
message: string,
139165
shouldPseudolocalize: boolean = false
140166
) {
141-
return compileMessage(message, (value) =>
167+
return compileMessageOrThrow(message, (value) =>
142168
shouldPseudolocalize ? pseudoLocalize(value) : value
143169
)
144170
}

packages/cli/src/api/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ export {
88
extractFromFileWithBabel,
99
} from "./extractors/babel"
1010
export { getCatalogDependentFiles } from "./catalog/getCatalogDependentFiles"
11+
export {
12+
createMissingErrorMessage,
13+
createCompilationErrorMessage,
14+
} from "./messages"
1115
export * from "./types"

packages/cli/src/api/messages.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {
2+
createCompilationErrorMessage,
3+
createMissingErrorMessage,
4+
} from "./messages"
5+
6+
describe("createMissingErrorMessage", () => {
7+
it("should print correct missing message", async () => {
8+
const message = createMissingErrorMessage(
9+
"en",
10+
[
11+
{
12+
id: "1",
13+
source: "Hello",
14+
},
15+
{
16+
id: "World",
17+
source: "World",
18+
},
19+
],
20+
"bla bla"
21+
)
22+
23+
expect(message).toMatchInlineSnapshot(`
24+
Failed to compile catalog for locale en!
25+
26+
Missing 2 translation(s):
27+
28+
1: Hello
29+
World: World
30+
31+
`)
32+
})
33+
})
34+
35+
describe("createCompilationErrorMessage", () => {
36+
const errors = [
37+
{
38+
error: new Error("Syntax error"),
39+
source: "Hello",
40+
id: "1",
41+
},
42+
{
43+
error: new Error("Syntax error"),
44+
source: "World",
45+
id: "World",
46+
},
47+
]
48+
49+
it("should print correct compile error message", () => {
50+
const message = createCompilationErrorMessage("en", errors)
51+
52+
expect(message).toMatchInlineSnapshot(`
53+
Failed to compile catalog for locale en!
54+
55+
Compilation error for 2 translation(s):
56+
57+
1: Hello
58+
Reason: Syntax error
59+
60+
World: World
61+
Reason: Syntax error
62+
63+
64+
`)
65+
})
66+
})

packages/cli/src/api/messages.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { TranslationMissingEvent } from "./catalog/getTranslationsForCatalog"
2+
import chalk from "chalk"
3+
import { MessageCompilationError } from "./compile"
4+
5+
export function createMissingErrorMessage(
6+
locale: string,
7+
missingMessages: TranslationMissingEvent[],
8+
configurationMsg: string
9+
) {
10+
let message = `Failed to compile catalog for locale ${chalk.bold(locale)}!
11+
12+
Missing ${missingMessages.length} translation(s):
13+
\n`
14+
15+
missingMessages.forEach((missing) => {
16+
const source =
17+
missing.source || missing.source === missing.id
18+
? `: ${missing.source}`
19+
: ""
20+
21+
message += `${missing.id}${source}\n`
22+
})
23+
24+
return message
25+
}
26+
27+
export function createCompilationErrorMessage(
28+
locale: string,
29+
errors: MessageCompilationError[]
30+
) {
31+
let message = `Failed to compile catalog for locale ${chalk.bold(locale)}!
32+
33+
Compilation error for ${errors.length} translation(s):
34+
\n`
35+
36+
errors.forEach((error) => {
37+
const source =
38+
error.source || error.source === error.id ? `: ${error.source}` : ""
39+
40+
message += `${error.id}${source}\nReason: ${error.error.message}\n\n`
41+
})
42+
43+
return message
44+
}

0 commit comments

Comments
 (0)