@lingui/format-po 6.6.0 → 6.8.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.
package/README.md CHANGED
@@ -4,20 +4,21 @@
4
4
 
5
5
  # @lingui/format-po
6
6
 
7
- > Read and write message catalogs in Gettext PO format with ICU plurals
7
+ > Reads and writes Lingui message catalogs as gettext PO files with ICU plurals, the default catalog format
8
8
 
9
- `@lingui/format-po` is part of [LinguiJS][linguijs]. See the [documentation][documentation] for all information, tutorials and examples.
9
+ `@lingui/format-po` is part of [Lingui][documentation]. Lingui is a lightweight, open-source internationalization (i18n) library for JavaScript and TypeScript. It brings compile-time macros and a CLI for message extraction to React, React Native, Vue, SolidJS, Astro, Svelte, and Node.js.
10
10
 
11
- ## Installation & Usage
11
+ PO is the default and recommended catalog format: it is readable, almost every translation tool supports it, and it carries comments, source locations and context for translators. The formatter ships with `@lingui/cli`, so install this package only to pass options.
12
12
 
13
- See the [reference][reference] documentation.
13
+ ## Usage
14
+
15
+ See the [catalog formats reference][reference].
14
16
 
15
17
  ## License
16
18
 
17
- This package is licensed under [MIT][license] license.
19
+ This package is licensed under the [MIT][license] license.
18
20
 
19
21
  [license]: https://github.com/lingui/js-lingui/blob/main/LICENSE
20
- [linguijs]: https://github.com/lingui/js-lingui
21
22
  [documentation]: https://lingui.dev
22
23
  [reference]: https://lingui.dev/ref/catalog-formats#po
23
24
  [package]: https://www.npmjs.com/package/@lingui/format-po
package/dist/po.d.mts CHANGED
@@ -1,3 +1,4 @@
1
+ import { PoFile } from 'pofile-ts';
1
2
  import { CatalogFormatter } from '@lingui/conf';
2
3
 
