@lingui/format-po-gettext 5.6.0 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -110,20 +110,19 @@ function serializePlurals(item, message, id, isGeneratedId, options) {
110
110
  id
111
111
  );
112
112
  }
113
- const ctx = new URLSearchParams({
114
- pluralize_on: messageAst.arg
115
- });
113
+ const ctx = {
114
+ pluralizeOn: [messageAst.arg]
115
+ };
116
116
  if (isGeneratedId) {
117
117
  item.msgid = stringifyICUCase(messageAst.cases[0]);
118
118
  item.msgid_plural = stringifyICUCase(
119
119
  messageAst.cases[messageAst.cases.length - 1]
120
120
  );
121
- ctx.set("icu", icuMessage);
121
+ ctx.icu = icuMessage;
122
122
  } else {
123
123
  item.msgid_plural = id + "_plural";
124
124
  }
125
- ctx.sort();
126
- item.extractedComments.push(ctxPrefix + ctx.toString());
125
+ item.extractedComments.push(ctxPrefix + serializeContextToComment(ctx));
127
126
  if (message.translation?.length > 0) {
128
127
  const ast = parser.parse(message.translation)[0];
129
128
  if (ast.cases == null) {
@@ -208,14 +207,13 @@ const convertPluralsToICU = (item, pluralForms, lang, ctxPrefix = DEFAULT_CTX_PR
208
207
  );
209
208
  return;
210
209
  }
211
- const contextComment = item.extractedComments.find((comment) => comment.startsWith(ctxPrefix))?.substring(ctxPrefix.length);
212
- const ctx = new URLSearchParams(contextComment);
213
- if (contextComment != null) {
210
+ const ctx = getContextFromComments(item.extractedComments, ctxPrefix);
211
+ if (ctx) {
214
212
  item.extractedComments = item.extractedComments.filter(
215
213
  (comment) => !comment.startsWith(ctxPrefix)
216
214
  );
217
215
  }
218
- const storedICU = ctx.get("icu");
216
+ const storedICU = ctx?.icu;
219
217
  if (storedICU != null) {
220
218
  item.msgid = storedICU;
221
219
  }
@@ -239,7 +237,7 @@ const convertPluralsToICU = (item, pluralForms, lang, ctxPrefix = DEFAULT_CTX_PR
239
237
  const pluralClauses = item.msgstr.map(
240
238
  (str, index) => pluralForms[index] ? pluralForms[index] + " {" + str + "}" : ""
241
239
  ).join(" ");
242
- let pluralizeOn = ctx.get("pluralize_on");
240
+ let pluralizeOn = ctx?.pluralizeOn?.[0];
243
241
  if (!pluralizeOn) {
244
242
  console.warn(
245
243
  `Unable to determine plural placeholder name for item with key "%s" in language "${lang}" (should be stored in a comment starting with "#. ${ctxPrefix}"), assuming "count".`,
@@ -249,6 +247,128 @@ const convertPluralsToICU = (item, pluralForms, lang, ctxPrefix = DEFAULT_CTX_PR
249
247
  }
250
248
  item.msgstr = ["{" + pluralizeOn + ", plural, " + pluralClauses + "}"];
251
249
  };
250
+ const updateContextComment = (item, contextComment, ctxPrefix) => {
251
+ item.extractedComments = [
252
+ ...item.extractedComments.filter((c) => !c.startsWith(ctxPrefix)),
253
+ ctxPrefix + contextComment
254
+ ];
255
+ };
256
+ function serializeContextToComment(ctx) {
257
+ const urlParams = new URLSearchParams();
258
+ if (ctx.icu) {
259
+ urlParams.set("icu", ctx.icu);
260
+ }
261
+ if (ctx.pluralizeOn) {
262
+ urlParams.set("pluralize_on", ctx.pluralizeOn.join(","));
263
+ }
264
+ urlParams.sort();
265
+ return urlParams.toString().replace(/%7B/g, "{").replace(/%7D/g, "}").replace(/%2C/g, ",").replace(/%23/g, "#").replace(/%24/g, "$").replace(/\+/g, " ");
266
+ }
267
+ function getContextFromComments(extractedComments, ctxPrefix) {
268
+ const contextComment = extractedComments.find(
269
+ (comment) => comment.startsWith(ctxPrefix)
270
+ );
271
+ if (!contextComment) {
272
+ return void 0;
273
+ }
274
+ const urlParams = new URLSearchParams(
275
+ contextComment?.substring(ctxPrefix.length)
276
+ );
277
+ return {
278
+ icu: urlParams.get("icu"),
279
+ pluralizeOn: urlParams.get("pluralize_on") ? urlParams.get("pluralize_on").split(",") : []
280
+ };
281
+ }
282
+ function mergeDuplicatePluralEntries(items, options) {
283
+ const ctxPrefix = options.customICUPrefix || DEFAULT_CTX_PREFIX;
284
+ const itemMap = /* @__PURE__ */ new Map();
285
+ for (const item of items) {
286
+ if (item.msgid_plural) {
287
+ const key = `${item.msgid}|||${item.msgid_plural}`;
288
+ if (!itemMap.has(key)) {
289
+ itemMap.set(key, []);
290
+ }
291
+ itemMap.get(key).push(item);
292
+ }
293
+ }
294
+ const mergedItems = [];
295
+ for (const duplicateItems of itemMap.values()) {
296
+ if (duplicateItems.length === 1) {
297
+ mergedItems.push(duplicateItems[0]);
298
+ } else {
299
+ const mergedItem = duplicateItems[0];
300
+ const allVariables = duplicateItems.map((item) => {
301
+ const ctx2 = getContextFromComments(item.extractedComments, ctxPrefix);
302
+ return ctx2?.pluralizeOn[0] || "count";
303
+ });
304
+ const ctx = getContextFromComments(
305
+ mergedItem.extractedComments,
306
+ ctxPrefix
307
+ );
308
+ if (!ctx) {
309
+ continue;
310
+ }
311
+ updateContextComment(
312
+ mergedItem,
313
+ serializeContextToComment({
314
+ icu: replaceArgInIcu(ctx.icu, allVariables[0], "$var"),
315
+ pluralizeOn: allVariables
316
+ }),
317
+ ctxPrefix
318
+ );
319
+ mergedItem.references = duplicateItems.flatMap((item) => item.references);
320
+ mergedItems.push(mergedItem);
321
+ }
322
+ }
323
+ return mergedItems;
324
+ }
325
+ function replaceArgInIcu(icu, oldVar, newVar) {
326
+ return icu.replace(new RegExp(`{${oldVar}, plural,`), `{${newVar}, plural,`);
327
+ }
328
+ function expandMergedPluralEntries(items, options) {
329
+ const ctxPrefix = options.customICUPrefix || DEFAULT_CTX_PREFIX;
330
+ const expandedItems = [];
331
+ for (const item of items) {
332
+ if (!item.msgid_plural) {
333
+ expandedItems.push(item);
334
+ continue;
335
+ }
336
+ const ctx = getContextFromComments(item.extractedComments, ctxPrefix);
337
+ if (!ctx) {
338
+ console.warn(
339
+ `Plural entry with msgid "${item.msgid}" is missing context information for expansion.`
340
+ );
341
+ expandedItems.push(item);
342
+ continue;
343
+ }
344
+ const variableList = ctx.pluralizeOn;
345
+ if (variableList.length === 1) {
346
+ expandedItems.push(item);
347
+ continue;
348
+ }
349
+ for (const variable of variableList) {
350
+ const newItem = new PO__default.Item();
351
+ newItem.msgid = item.msgid;
352
+ newItem.msgid_plural = item.msgid_plural;
353
+ newItem.msgstr = [...item.msgstr];
354
+ newItem.msgctxt = item.msgctxt;
355
+ newItem.comments = [...item.comments];
356
+ updateContextComment(
357
+ item,
358
+ serializeContextToComment({
359
+ pluralizeOn: [variable],
360
+ // get icu comment, replace variable placeholder with current variable
361
+ icu: replaceArgInIcu(ctx.icu, "\\$var", variable)
362
+ }),
363
+ ctxPrefix
364
+ );
365
+ newItem.extractedComments = item.extractedComments;
366
+ newItem.flags = { ...item.flags };
367
+ expandedItems.push(newItem);
368
+ }
369
+ }
370
+ return expandedItems;
371
+ }
252
372
  function formatter(options = {}) {
253
373
  options = {
254
374
  origins: true,
@@ -261,6 +381,9 @@ function formatter(options = {}) {
261
381
  templateExtension: ".pot",
262
382
  parse(content, ctx) {
263
383
  const po = PO__default.parse(content);
384
+ if (options.mergePlurals) {
385
+ po.items = expandMergedPluralEntries(po.items, options);
386
+ }
264
387
  const pluralForms = getPluralCases(
265
388
  po.headers.Language,
266
389
  po.headers["Plural-Forms"]
@@ -285,6 +408,25 @@ function formatter(options = {}) {
285
408
  const message = catalog[id];
286
409
  return serializePlurals(item, message, id, isGeneratedId, options);
287
410
  });
411
+ if (options.mergePlurals) {
412
+ const mergedPlurals = mergeDuplicatePluralEntries(po.items, options);
413
+ const newItems = [];
414
+ const processed = /* @__PURE__ */ new Set();
415
+ po.items.forEach((item) => {
416
+ if (!item.msgid_plural) {
417
+ newItems.push(item);
418
+ } else {
419
+ const mergedItem = mergedPlurals.find(
420
+ (merged) => merged.msgid === item.msgid && merged.msgid_plural === item.msgid_plural
421
+ );
422
+ if (mergedItem && !processed.has(mergedItem)) {
423
+ processed.add(mergedItem);
424
+ newItems.push(mergedItem);
425
+ }
426
+ }
427
+ });
428
+ po.items = newItems;
429
+ }
288
430
  return po.toString();
289
431
  }
290
432
  };
@@ -4,6 +4,7 @@ import { PoFormatterOptions } from '@lingui/format-po';
4
4
  type PoGettextFormatterOptions = PoFormatterOptions & {
5
5
  disableSelectWarning?: boolean;
6
6
  customICUPrefix?: string;
7
+ mergePlurals?: boolean;
7
8
  };
8
9
  declare function formatter(options?: PoGettextFormatterOptions): CatalogFormatter;
9
10
 
@@ -4,6 +4,7 @@ import { PoFormatterOptions } from '@lingui/format-po';
4
4
  type PoGettextFormatterOptions = PoFormatterOptions & {
5
5
  disableSelectWarning?: boolean;
6
6
  customICUPrefix?: string;
7
+ mergePlurals?: boolean;
7
8
  };
8
9
  declare function formatter(options?: PoGettextFormatterOptions): CatalogFormatter;
9
10
 
@@ -4,6 +4,7 @@ import { PoFormatterOptions } from '@lingui/format-po';
4
4
  type PoGettextFormatterOptions = PoFormatterOptions & {
5
5
  disableSelectWarning?: boolean;
6
6
  customICUPrefix?: string;
7
+ mergePlurals?: boolean;
7
8
  };
8
9
  declare function formatter(options?: PoGettextFormatterOptions): CatalogFormatter;
9
10
 
@@ -101,20 +101,19 @@ function serializePlurals(item, message, id, isGeneratedId, options) {
101
101
  id
102
102
  );
103
103
  }
104
- const ctx = new URLSearchParams({
105
- pluralize_on: messageAst.arg
106
- });
104
+ const ctx = {
105
+ pluralizeOn: [messageAst.arg]
106
+ };
107
107
  if (isGeneratedId) {
108
108
  item.msgid = stringifyICUCase(messageAst.cases[0]);
109
109
  item.msgid_plural = stringifyICUCase(
110
110
  messageAst.cases[messageAst.cases.length - 1]
111
111
  );
112
- ctx.set("icu", icuMessage);
112
+ ctx.icu = icuMessage;
113
113
  } else {
114
114
  item.msgid_plural = id + "_plural";
115
115
  }
116
- ctx.sort();
117
- item.extractedComments.push(ctxPrefix + ctx.toString());
116
+ item.extractedComments.push(ctxPrefix + serializeContextToComment(ctx));
118
117
  if (message.translation?.length > 0) {
119
118
  const ast = parse(message.translation)[0];
120
119
  if (ast.cases == null) {
@@ -199,14 +198,13 @@ const convertPluralsToICU = (item, pluralForms, lang, ctxPrefix = DEFAULT_CTX_PR
199
198
  );
200
199
  return;
201
200
  }
202
- const contextComment = item.extractedComments.find((comment) => comment.startsWith(ctxPrefix))?.substring(ctxPrefix.length);
203
- const ctx = new URLSearchParams(contextComment);
204
- if (contextComment != null) {
201
+ const ctx = getContextFromComments(item.extractedComments, ctxPrefix);
202
+ if (ctx) {
205
203
  item.extractedComments = item.extractedComments.filter(
206
204
  (comment) => !comment.startsWith(ctxPrefix)
207
205
  );
208
206
  }
209
- const storedICU = ctx.get("icu");
207
+ const storedICU = ctx?.icu;
210
208
  if (storedICU != null) {
211
209
  item.msgid = storedICU;
212
210
  }
@@ -230,7 +228,7 @@ const convertPluralsToICU = (item, pluralForms, lang, ctxPrefix = DEFAULT_CTX_PR
230
228
  const pluralClauses = item.msgstr.map(
231
229
  (str, index) => pluralForms[index] ? pluralForms[index] + " {" + str + "}" : ""
232
230
  ).join(" ");
233
- let pluralizeOn = ctx.get("pluralize_on");
231
+ let pluralizeOn = ctx?.pluralizeOn?.[0];
234
232
  if (!pluralizeOn) {
235
233
  console.warn(
236
234
  `Unable to determine plural placeholder name for item with key "%s" in language "${lang}" (should be stored in a comment starting with "#. ${ctxPrefix}"), assuming "count".`,
@@ -240,6 +238,128 @@ const convertPluralsToICU = (item, pluralForms, lang, ctxPrefix = DEFAULT_CTX_PR
240
238
  }
241
239
  item.msgstr = ["{" + pluralizeOn + ", plural, " + pluralClauses + "}"];
242
240
  };
241
+ const updateContextComment = (item, contextComment, ctxPrefix) => {
242
+ item.extractedComments = [
243
+ ...item.extractedComments.filter((c) => !c.startsWith(ctxPrefix)),
244
+ ctxPrefix + contextComment
245
+ ];
246
+ };
247
+ function serializeContextToComment(ctx) {
248
+ const urlParams = new URLSearchParams();
249
+ if (ctx.icu) {
250
+ urlParams.set("icu", ctx.icu);
251
+ }
252
+ if (ctx.pluralizeOn) {
253
+ urlParams.set("pluralize_on", ctx.pluralizeOn.join(","));
254
+ }
255
+ urlParams.sort();
256
+ return urlParams.toString().replace(/%7B/g, "{").replace(/%7D/g, "}").replace(/%2C/g, ",").replace(/%23/g, "#").replace(/%24/g, "$").replace(/\+/g, " ");
257
+ }
258
+ function getContextFromComments(extractedComments, ctxPrefix) {
259
+ const contextComment = extractedComments.find(
260
+ (comment) => comment.startsWith(ctxPrefix)
261
+ );
262
+ if (!contextComment) {
263
+ return void 0;
264
+ }
265
+ const urlParams = new URLSearchParams(
266
+ contextComment?.substring(ctxPrefix.length)
267
+ );
268
+ return {
269
+ icu: urlParams.get("icu"),
270
+ pluralizeOn: urlParams.get("pluralize_on") ? urlParams.get("pluralize_on").split(",") : []
271
+ };
272
+ }
273
+ function mergeDuplicatePluralEntries(items, options) {
274
+ const ctxPrefix = options.customICUPrefix || DEFAULT_CTX_PREFIX;
275
+ const itemMap = /* @__PURE__ */ new Map();
276
+ for (const item of items) {
277
+ if (item.msgid_plural) {
278
+ const key = `${item.msgid}|||${item.msgid_plural}`;
279
+ if (!itemMap.has(key)) {
280
+ itemMap.set(key, []);
281
+ }
282
+ itemMap.get(key).push(item);
283
+ }
284
+ }
285
+ const mergedItems = [];
286
+ for (const duplicateItems of itemMap.values()) {
287
+ if (duplicateItems.length === 1) {
288
+ mergedItems.push(duplicateItems[0]);
289
+ } else {
290
+ const mergedItem = duplicateItems[0];
291
+ const allVariables = duplicateItems.map((item) => {
292
+ const ctx2 = getContextFromComments(item.extractedComments, ctxPrefix);
293
+ return ctx2?.pluralizeOn[0] || "count";
294
+ });
295
+ const ctx = getContextFromComments(
296
+ mergedItem.extractedComments,
297
+ ctxPrefix
298
+ );
299
+ if (!ctx) {
300
+ continue;
301
+ }
302
+ updateContextComment(
303
+ mergedItem,
304
+ serializeContextToComment({
305
+ icu: replaceArgInIcu(ctx.icu, allVariables[0], "$var"),
306
+ pluralizeOn: allVariables
307
+ }),
308
+ ctxPrefix
309
+ );
310
+ mergedItem.references = duplicateItems.flatMap((item) => item.references);
311
+ mergedItems.push(mergedItem);
312
+ }
313
+ }
314
+ return mergedItems;
315
+ }
316
+ function replaceArgInIcu(icu, oldVar, newVar) {
317
+ return icu.replace(new RegExp(`{${oldVar}, plural,`), `{${newVar}, plural,`);
318
+ }
319
+ function expandMergedPluralEntries(items, options) {
320
+ const ctxPrefix = options.customICUPrefix || DEFAULT_CTX_PREFIX;
321
+ const expandedItems = [];
322
+ for (const item of items) {
323
+ if (!item.msgid_plural) {
324
+ expandedItems.push(item);
325
+ continue;
326
+ }
327
+ const ctx = getContextFromComments(item.extractedComments, ctxPrefix);
328
+ if (!ctx) {
329
+ console.warn(
330
+ `Plural entry with msgid "${item.msgid}" is missing context information for expansion.`
331
+ );
332
+ expandedItems.push(item);
333
+ continue;
334
+ }
335
+ const variableList = ctx.pluralizeOn;
336
+ if (variableList.length === 1) {
337
+ expandedItems.push(item);
338
+ continue;
339
+ }
340
+ for (const variable of variableList) {
341
+ const newItem = new PO.Item();
342
+ newItem.msgid = item.msgid;
343
+ newItem.msgid_plural = item.msgid_plural;
344
+ newItem.msgstr = [...item.msgstr];
345
+ newItem.msgctxt = item.msgctxt;
346
+ newItem.comments = [...item.comments];
347
+ updateContextComment(
348
+ item,
349
+ serializeContextToComment({
350
+ pluralizeOn: [variable],
351
+ // get icu comment, replace variable placeholder with current variable
352
+ icu: replaceArgInIcu(ctx.icu, "\\$var", variable)
353
+ }),
354
+ ctxPrefix
355
+ );
356
+ newItem.extractedComments = item.extractedComments;
357
+ newItem.flags = { ...item.flags };
358
+ expandedItems.push(newItem);
359
+ }
360
+ }
361
+ return expandedItems;
362
+ }
243
363
  function formatter(options = {}) {
244
364
  options = {
245
365
  origins: true,
@@ -252,6 +372,9 @@ function formatter(options = {}) {
252
372
  templateExtension: ".pot",
253
373
  parse(content, ctx) {
254
374
  const po = PO.parse(content);
375
+ if (options.mergePlurals) {
376
+ po.items = expandMergedPluralEntries(po.items, options);
377
+ }
255
378
  const pluralForms = getPluralCases(
256
379
  po.headers.Language,
257
380
  po.headers["Plural-Forms"]
@@ -276,6 +399,25 @@ function formatter(options = {}) {
276
399
  const message = catalog[id];
277
400
  return serializePlurals(item, message, id, isGeneratedId, options);
278
401
  });
402
+ if (options.mergePlurals) {
403
+ const mergedPlurals = mergeDuplicatePluralEntries(po.items, options);
404
+ const newItems = [];
405
+ const processed = /* @__PURE__ */ new Set();
406
+ po.items.forEach((item) => {
407
+ if (!item.msgid_plural) {
408
+ newItems.push(item);
409
+ } else {
410
+ const mergedItem = mergedPlurals.find(
411
+ (merged) => merged.msgid === item.msgid && merged.msgid_plural === item.msgid_plural
412
+ );
413
+ if (mergedItem && !processed.has(mergedItem)) {
414
+ processed.add(mergedItem);
415
+ newItems.push(mergedItem);
416
+ }
417
+ }
418
+ });
419
+ po.items = newItems;
420
+ }
279
421
  return po.toString();
280
422
  }
281
423
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingui/format-po-gettext",
3
- "version": "5.6.0",
3
+ "version": "5.7.0",
4
4
  "description": "Gettext PO format with gettext-style plurals for Lingui Catalogs",
5
5
  "main": "./dist/po-gettext.cjs",
6
6
  "module": "./dist/po-gettext.mjs",
@@ -43,9 +43,9 @@
43
43
  "dist/"
44
44
  ],
45
45
  "dependencies": {
46
- "@lingui/conf": "5.6.0",
47
- "@lingui/format-po": "5.6.0",
48
- "@lingui/message-utils": "5.6.0",
46
+ "@lingui/conf": "5.7.0",
47
+ "@lingui/format-po": "5.7.0",
48
+ "@lingui/message-utils": "5.7.0",
49
49
  "@messageformat/parser": "^5.0.0",
50
50
  "cldr-core": "^45.0.0",
51
51
  "node-gettext": "^3.0.0",
@@ -57,5 +57,5 @@
57
57
  "mockdate": "^3.0.5",
58
58
  "unbuild": "2.0.0"
59
59
  },
60
- "gitHead": "6a8e2a60cf936ba8dc7ae09442ba616e5e4e9e5d"
60
+ "gitHead": "e8c42d548af8fae7365094e58249148fa6a6019f"
61
61
  }