Skip to content

Commit 8ee42cb

Browse files
authored
feat: msgctxt support (#1094)
1 parent 3b9df3e commit 8ee42cb

22 files changed

Lines changed: 355 additions & 47 deletions

File tree

packages/babel-plugin-extract-messages/src/index.ts

Lines changed: 72 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,83 @@ const VISITED = Symbol("I18nVisited")
1717
function addMessage(
1818
path,
1919
messages,
20-
{ id, message: newDefault, origin, comment, ...props }
20+
{ id, message: newDefault, origin, comment, context, ...props }
2121
) {
2222
// prevent from adding undefined msgid
2323
if (id === undefined) return
2424

25-
if (messages.has(id)) {
26-
const message = messages.get(id)
25+
const extractedComments = comment ? [comment] : []
26+
27+
if (context) {
28+
if (messages.has(context)) {
29+
const existingContext = messages.get(context)
30+
if (existingContext.has(id)) {
31+
const message = messages.get(id)
32+
// only set/check default language when it's defined.
33+
if (message.message && newDefault && message.message !== newDefault) {
34+
throw path.buildCodeFrameError(
35+
"Different defaults for the same message ID."
36+
)
37+
}
38+
39+
if (newDefault) {
40+
message.message = newDefault
41+
}
2742

28-
// only set/check default language when it's defined.
29-
if (message.message && newDefault && message.message !== newDefault) {
30-
throw path.buildCodeFrameError(
31-
"Different defaults for the same message ID."
32-
)
43+
;[].push.apply(message.origin, origin)
44+
45+
if (comment) {
46+
;[].push.apply(message.extractedComments, [comment])
47+
}
48+
} else {
49+
existingContext.set(id, { ...props, message: newDefault, origin, extractedComments })
50+
messages.set(context, existingContext)
51+
}
52+
} else {
53+
const newContext = new Map();
54+
newContext.set(id, { ...props, message: newDefault, origin, extractedComments })
55+
messages.set(context, newContext)
3356
}
34-
35-
if (newDefault) {
36-
message.message = newDefault
57+
} else {
58+
if (messages.has(id)) {
59+
const message = messages.get(id)
60+
61+
// only set/check default language when it's defined.
62+
if (message.message && newDefault && message.message !== newDefault) {
63+
throw path.buildCodeFrameError(
64+
"Different defaults for the same message ID."
65+
)
66+
}
67+
68+
if (newDefault) {
69+
message.message = newDefault
70+
}
71+
72+
;[].push.apply(message.origin, origin)
73+
if (comment) {
74+
;[].push.apply(message.extractedComments, [comment])
75+
}
76+
} else {
77+
messages.set(id, { ...props, message: newDefault, origin, extractedComments })
3778
}
79+
}
80+
}
3881

39-
;[].push.apply(message.origin, origin)
40-
if (comment) {
41-
;[].push.apply(message.extractedComments, [comment])
42-
}
43-
} else {
44-
const extractedComments = comment ? [comment] : []
45-
messages.set(id, { ...props, message: newDefault, origin, extractedComments })
82+
/**
83+
* An ES6 Map type is not possible to encode with JSON.stringify,
84+
* so we can instead use a replacer function as an argument to
85+
* tell the JSON parser how to serialize / deserialize the Maps
86+
* it encounters.
87+
*/
88+
function mapReplacer(key, value) {
89+
if (value instanceof Map) {
90+
const object = {}
91+
value.forEach((v, k) => {
92+
return object[k] = v;
93+
});
94+
return object;
4695
}
96+
return value;
4797
}
4898

4999
export default function ({ types: t }) {
@@ -117,7 +167,7 @@ export default function ({ types: t }) {
117167

118168
const props = attrs.reduce((acc, item) => {
119169
const key = item.name.name
120-
if (key === "id" || key === "message" || key === "comment") {
170+
if (key === "id" || key === "message" || key === "comment" || key === "context") {
121171
if (item.value.value) {
122172
acc[key] = item.value.value
123173
} else if (
@@ -169,7 +219,7 @@ export default function ({ types: t }) {
169219
return
170220
}
171221

172-
const copyOptions = ["message", "comment"]
222+
const copyOptions = ["message", "comment", "context"]
173223

174224
if (t.isObjectExpression(path.node.arguments[2])) {
175225
path.node.arguments[2].properties.forEach((property) => {
@@ -228,7 +278,7 @@ export default function ({ types: t }) {
228278
visited.add(path.node)
229279

230280
const props = {}
231-
const copyProps = ["id", "message", "comment"]
281+
const copyProps = ["id", "message", "comment", "context"]
232282
path.node.properties
233283
.filter(({ key }) => copyProps.indexOf(key.name) !== -1)
234284
.forEach(({ key, value }, i) => {
@@ -298,7 +348,7 @@ export default function ({ types: t }) {
298348
catalog[key] = value
299349
})
300350

301-
fs.writeFileSync(catalogFilename, JSON.stringify(catalog, null, 2))
351+
fs.writeFileSync(catalogFilename, JSON.stringify(catalog, mapReplacer, 2))
302352
},
303353
}
304354
}

packages/babel-plugin-extract-messages/test/__snapshots__/index.ts.snap

Lines changed: 120 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
exports[`@lingui/babel-plugin-extract-messages should extract Plural messages from JSX files when there's no Trans tag (integration) 1`] = `
44
Object {
5+
Some context: Object {
6+
{count, plural, one {# book} other {# books}}: Object {
7+
extractedComments: Array [],
8+
origin: Array [
9+
Array [
10+
jsx-without-trans.js,
11+
4,
12+
],
13+
],
14+
},
15+
},
516
{count, plural, one {# book} other {# books}}: Object {
617
extractedComments: Array [],
718
origin: Array [
@@ -25,6 +36,37 @@ Object {
2536
],
2637
],
2738
},
39+
Context1: Object {
40+
Some ID: Object {
41+
extractedComments: Array [],
42+
origin: Array [
43+
Array [
44+
js-with-macros.js,
45+
34,
46+
],
47+
],
48+
},
49+
Some other ID: Object {
50+
extractedComments: Array [],
51+
origin: Array [
52+
Array [
53+
js-with-macros.js,
54+
39,
55+
],
56+
],
57+
},
58+
},
59+
Context2: Object {
60+
Some ID: Object {
61+
extractedComments: Array [],
62+
origin: Array [
63+
Array [
64+
js-with-macros.js,
65+
44,
66+
],
67+
],
68+
},
69+
},
2870
Description: Object {
2971
extractedComments: Array [
3072
description,
@@ -83,6 +125,17 @@ Object {
83125

84126
exports[`@lingui/babel-plugin-extract-messages should extract all messages from JS files 1`] = `
85127
Object {
128+
Context1: Object {
129+
Some id: Object {
130+
extractedComments: Array [],
131+
origin: Array [
132+
Array [
133+
js-without-macros.js,
134+
11,
135+
],
136+
],
137+
},
138+
},
86139
Description: Object {
87140
extractedComments: Array [
88141
description,
@@ -127,6 +180,37 @@ Object {
127180

128181
exports[`@lingui/babel-plugin-extract-messages should extract all messages from JSX files (macros) 1`] = `
129182
Object {
183+
Context1: Object {
184+
Some message: Object {
185+
extractedComments: Array [],
186+
origin: Array [
187+
Array [
188+
jsx-with-macros.js,
189+
4,
190+
],
191+
],
192+
},
193+
Some other message: Object {
194+
extractedComments: Array [],
195+
origin: Array [
196+
Array [
197+
jsx-with-macros.js,
198+
5,
199+
],
200+
],
201+
},
202+
},
203+
Context2: Object {
204+
Some message: Object {
205+
extractedComments: Array [],
206+
origin: Array [
207+
Array [
208+
jsx-with-macros.js,
209+
6,
210+
],
211+
],
212+
},
213+
},
130214
Hi, my name is {name}: Object {
131215
extractedComments: Array [],
132216
origin: Array [
@@ -141,7 +225,7 @@ Object {
141225
origin: Array [
142226
Array [
143227
jsx-with-macros.js,
144-
4,
228+
7,
145229
],
146230
],
147231
},
@@ -150,7 +234,7 @@ Object {
150234
origin: Array [
151235
Array [
152236
jsx-with-macros.js,
153-
6,
237+
9,
154238
],
155239
],
156240
},
@@ -159,12 +243,43 @@ Object {
159243

160244
exports[`@lingui/babel-plugin-extract-messages should extract all messages from JSX files 1`] = `
161245
Object {
246+
Context1: Object {
247+
msg.context: Object {
248+
extractedComments: Array [],
249+
origin: Array [
250+
Array [
251+
jsx-without-macros.js,
252+
7,
253+
],
254+
],
255+
},
256+
msg.notcontext: Object {
257+
extractedComments: Array [],
258+
origin: Array [
259+
Array [
260+
jsx-without-macros.js,
261+
8,
262+
],
263+
],
264+
},
265+
},
266+
Context2: Object {
267+
msg.context: Object {
268+
extractedComments: Array [],
269+
origin: Array [
270+
Array [
271+
jsx-without-macros.js,
272+
9,
273+
],
274+
],
275+
},
276+
},
162277
Hi, my name is <0>{name}</0>: Object {
163278
extractedComments: Array [],
164279
origin: Array [
165280
Array [
166281
jsx-without-macros.js,
167-
9,
282+
12,
168283
],
169284
],
170285
},
@@ -174,11 +289,11 @@ Object {
174289
origin: Array [
175290
Array [
176291
jsx-without-macros.js,
177-
7,
292+
10,
178293
],
179294
Array [
180295
jsx-without-macros.js,
181-
8,
296+
11,
182297
],
183298
],
184299
},

packages/babel-plugin-extract-messages/test/fixtures/duplicate-id.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@ import { Trans } from "@lingui/react"
33
// OK - Default message is defined here
44
;<Trans id="msg" message="Hello World" />
55

6+
// OK - same id, different context
7+
;<Trans id="msg" context="Some context" message="Hello World" />
8+
69
// Error! - Attempt to redefine the defaultm essage
710
;<Trans id="msg" message="Different default message" />

packages/babel-plugin-extract-messages/test/fixtures/js-with-macros.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,18 @@ const id = 'message id'
3030
const withUnknownId = t({
3131
id: id
3232
})
33+
34+
const tWithContextA = t({
35+
id: "Some ID",
36+
context: "Context1"
37+
})
38+
39+
const tWithContextB = t({
40+
id: "Some other ID",
41+
context: "Context1"
42+
})
43+
44+
const defineMessageWithContext = defineMessage({
45+
id: "Some ID",
46+
context: "Context2"
47+
})

packages/babel-plugin-extract-messages/test/fixtures/js-without-macros.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ const withDescription = /*i18n*/{id: 'Description', comment: "description"}
77
const withId = /*i18n*/{id: 'ID', message: 'Message with id'}
88

99
const withValues = /*i18n*/{id: 'Values {param}', values: { param: param }}
10+
11+
const withContext = /*i18n*/{id: 'Some id', context: 'Context1'}

packages/babel-plugin-extract-messages/test/fixtures/jsx-with-macros.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { t, plural, Trans } from "@lingui/macro"
22

33
;<Trans>Hi, my name is {name}</Trans>
4+
;<Trans context="Context1">Some message</Trans>
5+
;<Trans context="Context1">Some other message</Trans>
6+
;<Trans context="Context2">Some message</Trans>
47
;<span title={t`Title`} />
58
;<span
69
title={plural(count, {

packages/babel-plugin-extract-messages/test/fixtures/jsx-without-macros.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { Trans } from "@lingui/react"
44
;<Trans /> // this should be ignored
55
;<Trans message="Missing ID" /> // this should be ignored
66
;<Trans id={"msg.hello"} comment="Description" />
7+
;<Trans id="msg.context" context="Context1" />
8+
;<Trans id="msg.notcontext" context="Context1" />
9+
;<Trans id="msg.context" context="Context2" />
710
;<Trans id="msg.default" message="Hello World" />
811
;<Trans id="msg.default" message="Hello World" />
912
;<Trans id="Hi, my name is <0>{name}</0>" values={{ count }} />
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import { Plural } from "@lingui/macro";
22

3-
<Plural value={count} one="# book" other="# books" />
3+
;<Plural value={count} one="# book" other="# books" />
4+
;<Plural value={count} one="# book" other="# books" context="Some context" />

0 commit comments

Comments
 (0)