@transifex/native 3.2.2 → 4.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transifex/native",
3
- "version": "3.2.2",
3
+ "version": "4.1.0",
4
4
  "description": "i18n framework using Transifex Native",
5
5
  "keywords": [
6
6
  "transifex",
@@ -31,31 +31,30 @@
31
31
  "url": "https://github.com/transifex/transifex-javascript/issues"
32
32
  },
33
33
  "engines": {
34
- "node": ">=12.0.0"
34
+ "node": ">=14.0.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@babel/core": "^7.17.8",
38
- "@babel/plugin-transform-runtime": "^7.14.5",
39
- "@babel/preset-env": "^7.14.5",
40
- "@babel/runtime": "^7.17.8",
37
+ "@babel/core": "^7.17.10",
38
+ "@babel/plugin-transform-runtime": "^7.17.10",
39
+ "@babel/preset-env": "^7.17.10",
40
+ "@babel/runtime": "^7.17.9",
41
41
  "babel-eslint": "^10.1.0",
42
- "babel-loader": "^8.1.0",
42
+ "babel-loader": "^8.2.5",
43
43
  "chai": "^4.3.4",
44
44
  "eslint": "^7.28.0",
45
45
  "eslint-config-airbnb-base": "^14.2.0",
46
- "eslint-loader": "^4.0.2",
47
- "eslint-plugin-import": "^2.23.4",
48
- "glob": "^7.1.7",
49
- "mocha": "^9.1.3",
46
+ "eslint-plugin-import": "^2.26.0",
47
+ "glob": "^8.0.1",
48
+ "mocha": "^10.0.0",
50
49
  "nock": "^13.1.0",
51
50
  "nyc": "^15.1.0",
52
51
  "source-map-support": "^0.5.19",
53
- "webpack": "^4.46.0",
54
- "webpack-cli": "^3.3.12"
52
+ "webpack": "^5.72.0",
53
+ "webpack-cli": "^4.9.2"
55
54
  },
56
55
  "dependencies": {
57
56
  "@messageformat/core": "^3.0.0",
58
- "axios": "^0.26.1",
57
+ "axios": "^0.27.2",
59
58
  "md5": "^2.3.0"
60
59
  }
61
60
  }
package/src/TxNative.js CHANGED
@@ -7,7 +7,7 @@ import SourceErrorPolicy from './policies/SourceErrorPolicy';
7
7
  import SourceStringPolicy from './policies/SourceStringPolicy';
8
8
  import MessageFormatRenderer from './renderers/MessageFormatRenderer';
9
9
  import {
10
- generateKey, isString, isPluralized, escape, generateHashedKey, sleep,
10
+ generateKey, isString, escape, generateHashedKey, sleep,
11
11
  } from './utils';
