@lingui/format-po-gettext 5.9.2 → 6.0.0-next.1

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.
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@lingui/format-po-gettext",
3
- "version": "5.9.2",
3
+ "version": "6.0.0-next.1",
4
4
  "description": "Gettext PO format with gettext-style plurals for Lingui Catalogs",
5
- "main": "./dist/po-gettext.cjs",
6
- "module": "./dist/po-gettext.mjs",
7
- "types": "./dist/po-gettext.d.ts",
5
+ "type": "module",
8
6
  "sideEffects": false,
9
7
  "license": "MIT",
8
+ "exports": {
9
+ ".": "./dist/po-gettext.mjs"
10
+ },
10
11
  "keywords": [
11
12
  "i18n",
12
13
  "lingui-formatter",
@@ -22,8 +23,8 @@
22
23
  "multilingual"
23
24
  ],
24
25
  "scripts": {
25
- "build": "rimraf ./dist && unbuild",
26
- "stub": "unbuild --stub"
26
+ "build": "unbuild",
27
+ "check-types": "tsc --noEmit"
27
28
  },
28
29
  "homepage": "https://lingui.dev",
29
30
  "repository": {
@@ -35,7 +36,7 @@
35
36
  "url": "https://github.com/lingui/js-lingui/issues"
36
37
  },
37
38
  "engines": {
38
- "node": ">=20.0.0"
39
+ "node": ">=22.19.0"
39
40
  },
40
41
  "files": [
41
42
  "LICENSE",
@@ -43,19 +44,19 @@
43
44
  "dist/"
44
45
  ],
45
46
  "dependencies": {
46
- "@lingui/conf": "5.9.2",
47
- "@lingui/format-po": "5.9.2",
48
- "@lingui/message-utils": "5.9.2",
47
+ "@lingui/conf": "6.0.0-next.1",
48
+ "@lingui/format-po": "6.0.0-next.1",
49
+ "@lingui/message-utils": "6.0.0-next.1",
49
50
  "@messageformat/parser": "^5.0.0",
50
- "cldr-core": "^45.0.0",
51
- "node-gettext": "^3.0.0",
52
- "plurals-cldr": "^2.0.1",
53
51
  "pofile": "^1.1.4"
54
52
  },
55
53
  "devDependencies": {
56
- "@lingui/jest-mocks": "^3.0.3",
57
- "mockdate": "^3.0.5",
58
- "unbuild": "2.0.0"
54
+ "@lingui/test-utils": "3.0.3",
55
+ "unbuild": "3.6.1",
56
+ "vitest": "4.0.18"
57
+ },
58
+ "unbuild": {
59
+ "declaration": "node16"
59
60
  },
60
- "gitHead": "7b30f31b5510c98442eabbcef531a6b70e1c1381"
61
+ "gitHead": "783af0c0371c7795e1fc43316f49dd253e161221"
61
62
  }
