@lingui/format-po-gettext 4.13.0 → 4.14.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.
@@ -6,13 +6,72 @@ const PO = require('pofile');
6
6
  const gettextPlurals = require('node-gettext/lib/plurals');
7
7
  const generateMessageId = require('@lingui/message-utils/generateMessageId');
8
8
  const formatPo = require('@lingui/format-po');
9
+ const cardinals = require('cldr-core/supplemental/plurals.json');
9
10
 
10
11
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
11
12
 
12
13
  const pluralsCldr__default = /*#__PURE__*/_interopDefaultCompat(pluralsCldr);
13
14
  const PO__default = /*#__PURE__*/_interopDefaultCompat(PO);
14
15
  const gettextPlurals__default = /*#__PURE__*/_interopDefaultCompat(gettextPlurals);
16
+ const cardinals__default = /*#__PURE__*/_interopDefaultCompat(cardinals);
15
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
+ let [start, end] = value.split("~");
28
+ const decimals = (start.split(".")[1] || "").length;
29
+ let mult = Math.pow(10, decimals);
30
+ const startNum = Number(start);
31
+ const endNum = Number(end);
32
+ let range = Array(Math.ceil(endNum * mult - startNum * mult + 1)).fill(0).map((v, idx) => (idx + startNum * mult) / mult);
33
+ let 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
+ let result = {};
54
+ Object.keys(rules).forEach((form) => {
55
+ let 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
+ let rules = renameKeys(ruleset);
68
+ pluralRules[loc.toLowerCase()] = createLocaleTest(rules);
69
+ }
70
+ );
71
+ return pluralRules;
72
+ }
73
+
74
+ const cldrSamples = getCldrPluralSamples();
16
75
  function stringifyICUCase(icuCase) {
17
76
  return icuCase.tokens.map((token) => {
18
77
  if (token.type === "content") {
@@ -90,13 +149,52 @@ function serializePlurals(item, message, id, isGeneratedId, options) {
90
149
  }
91
150
  return item;
92
151
  }
93
- const getPluralCases = (lang) => {
152
+ const getPluralCases = (lang, pluralFormsHeader) => {
153
+ let gettextPluralsInfo;
154
+ if (pluralFormsHeader) {
155
+ gettextPluralsInfo = parsePluralFormsFn(pluralFormsHeader);
156
+ }
94
157
  const [correctLang] = lang.split(/[-_]/g);
95
- const gettextPluralsInfo = gettextPlurals__default[correctLang];
96
- return gettextPluralsInfo?.examples.map(
97
- (pluralCase) => pluralsCldr__default(correctLang, pluralCase.sample)
98
- );
158
+ if (!gettextPluralsInfo) {
159
+ gettextPluralsInfo = gettextPlurals__default[correctLang];
160
+ }
161
+ if (!gettextPluralsInfo) {
162
+ if (lang !== "pseudo") {
163
+ console.warn(
164
+ `No plural rules found for language "${lang}". Please add a Plural-Forms header.`
165
+ );
166
+ }
167
+ return void 0;
168
+ }
169
+ const cases = [...Array(pluralsCldr__default.forms(correctLang).length)];
170
+ for (let form of pluralsCldr__default.forms(correctLang)) {
171
+ const samples = cldrSamples[correctLang][form];
172
+ const pluralForm = Number(
173
+ gettextPluralsInfo.pluralsFunc(Number(samples[0]))
174
+ );
175
+ cases[pluralForm] = form;
176
+ }
177
+ return cases;
99
178
  };
179
+ function parsePluralFormsFn(pluralFormsHeader) {
180
+ const [npluralsExpr, expr] = pluralFormsHeader.split(";");
181
+ try {
182
+ const nplurals = new Function(npluralsExpr + "; return nplurals;")();
183
+ const pluralsFunc = new Function(
184
+ "n",
185
+ expr + "; return plural;"
186
+ );
187
+ return {
188
+ nplurals,
189
+ pluralsFunc
190
+ };
191
+ } catch (e) {
192
+ console.warn(
193
+ `Plural-Forms header has incorrect value: ${pluralFormsHeader}`
194
+ );
195
+ return void 0;
196
+ }
197
+ }
100
198
  const convertPluralsToICU = (item, pluralForms, lang, ctxPrefix = DEFAULT_CTX_PREFIX) => {
101
199
  const translationCount = item.msgstr.length;
102
200
  const messageKey = item.msgid;
@@ -138,7 +236,9 @@ const convertPluralsToICU = (item, pluralForms, lang, ctxPrefix = DEFAULT_CTX_PR
138
236
  messageKey
139
237
  );
140
238
  }
141
- const pluralClauses = item.msgstr.map((str, index) => pluralForms[index] + " {" + str + "}").join(" ");
239
+ const pluralClauses = item.msgstr.map(
240
+ (str, index) => pluralForms[index] ? pluralForms[index] + " {" + str + "}" : ""
241
+ ).join(" ");
142
242
  let pluralizeOn = ctx.get("pluralize_on");
143
243
  if (!pluralizeOn) {
144
244
  console.warn(
@@ -161,7 +261,10 @@ function formatter(options = {}) {
161
261
  templateExtension: ".pot",
162
262
  parse(content, ctx) {
163
263
  const po = PO__default.parse(content);
164
- let pluralForms = getPluralCases(po.headers.Language);
264
+ let pluralForms = getPluralCases(
265
+ po.headers.Language,
266
+ po.headers["Plural-Forms"]
267
+ );
165
268
  po.items.forEach((item) => {
166
269
  convertPluralsToICU(
167
270
  item,
@@ -4,7 +4,65 @@ import PO from 'pofile';
4
4
  import gettextPlurals from 'node-gettext/lib/plurals';
5
5
  import { generateMessageId } from '@lingui/message-utils/generateMessageId';
6
6
  import { formatter as formatter$1 } from '@lingui/format-po';
7
+ import cardinals from 'cldr-core/supplemental/plurals.json';
7
8
 
9
+ function renameKeys(rules) {
10
+ const result = {};
11
+ Object.keys(rules).forEach((k) => {
12
+ const newKey = k.match(/[^-]+$/)[0];
13
+ result[newKey] = rules[k];
14
+ });
15
+ return result;
16
+ }
17
+ function fillRange(value) {
18
+ let [start, end] = value.split("~");
19
+ const decimals = (start.split(".")[1] || "").length;
20
+ let mult = Math.pow(10, decimals);
21
+ const startNum = Number(start);
22
+ const endNum = Number(end);
23
+ let range = Array(Math.ceil(endNum * mult - startNum * mult + 1)).fill(0).map((v, idx) => (idx + startNum * mult) / mult);
24
+ let last = range[range.length - 1];
25
+ if (endNum !== last) {
26
+ throw new Error(`Range create error for ${value}: last value is ${last}`);
27
+ }
28
+ return range.map((v) => Number(v));
29
+ }
30
+ function createSamples(src) {
31
+ let result = [];
32
+ src.replace(/…/, "").trim().replace(/,$/, "").split(",").map(function(val) {
33
+ return val.trim();
34
+ }).forEach((val) => {
35
+ if (val.indexOf("~") !== -1) {
36
+ result = result.concat(fillRange(val));
37
+ } else {
38
+ result.push(Number(val));
39
+ }
40
+ });
41
+ return result;
42
+ }
43
+ function createLocaleTest(rules) {
44
+ let result = {};
45
+ Object.keys(rules).forEach((form) => {
46
+ let samples = rules[form].split(/@integer|@decimal/).slice(1);
47
+ result[form] = [];
48
+ samples.forEach((sample) => {
49
+ result[form] = result[form].concat(createSamples(sample));
50
+ });
51
+ });
52
+ return result;
53
+ }
54
+ function getCldrPluralSamples() {
55
+ const pluralRules = {};
56
+ Object.entries(cardinals.supplemental["plurals-type-cardinal"]).forEach(
57
+ ([loc, ruleset]) => {
58
+ let rules = renameKeys(ruleset);
59
+ pluralRules[loc.toLowerCase()] = createLocaleTest(rules);
60
+ }
61
+ );
62
+ return pluralRules;
63
+ }
64
+
65
+ const cldrSamples = getCldrPluralSamples();
8
66
  function stringifyICUCase(icuCase) {
9
67
  return icuCase.tokens.map((token) => {
10
68
  if (token.type === "content") {
@@ -82,13 +140,52 @@ function serializePlurals(item, message, id, isGeneratedId, options) {
82
140
  }
83
141
  return item;
84
142
  }
85
- const getPluralCases = (lang) => {
143
+ const getPluralCases = (lang, pluralFormsHeader) => {
144
+ let gettextPluralsInfo;
145
+ if (pluralFormsHeader) {
146
+ gettextPluralsInfo = parsePluralFormsFn(pluralFormsHeader);
147
+ }
86
148
  const [correctLang] = lang.split(/[-_]/g);
87
- const gettextPluralsInfo = gettextPlurals[correctLang];
88
- return gettextPluralsInfo?.examples.map(
89
- (pluralCase) => pluralsCldr(correctLang, pluralCase.sample)
90
- );
149
+ if (!gettextPluralsInfo) {
150
+ gettextPluralsInfo = gettextPlurals[correctLang];
151
+ }
152
+ if (!gettextPluralsInfo) {
153
+ if (lang !== "pseudo") {
154
+ console.warn(
155
+ `No plural rules found for language "${lang}". Please add a Plural-Forms header.`
156
+ );
157
+ }
158
+ return void 0;
159
+ }
160
+ const cases = [...Array(pluralsCldr.forms(correctLang).length)];
161
+ for (let form of pluralsCldr.forms(correctLang)) {
162
+ const samples = cldrSamples[correctLang][form];
163
+ const pluralForm = Number(
164
+ gettextPluralsInfo.pluralsFunc(Number(samples[0]))
165
+ );
166
+ cases[pluralForm] = form;
167
+ }
168
+ return cases;
91
169
  };
170
+ function parsePluralFormsFn(pluralFormsHeader) {
171
+ const [npluralsExpr, expr] = pluralFormsHeader.split(";");
172
+ try {
173
+ const nplurals = new Function(npluralsExpr + "; return nplurals;")();
174
+ const pluralsFunc = new Function(
175
+ "n",
176
+ expr + "; return plural;"
177
+ );
178
+ return {
179
+ nplurals,
180
+ pluralsFunc
181
+ };
182
+ } catch (e) {
183
+ console.warn(
184
+ `Plural-Forms header has incorrect value: ${pluralFormsHeader}`
185
+ );
186
+ return void 0;
187
+ }
188
+ }
92
189
  const convertPluralsToICU = (item, pluralForms, lang, ctxPrefix = DEFAULT_CTX_PREFIX) => {
93
190
  const translationCount = item.msgstr.length;
94
191
  const messageKey = item.msgid;
@@ -130,7 +227,9 @@ const convertPluralsToICU = (item, pluralForms, lang, ctxPrefix = DEFAULT_CTX_PR
130
227
  messageKey
131
228
  );
132
229
  }
133
- const pluralClauses = item.msgstr.map((str, index) => pluralForms[index] + " {" + str + "}").join(" ");
230
+ const pluralClauses = item.msgstr.map(
231
+ (str, index) => pluralForms[index] ? pluralForms[index] + " {" + str + "}" : ""
232
+ ).join(" ");
134
233
  let pluralizeOn = ctx.get("pluralize_on");
135
234
  if (!pluralizeOn) {
136
235
  console.warn(
@@ -153,7 +252,10 @@ function formatter(options = {}) {
153
252
  templateExtension: ".pot",
154
253
  parse(content, ctx) {
155
254
  const po = PO.parse(content);
156
- let pluralForms = getPluralCases(po.headers.Language);
255
+ let pluralForms = getPluralCases(
256
+ po.headers.Language,
257
+ po.headers["Plural-Forms"]
258
+ );
157
259
  po.items.forEach((item) => {
158
260
  convertPluralsToICU(
159
261
  item,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingui/format-po-gettext",
3
- "version": "4.13.0",
3
+ "version": "4.14.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",
@@ -41,10 +41,11 @@
41
41
  "dist/"
42
42
  ],
43
43
  "dependencies": {
44
- "@lingui/conf": "4.13.0",
45
- "@lingui/format-po": "4.13.0",
46
- "@lingui/message-utils": "4.13.0",
44
+ "@lingui/conf": "4.14.0",
45
+ "@lingui/format-po": "4.14.0",
46
+ "@lingui/message-utils": "4.14.0",
47
47
  "@messageformat/parser": "^5.0.0",
48
+ "cldr-core": "^45.0.0",
48
49
  "node-gettext": "^3.0.0",
49
50
  "plurals-cldr": "^2.0.1",
50
51
  "pofile": "^1.1.4"
@@ -55,5 +56,5 @@
55
56
  "tsd": "^0.28.0",
56
57
  "unbuild": "2.0.0"
57
58
  },
58
- "gitHead": "263faa5b8cbe07ea1bbe4f5902794f29b773984d"
59
+ "gitHead": "8dcb749688e2f452f85fc0eaad4d033a44975b42"
59
60
  }