3
4
  type PoFormatterOptions = {
@@ -98,7 +99,9 @@ type PoFormatterOptions = {
98
99
  */
99
100
  compactMultiline?: boolean;
100
101
  };
102
+ /** Parse a PO file while preserving obsolete markers that pofile-ts can lose. */
103
+ declare function parsePoFile(content: string): PoFile;
101
104
  declare function formatter(options?: PoFormatterOptions): CatalogFormatter;
102
105
 
103
- export { formatter };
106
+ export { formatter, parsePoFile };
104
107
  export type { PoFormatterOptions };
package/dist/po.mjs CHANGED
@@ -32,16 +32,98 @@ const joinOrigin = (origin) => origin.join(":");
32
32
  function isGeneratedId(id, message) {
33
33
  return id === generateMessageId(message.message, message.context);
34
34
  }
35
- function getCreateHeaders(language, customHeaderAttributes) {
36
- return {
37
- "POT-Creation-Date": formatPotCreationDate(/* @__PURE__ */ new Date()),
38
- "MIME-Version": "1.0",
39
- "Content-Type": "text/plain; charset=utf-8",
40
- "Content-Transfer-Encoding": "8bit",
41
- "X-Generator": "@lingui/cli",
42
- ...language ? { Language: language } : {},
43
- ...customHeaderAttributes ?? {}
44
- };
35
+ const MANAGED_HEADERS = [
36
+ "POT-Creation-Date",
37
+ "MIME-Version",
38
+ "Content-Type",
39
+ "Content-Transfer-Encoding",
40
+ "X-Generator",
41
+ "Language"
42
+ ];
43
+ function getNewHeaders(language, customHeaderAttributes) {
44
+ const nextHeaders = {};
45
+ nextHeaders["POT-Creation-Date"] = customHeaderAttributes?.["POT-Creation-Date"] ?? formatPotCreationDate(/* @__PURE__ */ new Date());
46
+ nextHeaders["MIME-Version"] = "1.0";
47
+ nextHeaders["Content-Type"] = "text/plain; charset=utf-8";
48
+ nextHeaders["Content-Transfer-Encoding"] = "8bit";
49
+ nextHeaders["X-Generator"] = "@lingui/cli";
50
+ if (language) {
51
+ nextHeaders.Language = language;
52
+ }
53
+ Object.entries(customHeaderAttributes ?? {}).forEach(([key, value]) => {
54
+ nextHeaders[key] = value;
55
+ });
56
+ return nextHeaders;
57
+ }
58
+ function getExistingHeaders(existingHeaders, existingHeaderOrder, customHeaderAttributes) {
59
+ const nextHeaders = {};
60
+ existingHeaderOrder.forEach((key) => {
61
+ if (key in existingHeaders) {
62
+ nextHeaders[key] = existingHeaders[key];
63
+ }
64
+ });
65
+ Object.entries(customHeaderAttributes ?? {}).forEach(([key, value]) => {
66
+ nextHeaders[key] = value;
67
+ });
68
+ return nextHeaders;
69
+ }
70
+ function getHeaderOrder(headers, language, customHeaderAttributes) {
71
+ const managedOrder = [
72
+ "POT-Creation-Date",
73
+ "MIME-Version",
74
+ "Content-Type",
75
+ "Content-Transfer-Encoding",
76
+ "X-Generator",
77
+ ...language ? ["Language"] : [],
78
+ ...Object.keys(customHeaderAttributes ?? {}).filter(
79
+ (key) => !MANAGED_HEADERS.includes(key)
80
+ )
81
+ ];
82
+ const order = new Set(managedOrder);
83
+ Object.keys(headers).forEach((key) => {
84
+ order.add(key);
85
+ });
86
+ return [...order];
87
+ }
88
+ function getExistingHeaderOrder(headers, existingHeaderOrder) {
89
+ const order = new Set(existingHeaderOrder.filter((key) => key in headers));
90
+ Object.keys(headers).forEach((key) => {
91
+ order.add(key);
92
+ });
93
+ return [...order];
94
+ }
95
+ function parsePoItemsInSourceOrder(content) {
96
+ const lines = content.split(/\r?\n/);
97
+ const messageStart = /^(?:#~\s*)?msgid(?:\s|$)/;
98
+ const contextStart = /^(?:#~\s*)?msgctxt(?:\s|$)/;
99
+ const itemStarts = [];
100
+ let pendingContextStart;
101
+ lines.forEach((rawLine, index) => {
102
+ const line = rawLine.trim();
103
+ if (contextStart.test(line)) {
104
+ pendingContextStart = index;
105
+ return;
106
+ }
107
+ if (messageStart.test(line)) {
108
+ itemStarts.push(pendingContextStart ?? index);
109
+ pendingContextStart = void 0;
110
+ }
111
+ });
112
+ return itemStarts.flatMap((start, index) => {
113
+ const end = itemStarts[index + 1] ?? lines.length;
114
+ return parsePo(lines.slice(start, end).join("\n")).items;
115
+ });
116
+ }
117
+ function parsePoFile(content) {
118
+ const po = parsePo(content);
119
+ const sourceItems = parsePoItemsInSourceOrder(content);
120
+ po.items.forEach((item, index) => {
121
+ const sourceItem = sourceItems[index];
122
+ if (sourceItem) {
123
+ item.obsolete = sourceItem.obsolete;
124
+ }
125
+ });
126
+ return po;
45
127
  }
46
128
  const EXPLICIT_ID_FLAG = "js-lingui-explicit-id";
47
129
  const GENERATED_ID_FLAG = "js-lingui-generated-id";
@@ -136,7 +218,10 @@ function deserialize(items, options) {
136
218
  id = generateMessageId(item.msgid, item.msgctxt);
137
219
  message.message = item.msgid;
138
220
  }
139
- catalog[id] = message;
221
+ const existingMessage = catalog[id];
222
+ if (existingMessage === void 0 || !message.obsolete || existingMessage.obsolete) {
223
+ catalog[id] = message;
224
+ }
140
225
  return catalog;
141
226
  }, {});
142
227
  }
@@ -151,21 +236,20 @@ function formatter(options = {}) {
151
236
  catalogExtension: ".po",
152
237
  templateExtension: ".pot",
153
238
  parse(content) {
154
- const po = parsePo(content);
239
+ const po = parsePoFile(content);
155
240
  return deserialize(po.items, options);
156
241
  },
157
242
  serialize(catalog, ctx) {
158
- let po;
159
- if (ctx.existing) {
160
- po = parsePo(ctx.existing);
161
- } else {
162
- po = createPoFile();
163
- po.headers = getCreateHeaders(
164
- ctx.locale,
165
- options.customHeaderAttributes
166
- );
167
- po.headerOrder = Object.keys(po.headers);
168
- }
243
+ const existingPo = ctx.existing !== void 0 && ctx.existing !== "" ? parsePoFile(ctx.existing) : void 0;
244
+ const po = createPoFile();
245
+ po.comments = [...existingPo?.comments ?? []];
246
+ po.extractedComments = [...existingPo?.extractedComments ?? []];
247
+ po.headers = existingPo ? getExistingHeaders(
248
+ existingPo.headers,
249
+ existingPo.headerOrder,
250
+ options.customHeaderAttributes
251
+ ) : getNewHeaders(ctx.locale, options.customHeaderAttributes);
252
+ po.headerOrder = existingPo ? getExistingHeaderOrder(po.headers, existingPo.headerOrder) : getHeaderOrder(po.headers, ctx.locale, options.customHeaderAttributes);
169
253
  po.items = serialize(catalog, options, {
170
254
  locale: ctx.locale,
171
255
  sourceLocale: ctx.sourceLocale
@@ -182,4 +266,4 @@ function formatter(options = {}) {
182
266
  };
183
267
  }
184
268
 
185
- export { formatter };
269
+ export { formatter, parsePoFile };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lingui/format-po",
3
- "version": "6.6.0",
4
- "description": "Gettext PO formatter for Lingui message catalogs",
3
+ "version": "6.8.0",
4
+ "description": "Reads and writes Lingui message catalogs as gettext PO files with ICU plurals, the default catalog format",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "license": "MIT",
@@ -42,8 +42,8 @@
42
42
  "dist/"
43
43
  ],
44
44
  "dependencies": {
45
- "@lingui/conf": "6.6.0",
46
- "@lingui/message-utils": "6.6.0",
45
+ "@lingui/conf": "6.8.0",
46
+ "@lingui/message-utils": "6.8.0",
47
47
  "pofile-ts": "^4.0.3"
48
48
  },
49
49
  "devDependencies": {
@@ -54,5 +54,5 @@
54
54
  "unbuild": {
55
55
  "declaration": "node16"
56
56
  },
57
- "gitHead": "665a19815378dedd89346bb7707bdb0e28df79e7"
57
+ "gitHead": "8c0f1ba378076cc49b320cccd2e06c60afe94963"
58
58
  }