@@ -1,437 +0,0 @@
1
- 'use strict';
2
-
3
- const parser = require('@messageformat/parser');
4
- const pluralsCldr = require('plurals-cldr');
5
- const PO = require('pofile');
6
- const gettextPlurals = require('node-gettext/lib/plurals');
7
- const generateMessageId = require('@lingui/message-utils/generateMessageId');
8
- const formatPo = require('@lingui/format-po');
9
- const cardinals = require('cldr-core/supplemental/plurals.json');
10
-
11
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
12
-
13
- const pluralsCldr__default = /*#__PURE__*/_interopDefaultCompat(pluralsCldr);
14
- const PO__default = /*#__PURE__*/_interopDefaultCompat(PO);
15
- const gettextPlurals__default = /*#__PURE__*/_interopDefaultCompat(gettextPlurals);
16
- const cardinals__default = /*#__PURE__*/_interopDefaultCompat(cardinals);
17
-
18
- function renameKeys(rules) {
19
- const result = {};
20
- Object.keys(rules).forEach((k) => {
21
- const newKey = k.match(/[^-]+$/)[0];
22
- result[newKey] = rules[k];
23
- });
24
- return result;
25
- }
26
- function fillRange(value) {
27
- const [start, end] = value.split("~");
28
- const decimals = (start.split(".")[1] || "").length;
29
- const mult = Math.pow(10, decimals);
30
- const startNum = Number(start);
31
- const endNum = Number(end);
32
- const range = Array(Math.ceil(endNum * mult - startNum * mult + 1)).fill(0).map((v, idx) => (idx + startNum * mult) / mult);
33
- const last = range[range.length - 1];
34
- if (endNum !== last) {
35
- throw new Error(`Range create error for ${value}: last value is ${last}`);
36
- }
37
- return range.map((v) => Number(v));
38
- }
39
- function createSamples(src) {
40
- let result = [];
41
- src.replace(/…/, "").trim().replace(/,$/, "").split(",").map(function(val) {
42
- return val.trim();
43
- }).forEach((val) => {
44
- if (val.indexOf("~") !== -1) {
45
- result = result.concat(fillRange(val));
46
- } else {
47
- result.push(Number(val));
48
- }
49
- });
50
- return result;
51
- }
52
- function createLocaleTest(rules) {
53
- const result = {};
54
- Object.keys(rules).forEach((form) => {
55
- const samples = rules[form].split(/@integer|@decimal/).slice(1);
56
- result[form] = [];
57
- samples.forEach((sample) => {
58
- result[form] = result[form].concat(createSamples(sample));
59
- });
60
- });
61
- return result;
62
- }
63
- function getCldrPluralSamples() {
64
- const pluralRules = {};
65
- Object.entries(cardinals__default.supplemental["plurals-type-cardinal"]).forEach(
66
- ([loc, ruleset]) => {
67
- const rules = renameKeys(ruleset);
68
- pluralRules[loc.toLowerCase()] = createLocaleTest(rules);
69
- }
70
- );
71
- return pluralRules;
72
- }
73
-
74
- const cldrSamples = getCldrPluralSamples();
75
- function stringifyICUCase(icuCase) {
76
- return icuCase.tokens.map((token) => {
77
- if (token.type === "content") {
78
- return token.value;
79
- } else if (token.type === "octothorpe") {
80
- return "#";
81
- } else if (token.type === "argument") {
82
- return "{" + token.arg + "}";
83
- } else {
84
- console.warn(
85
- `Unexpected token "${token}" while stringifying plural case "${icuCase}". Token will be ignored.`
86
- );
87
- return "";
88
- }
89
- }).join("");
90
- }
91
- const ICU_PLURAL_REGEX = /^{.*, plural, .*}$/;
92
- const ICU_SELECT_REGEX = /^{.*, select(Ordinal)?, .*}$/;
93
- const LINE_ENDINGS = /\r?\n/g;
94
- const DEFAULT_CTX_PREFIX = "js-lingui:";
95
- function serializePlurals(item, message, id, isGeneratedId, options, formatterCtx) {
96
- const icuMessage = message.message;
97
- const ctxPrefix = options.customICUPrefix || DEFAULT_CTX_PREFIX;
98
- if (!icuMessage) {
99
- return item;
100
- }
101
- const _simplifiedMessage = icuMessage.replace(LINE_ENDINGS, " ");
102
- if (ICU_PLURAL_REGEX.test(_simplifiedMessage)) {
103
- try {
104
- const messageAst = parser.parse(icuMessage)[0];
105
- if (messageAst.cases.some(
106
- (icuCase) => icuCase.tokens.some((token) => token.type === "plural")
107
- )) {
108
- console.warn(
109
- `Nested plurals cannot be expressed with gettext plurals. Message with key "%s" will not be saved correctly.`,
110
- id
111
- );
112
- }
113
- const ctx = {
114
- pluralizeOn: [messageAst.arg]
115
- };
116
- if (isGeneratedId) {
117
- item.msgid = stringifyICUCase(messageAst.cases[0]);
118
- item.msgid_plural = stringifyICUCase(
119
- messageAst.cases[messageAst.cases.length - 1]
120
- );
121
- ctx.icu = icuMessage;
122
- } else {
123
- item.msgid_plural = id + "_plural";
124
- }
125
- item.extractedComments.push(ctxPrefix + serializeContextToComment(ctx));
126
- if (message.translation?.length > 0) {
127
- const ast = parser.parse(message.translation)[0];
128
- if (ast.cases == null) {
129
- console.warn(
130
- `Found translation without plural cases for key "${id}". This likely means that a translated .po file misses multiple msgstr[] entries for the key. Translation found: "${message.translation}"`
131
- );
132
- item.msgstr = [message.translation];
133
- } else {
134
- item.msgstr = ast.cases.map(stringifyICUCase);
135
- }
136
- } else if (!isGeneratedId && (formatterCtx.locale === formatterCtx.sourceLocale || formatterCtx.locale === null)) {
137
- item.msgstr = messageAst.cases.map(stringifyICUCase);
138
- }
139
- } catch (e) {
140
- console.error(`Error parsing message ICU for key "${id}":`, e);
141
- }
142
- } else {
143
- if (!options.disableSelectWarning && ICU_SELECT_REGEX.test(_simplifiedMessage)) {
144
- console.warn(
145
- `ICU 'select' and 'selectOrdinal' formats cannot be expressed natively in gettext format. Item with key "%s" will be included in the catalog as raw ICU message. To disable this warning, include '{ disableSelectWarning: true }' in the config's 'formatOptions'`,
146
- id
147
- );
148
- }
149
- item.msgstr = [message.translation];
150
- }
151
- return item;
152
- }
153
- const getPluralCases = (lang, pluralFormsHeader) => {
154
- let gettextPluralsInfo;
155
- if (pluralFormsHeader) {
156
- gettextPluralsInfo = parsePluralFormsFn(pluralFormsHeader);
157
- }
158
- const [correctLang] = lang.split(/[-_]/g);
159
- if (!gettextPluralsInfo) {
160
- gettextPluralsInfo = gettextPlurals__default[correctLang];
161
- }
162
- if (!gettextPluralsInfo) {
163
- if (lang !== "pseudo") {
164
- console.warn(
165
- `No plural rules found for language "${lang}". Please add a Plural-Forms header.`
166
- );
167
- }
168
- return void 0;
169
- }
170
- const cases = [...Array(pluralsCldr__default.forms(correctLang).length)];
171
- for (const form of pluralsCldr__default.forms(correctLang)) {
172
- const samples = cldrSamples[correctLang][form];
173
- const pluralForm = Number(
174
- gettextPluralsInfo.pluralsFunc(Number(samples[0]))
175
- );
176
- cases[pluralForm] = form;
177
- }
178
- return cases;
179
- };
180
- function parsePluralFormsFn(pluralFormsHeader) {
181
- const [npluralsExpr, expr] = pluralFormsHeader.split(";");
182
- try {
183
- const nplurals = new Function(npluralsExpr + "; return nplurals;")();
184
- const pluralsFunc = new Function(
185
- "n",
186
- expr + "; return plural;"
187
- );
188
- return {
189
- nplurals,
190
- pluralsFunc
191
- };
192
- } catch (e) {
193
- console.warn(
194
- `Plural-Forms header has incorrect value: ${pluralFormsHeader}`
195
- );
196
- return void 0;
197
- }
198
- }
199
- const convertPluralsToICU = (item, pluralForms, lang, ctxPrefix = DEFAULT_CTX_PREFIX) => {
200
- const translationCount = item.msgstr.length;
201
- const messageKey = item.msgid;
202
- if (translationCount <= 1 && !item.msgid_plural) {
203
- return;
204
- }
205
- if (!item.msgid_plural) {
206
- console.warn(
207
- `Multiple translations for item with key "%s" but missing 'msgid_plural' in catalog "${lang}". This is not supported and the plural cases will be ignored.`,
208
- messageKey
209
- );
210
- return;
211
- }
212
- const ctx = getContextFromComments(item.extractedComments, ctxPrefix);
213
- if (ctx) {
214
- item.extractedComments = item.extractedComments.filter(
215
- (comment) => !comment.startsWith(ctxPrefix)
216
- );
217
- }
218
- const storedICU = ctx?.icu;
219
- if (storedICU != null) {
220
- item.msgid = storedICU;
221
- }
222
- if (item.msgstr.every((str) => str.length === 0)) {
223
- return;
224
- }
225
- if (pluralForms == null) {
226
- console.warn(
227
- `Multiple translations for item with key "%s" in language "${lang}", but no plural cases were found. This prohibits the translation of .po plurals into ICU plurals. Pluralization will not work for this key.`,
228
- messageKey
229
- );
230
- return;
231
- }
232
- const pluralCount = pluralForms.length;
233
- if (translationCount > pluralCount) {
234
- console.warn(
235
- `More translations provided (${translationCount}) for item with key "%s" in language "${lang}" than there are plural cases available (${pluralCount}). This will result in not all translations getting picked up.`,
236
- messageKey
237
- );
238
- }
239
- const pluralClauses = item.msgstr.map(
240
- (str, index) => pluralForms[index] ? pluralForms[index] + " {" + str + "}" : ""
241
- ).join(" ");
242
- let pluralizeOn = ctx?.pluralizeOn?.[0];
243
- if (!pluralizeOn) {
244
- console.warn(
245
- `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".`,
246
- messageKey
247
- );
248
- pluralizeOn = "count";
249
- }
250
- item.msgstr = ["{" + pluralizeOn + ", plural, " + pluralClauses + "}"];
251
- };
252
- const updateContextComment = (item, contextComment, ctxPrefix) => {
253
- item.extractedComments = [
254
- ...item.extractedComments.filter((c) => !c.startsWith(ctxPrefix)),
255
- ctxPrefix + contextComment
256
- ];
257
- };
258
- function serializeContextToComment(ctx) {
259
- const urlParams = new URLSearchParams();
260
- if (ctx.icu) {
261
- urlParams.set("icu", ctx.icu);
262
- }
263
- if (ctx.pluralizeOn) {
264
- urlParams.set("pluralize_on", ctx.pluralizeOn.join(","));
265
- }
266
- urlParams.sort();
267
- return urlParams.toString();
268
- }
269
- function getContextFromComments(extractedComments, ctxPrefix) {
270
- const contextComment = extractedComments.find(
271
- (comment) => comment.startsWith(ctxPrefix)
272
- );
273
- if (!contextComment) {
274
- return void 0;
275
- }
276
- const urlParams = new URLSearchParams(
277
- contextComment?.substring(ctxPrefix.length)
278
- );
279
- return {
280
- icu: urlParams.get("icu"),
281
- pluralizeOn: urlParams.get("pluralize_on") ? urlParams.get("pluralize_on").split(",") : []
282
- };
283
- }
284
- function mergeDuplicatePluralEntries(items, options) {
285
- const ctxPrefix = options.customICUPrefix || DEFAULT_CTX_PREFIX;
286
- const itemMap = /* @__PURE__ */ new Map();
287
- for (const item of items) {
288
- if (item.msgid_plural) {
289
- const key = `${item.msgid}|||${item.msgid_plural}`;
290
- if (!itemMap.has(key)) {
291
- itemMap.set(key, []);
292
- }
293
- itemMap.get(key).push(item);
294
- }
295
- }
296
- const mergedItems = [];
297
- for (const duplicateItems of itemMap.values()) {
298
- if (duplicateItems.length === 1) {
299
- mergedItems.push(duplicateItems[0]);
300
- } else {
301
- const mergedItem = duplicateItems[0];
302
- const allVariables = duplicateItems.map((item) => {
303
- const ctx2 = getContextFromComments(item.extractedComments, ctxPrefix);
304
- return ctx2?.pluralizeOn[0] || "count";
305
- });
306
- const ctx = getContextFromComments(
307
- mergedItem.extractedComments,
308
- ctxPrefix
309
- );
310
- if (!ctx) {
311
- continue;
312
- }
313
- updateContextComment(
314
- mergedItem,
315
- serializeContextToComment({
316
- icu: replaceArgInIcu(ctx.icu, allVariables[0], "$var"),
317
- pluralizeOn: allVariables
318
- }),
319
- ctxPrefix
320
- );
321
- mergedItem.references = duplicateItems.flatMap((item) => item.references);
322
- mergedItems.push(mergedItem);
323
- }
324
- }
325
- return mergedItems;
326
- }
327
- function replaceArgInIcu(icu, oldVar, newVar) {
328
- return icu.replace(new RegExp(`{${oldVar}, plural,`), `{${newVar}, plural,`);
329
- }
330
- function expandMergedPluralEntries(items, options) {
331
- const ctxPrefix = options.customICUPrefix || DEFAULT_CTX_PREFIX;
332
- const expandedItems = [];
333
- for (const item of items) {
334
- if (!item.msgid_plural) {
335
- expandedItems.push(item);
336
- continue;
337
- }
338
- const ctx = getContextFromComments(item.extractedComments, ctxPrefix);
339
- if (!ctx) {
340
- console.warn(
341
- `Plural entry with msgid "${item.msgid}" is missing context information for expansion.`
342
- );
343
- expandedItems.push(item);
344
- continue;
345
- }
346
- const variableList = ctx.pluralizeOn;
347
- if (variableList.length === 1) {
348
- expandedItems.push(item);
349
- continue;
350
- }
351
- for (const variable of variableList) {
352
- const newItem = new PO__default.Item();
353
- newItem.msgid = item.msgid;
354
- newItem.msgid_plural = item.msgid_plural;
355
- newItem.msgstr = [...item.msgstr];
356
- newItem.msgctxt = item.msgctxt;
357
- newItem.comments = [...item.comments];
358
- updateContextComment(
359
- item,
360
- serializeContextToComment({
361
- pluralizeOn: [variable],
362
- // get icu comment, replace variable placeholder with current variable
363
- icu: replaceArgInIcu(ctx.icu, "\\$var", variable)
364
- }),
365
- ctxPrefix
366
- );
367
- newItem.extractedComments = item.extractedComments;
368
- newItem.flags = { ...item.flags };
369
- expandedItems.push(newItem);
370
- }
371
- }
372
- return expandedItems;
373
- }
374
- function formatter(options = {}) {
375
- options = {
376
- origins: true,
377
- lineNumbers: true,
378
- ...options
379
- };
380
- const formatter2 = formatPo.formatter(options);
381
- return {
382
- catalogExtension: ".po",
383
- templateExtension: ".pot",
384
- parse(content, ctx) {
385
- const po = PO__default.parse(content);
386
- if (options.mergePlurals) {
387
- po.items = expandMergedPluralEntries(po.items, options);
388
- }
389
- const pluralForms = getPluralCases(
390
- po.headers.Language,
391
- po.headers["Plural-Forms"]
392
- );
393
- po.items.forEach((item) => {
394
- convertPluralsToICU(
395
- item,
396
- pluralForms,
397
- po.headers.Language,
398
- options.customICUPrefix
399
- );
400
- });
401
- return formatter2.parse(po.toString(), ctx);
402
- },
403
- serialize(catalog, ctx) {
404
- const po = PO__default.parse(formatter2.serialize(catalog, ctx));
405
- po.items = po.items.map((item) => {
406
- const isGeneratedId = !item.extractedComments.includes(
407
- "js-lingui-explicit-id"
408
- );
409
- const id = isGeneratedId ? generateMessageId.generateMessageId(item.msgid, item.msgctxt) : item.msgid;
410
- const message = catalog[id];
411
- return serializePlurals(item, message, id, isGeneratedId, options, ctx);
412
- });
413
- if (options.mergePlurals) {
414
- const mergedPlurals = mergeDuplicatePluralEntries(po.items, options);
415
- const newItems = [];
416
- const processed = /* @__PURE__ */ new Set();
417
- po.items.forEach((item) => {
418
- if (!item.msgid_plural) {
419
- newItems.push(item);
420
- } else {
421
- const mergedItem = mergedPlurals.find(
422
- (merged) => merged.msgid === item.msgid && merged.msgid_plural === item.msgid_plural
423
- );
424
- if (mergedItem && !processed.has(mergedItem)) {
425
- processed.add(mergedItem);
426
- newItems.push(mergedItem);
427
- }
428
- }
429
- });
430
- po.items = newItems;
431
- }
432
- return po.toString();
433
- }
434
- };
435
- }
436
-
437
- exports.formatter = formatter;
@@ -1,24 +0,0 @@
1
- import { CatalogFormatter } from '@lingui/conf';
2
- import { PoFormatterOptions } from '@lingui/format-po';
3
-
4
- type PoGettextFormatterOptions = PoFormatterOptions & {
5
- /**
6
- * Disable warning about unsupported `Select` feature encountered in catalogs
7
- *
8
- * @default false
9
- */
10
- disableSelectWarning?: boolean;
11
- /**
12
- * Overrides the default prefix for icu and plural comments in the final PO catalog.
13
- *
14
- * @default "js-lingui:"
15
- */
16
- customICUPrefix?: string;
17
- /**
18
- * Combine plural entries that have the same content but different variables into a single PO entry
19
- */
20
- mergePlurals?: boolean;
21
- };
22
- declare function formatter(options?: PoGettextFormatterOptions): CatalogFormatter;
23
-
24
- export { type PoGettextFormatterOptions, formatter };
@@ -1,24 +0,0 @@
1
- import { CatalogFormatter } from '@lingui/conf';
2
- import { PoFormatterOptions } from '@lingui/format-po';
3
-
4
- type PoGettextFormatterOptions = PoFormatterOptions & {
5
- /**
6
- * Disable warning about unsupported `Select` feature encountered in catalogs
7
- *
8
- * @default false
9
- */
10
- disableSelectWarning?: boolean;
11
- /**
12
- * Overrides the default prefix for icu and plural comments in the final PO catalog.
13
- *
14
- * @default "js-lingui:"
15
- */
16
- customICUPrefix?: string;
17
- /**
18
- * Combine plural entries that have the same content but different variables into a single PO entry
19
- */
20
- mergePlurals?: boolean;
21
- };
22
- declare function formatter(options?: PoGettextFormatterOptions): CatalogFormatter;
23
-
24
- export { type PoGettextFormatterOptions, formatter };