12
12
  import {
13
13
  sendEvent,
@@ -15,6 +15,7 @@ import {
15
15
  FETCHING_LOCALES, LOCALES_FETCHED, LOCALES_FETCH_FAILED,
16
16
  LOCALE_CHANGED,
17
17
  } from './events';
18
+ import { isPluralized } from './plurals';
18
19
 
19
20
  /**
20
21
  * Native instance, combines functionality from
package/src/index.d.ts CHANGED
@@ -26,5 +26,7 @@ declare module '@transifex/native' {
26
26
  export const generateHashedKey: any;
27
27
  export const generateKey: any;
28
28
  export const isPluralized: any;
29
+ export const explodePlurals: any;
30
+ export const implodePlurals: any;
29
31
  export const normalizeLocale: any;
30
32
  }
package/src/index.js CHANGED
@@ -17,6 +17,7 @@ function _createNativeInstance(initOptions) {
17
17
  }
18
18
 
19
19
  export * from './utils';
20
+ export * from './plurals';
20
21
  export * from './events';
21
22
 
22
23
  export const PseudoTranslationPolicy = _PseudoTranslationPolicy;
package/src/plurals.js ADDED
@@ -0,0 +1,204 @@
1
+ const PLURAL_RULES = ['zero', 'one', 'two', 'few', 'many', 'other'];
2
+
3
+ // The `_consumeFOO` functions take an input, "consume" a part of it to
4
+ // produce the first return value and return the "unconsumed" part of the input
5
+ // as the second return value
6
+
7
+ // '{cnt, plural, one {ONE} other {OTHER}}' =>
8
+ // ['cnt', 'one {ONE} other {OTHER}']
9
+ function consumePreamble(string) {
10
+ if (!(string.length > 1
11
+ && string[0] === '{'
12
+ && string[string.length - 1] === '}')
13
+ ) {
14
+ return [null, null];
15
+ }
16
+
17
+ // {cnt, plural, one {foo} other {foos}}
18
+ // ^^^^^^^^^^^
19
+ const firstCommaPos = string.indexOf(',');
20
+ if (firstCommaPos === -1) { return [null, null]; }
21
+ const secondCommaPos = string.indexOf(',', firstCommaPos + 1);
22
+ if (secondCommaPos === -1) { return [null, null]; }
23
+
24
+ const varName = string.substring(1, firstCommaPos).trim();
25
+ const keyword = string
26
+ .substring(firstCommaPos + 1, secondCommaPos)
27
+ .trim();
28
+
29
+ // Make sure `varName` is a proper variable and that `keyword` is 'plural'
30
+ if (!/^[\w_]+$/.test(varName) || keyword !== 'plural') {
31
+ return [null, null];
32
+ }
33
+
34
+ return [
35
+ varName,
36
+ string.substring(secondCommaPos + 1, string.length - 1).trim()];
37
+ }
38
+
39
+ // 'one {ONE} other {OTHER}' => ['one', '{ONE} other {OTHER}']
40
+ // 'other {OTHER}' => ['other', '{OTHER}']
41
+ function consumeRule(string) {
42
+ // {cnt, plural, one {foo} other {foos}}
43
+ // ^^^^^
44
+ const leftBracketPos = string.indexOf('{');
45
+ if (leftBracketPos === -1) { return [null, null]; }
46
+ let rule = string.substring(0, leftBracketPos).trim();
47
+ if (rule[0] === '=') {
48
+ rule = rule.substring(1);
49
+ if (!Number.isNaN(parseInt(rule, 10)) && parseInt(rule, 10) === parseFloat(rule)) {
50
+ rule = parseInt(rule, 10);
51
+ rule = PLURAL_RULES[rule];
52
+ if (rule === undefined) { return [null, null]; }
53
+ } else { return [null, null]; }
54
+ } else if (PLURAL_RULES.indexOf(rule) === -1) {
55
+ return [null, null];
56
+ }
57
+ return [rule, string.substring(leftBracketPos)];
58
+ }
59
+
60
+ // '{ONE} other {OTHER}' => ['ONE', 'other {OTHER}']
61
+ // '{OTHER}' => ['OTHER', '']
62
+ function consumePlural(string) {
63
+ // {cnt, plural, one {foo} other {foos}}
64
+ // ^^^^^
65
+ let [bracketCount, escaping] = [0, false];
66
+ let ptr = 0;
67
+ while (ptr < string.length) {
68
+ const char = string[ptr];
69
+ if (char === "'") {
70
+ const peek = string[ptr + 1];
71
+ if (peek === "'") {
72
+ ptr += 1;
73
+ } else if (escaping) {
74
+ escaping = false;
75
+ } else if (peek === '{' || peek === '}') {
76
+ escaping = true;
77
+ }
78
+ } else if (char === '{') {
79
+ if (!escaping) {
80
+ bracketCount += 1;
81
+ }
82
+ } else if (char === '}') {
83
+ if (!escaping) {
84
+ bracketCount -= 1;
85
+ }
86
+ if (bracketCount === 0) {
87
+ return [string.substring(1, ptr), string.substring(ptr + 1).trim()];
88
+ }
89
+ }
90
+ ptr += 1;
91
+ }
92
+ return [null, null];
93
+ }
94
+
95
+ /* Make an effort to split (explode) an ICU message format string into
96
+ * plurals. Works on only the simplest case, when a gettext-like statement
97
+ * like:
98
+ *
99
+ * ngettext("You have %d new message", "You have %d new messages", cnt)
100
+ *
101
+ * was converted into an ICU message like:
102
+ *
103
+ * {cnt, plural,
104
+ * one {You have # new message}
105
+ * other {You have # new messages}}
106
+ *
107
+ * The return value is a size-2 array with the name of the variable used and
108
+ * an object mapping the plural rule to the plural version, eg:
109
+ *
110
+ * ['cnt',
111
+ * {one: 'You have # new message',
112
+ * other: 'You have # new messages'}]
113
+ *
114
+ * If the string can't be converted for any reason, `null` will be used in
115
+ * place of the variable name and only rule=5 (other) will be returned.
116
+ *
117
+ * @function
118
+ * @param {String} string
119
+ * @returns {[String, {rule: String}]} variable name and plural forms pair
120
+ *
121
+ * */
122
+ export function explodePlurals(string) {
123
+ const defaultResult = [null, { other: string }];
124
+
125
+ // {cnt, plural, one {foo} other {foos}}
126
+ // ^^^^^^^^^^^^
127
+ // eslint-disable-next-line prefer-const
128
+ let [varName, remaining] = consumePreamble(string);
129
+ if (varName == null) { return defaultResult; }
130
+
131
+ // {cnt, plural, one {foo} other {foos}}
132
+ // ^^^^^^^^^
133
+ const plurals = {};
134
+ let rule;
135
+ let plural;
136
+ [rule, remaining] = consumeRule(remaining);
137
+ if (rule == null) { return defaultResult; }
138
+
139
+ [plural, remaining] = consumePlural(remaining.trim());
140
+ if (plural == null) { return defaultResult; }
141
+ plurals[rule] = plural;
142
+
143
+ while (remaining.trim()) {
144
+ // {cnt, plural, one {foo} other {foos}}
145
+ // ^^^^^^^^^^^^
146
+ [rule, remaining] = consumeRule(remaining.trim());
147
+ if (rule == null) { return defaultResult; }
148
+ [plural, remaining] = consumePlural(remaining.trim());
149
+ if (plural == null) { return defaultResult; }
150
+ plurals[rule] = plural;
151
+ }
152
+
153
+ if ((Object.keys(plurals).length === 1 && !('other' in plurals))
154
+ || (!('one' in plurals && 'other' in plurals))
155
+ ) {
156
+ return defaultResult;
157
+ }
158
+
159
+ return [varName, plurals];
160
+ }
161
+
162
+ /* Convert a plurals object into an ICU plural statement, eg:
163
+ *
164
+ * {one: "ONE", other: "OTHER"} => '{???, plural, one {ONE} other {OTHER}}'
165
+ *
166
+ * @function
167
+ * @param {Object} plurals
168
+ * @param {string} [count='???']
169
+ * @returns {String}
170
+ */
171
+ export function implodePlurals(plurals, count = '???') {
172
+ const result = [`{${count}, plural,`];
173
+ // Lets get the rules in order
174
+ // eslint-disable-next-line no-restricted-syntax
175
+ for (const rule of PLURAL_RULES) {
176
+ if (rule in plurals) {
177
+ const plural = plurals[rule];
178
+ result.push(` ${rule} {${plural}}`);
179
+ }
180
+ }
181
+ result.push('}');
182
+ return result.join('');
183
+ }
184
+
185
+ /**
186
+ * Determine if a string is an ICU pluralized string. Only works for the
187
+ * simplest possible case, where the string starts and ends in '{' and '}'
188
+ * respectively and the root ICU directive is a plural statement. ie This
189
+ *
190
+ * {cnt, plural, one {ONE} other {OTHER}}
191
+ *
192
+ * will return 'true' while this
193
+ *
194
+ * hello {cnt, plural, one {ONE} other {OTHER}}
195
+ *
196
+ * will return 'false'.
197
+ *
198
+ * @param {String} string
199
+ * @returns {Boolean}
200
+ */
201
+ export function isPluralized(string) {
202
+ const plurals = explodePlurals(string)[1];
203
+ return Object.keys(plurals).length > 1;
204
+ }
package/src/utils.js CHANGED
@@ -84,138 +84,6 @@ export function normalizeLocale(locale) {
84
84
  return normalizedLocale;
85
85
  }
86
86
 
87
- const PLURAL_RULES = ['zero', 'one', 'two', 'few', 'many', 'other'];
88
-
89
- function _consumePreamble(string) {
90
- if (!(string.length > 1
91
- && string[0] === '{'
92
- && string[string.length - 1] === '}')
93
- ) {
94
- return [null, null];
95
- }
96
-
97
- // {cnt, plural, one {foo} other {foos}}
98
- // ^^^^^^^^^^^
99
- const firstCommaPos = string.indexOf(',');
100
- if (firstCommaPos === -1) { return [null, null]; }
101
- const secondCommaPos = string.indexOf(',', firstCommaPos + 1);
102
- if (secondCommaPos === -1) { return [null, null]; }
103
-
104
- const varName = string.substring(1, firstCommaPos).trim();
105
- const keyword = string
106
- .substring(firstCommaPos + 1, secondCommaPos)
107
- .trim();
108
-
109
- // Make sure `keyword` is 'plural'
110
- if (keyword !== 'plural') { return [null, null]; }
111
-
112
- return [
113
- varName,
114
- string.substring(secondCommaPos + 1, string.length - 1).trim()];
115
- }
116
-
117
- function _consumeRule(string) {
118
- // {cnt, plural, one {foo} other {foos}}
119
- // ^^^^^
120
- const leftBracketPos = string.indexOf('{');
121
- if (leftBracketPos === -1) { return [null, null]; }
122
- let rule = string.substring(0, leftBracketPos).trim();
123
- if (rule[0] === '=') {
124
- rule = rule.substring(1);
125
- if (!Number.isNaN(parseInt(rule, 10)) && parseInt(rule, 10) === parseFloat(rule)) {
126
- rule = parseInt(rule, 10);
127
- rule = PLURAL_RULES[rule];
128
- if (rule === undefined) { return [null, null]; }
129
- } else { return [null, null]; }
130
- } else if (PLURAL_RULES.indexOf(rule) === -1) {
131
- return [null, null];
132
- }
133
- return [rule, string.substring(leftBracketPos)];
134
- }
135
-
136
- function _consumePlural(string) {
137
- // {cnt, plural, one {foo} other {foos}}
138
- // ^^^^^
139
- let [bracketCount, escaping] = [0, false];
140
- let ptr = 0;
141
- while (ptr < string.length) {
142
- const char = string[ptr];
143
- if (char === "'") {
144
- const peek = string[ptr + 1];
145
- if (peek === "'") {
146
- ptr += 1;
147
- } else if (escaping) {
148
- escaping = false;
149
- } else if (peek === '{' || peek === '}') {
150
- escaping = true;
151
- }
152
- } else if (char === '{') {
153
- if (!escaping) {
154
- bracketCount += 1;
155
- }
156
- } else if (char === '}') {
157
- if (!escaping) {
158
- bracketCount -= 1;
159
- }
160
- if (bracketCount === 0) {
161
- return [string.substring(1, ptr), string.substring(ptr + 1).trim()];
162
- }
163
- }
164
- ptr += 1;
165
- }
166
- return [null, null];
167
- }
168
-
169
- /**
170
- * Determine if a string is an ICU pluralized string. Only works for the
171
- * simplest possible case, where the string starts and ends in '{' and '}'
172
- * respectively and the root ICU directive is a plural statement. ie This
173
- *
174
- * {cnt, plural, one {ONE} other {OTHER}}
175
- *
176
- * will return 'true' while this
177
- *
178
- * hello {cnt, plural, one {ONE} other {OTHER}}
179
- *
180
- * will return 'false'.
181
- *
182
- * @param {String} string
183
- * @returns {Boolean}
184
- */
185
- export function isPluralized(string) {
186
- const [varName, rest] = _consumePreamble(string);
187
- let remaining = rest;
188
- if (varName == null) { return false; }
189
-
190
- const plurals = {};
191
- let rule;
192
- let plural;
193
- [rule, remaining] = _consumeRule(remaining);
194
- if (rule == null) { return false; }
195
-
196
- [plural, remaining] = _consumePlural(remaining.trim());
197
- if (plural == null) { return false; }
198
- plurals[rule] = plural;
199
-
200
- while (remaining.trim()) {
201
- // {cnt, plural, one {foo} other {foos}}
202
- // ^^^^^^^^^^^^
203
- [rule, remaining] = _consumeRule(remaining.trim());
204
- if (rule == null) { return false; }
205
- [plural, remaining] = _consumePlural(remaining.trim());
206
- if (plural == null) { return false; }
207
- plurals[rule] = plural;
208
- }
209
-
210
- if ((Object.keys(plurals).length === 1 && !('other' in plurals))
211
- || (!('one' in plurals && 'other' in plurals))
212
- ) {
213
- return false;
214
- }
215
-
216
- return true;
217
- }
218
-
219
87
  /**
220
88
  * Sleep in msec
221
89
  *
@@ -21,22 +21,6 @@ module.exports = {
21
21
  ],
22
22
  module: {
23
23
  rules: [
24
- {
25
- test: /\.(js|jsx)$/,
26
- enforce: 'pre',
27
- use: [
28
- {
29
- options: {
30
- cache: true,
31
- configFile: '.eslintrc.json',
32
- formatter: 'eslint-formatter-pretty',
33
- emitWarning: true,
34
- failOnError: true,
35
- },
36
- loader: 'eslint-loader',
37
- },
38
- ],
39
- },
40
24
  {
41
25
  test: /\.(js|jsx)$/,
42
26
  exclude: /(node_modules)/,
package/webpack.node.js CHANGED
@@ -20,23 +20,6 @@ module.exports = {
20
20
  }),
21
21
  ],
22
22
  module: {
23
- rules: [
24
- {
25
- test: /\.(js|jsx)$/,
26
- enforce: 'pre',
27
- use: [
28
- {
29
- options: {
30
- cache: true,
31
- configFile: '.eslintrc.json',
32
- formatter: 'eslint-formatter-pretty',
33
- emitWarning: true,
34
- failOnError: true,
35
- },
36
- loader: 'eslint-loader',
37
- },
38
- ],
39
- },
40
- ],
23
+ rules: [],
41
24
  },
42
25
  };