locize-cli 7.8.1 → 7.9.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/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
5
5
  Project versioning adheres to [Semantic Versioning](http://semver.org/).
6
6
  Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
7
7
 
8
+ ## [7.9.1](https://github.com/locize/locize-cli/compare/v7.9.0...v7.9.1) - 2022-03-02
9
+
10
+ - xliff: fix combined plural keys
11
+
12
+
13
+ ## [7.9.0](https://github.com/locize/locize-cli/compare/v7.8.2...v7.9.0) - 2022-03-01
14
+
15
+ - xliff: detect i18n format and merge pluralforms if necessary
16
+
17
+
18
+ ## [7.8.2](https://github.com/locize/locize-cli/compare/v7.8.1...v7.8.2) - 2022-02-28
19
+
20
+ - update dependencies
21
+
22
+
8
23
  ## [7.8.1](https://github.com/locize/locize-cli/compare/v7.8.0...v7.8.1) - 2022-02-10
9
24
 
10
25
  - sync: optimize handling if pathmask does not include namespace
@@ -0,0 +1,154 @@
1
+ const jsyaml = require('js-yaml');
2
+
3
+ const delimiter = {
4
+ i18next: '_',
5
+ i18njs: '.'
6
+ };
7
+
8
+ const detectFormat = (keys) => {
9
+ const i18nextMatches = keys.filter((k) => k.indexOf(delimiter.i18next) > 0).length;
10
+ const i18njsMatches = keys.filter((k) => k.indexOf(delimiter.i18njs) > 0).length;
11
+ if (i18nextMatches > i18njsMatches) {
12
+ return 'i18next';
13
+ }
14
+ if (i18nextMatches < i18njsMatches) {
15
+ return 'i18njs';
16
+ }
17
+ };
18
+
19
+ const getBaseKey = (delimiter) => (k) => {
20
+ const parts = k.split(delimiter);
21
+ parts.pop();
22
+ const baseKey = parts.join(delimiter);
23
+ return baseKey;
24
+ };
25
+
26
+ const uniq = (value, index, self) => self.indexOf(value) === index;
27
+
28
+ const stringify = (o) => {
29
+ let str = jsyaml.dump(o);
30
+ const subKeys = Object.keys(o);
31
+ subKeys.forEach((sk) => {
32
+ if (isNaN(sk)) {
33
+ str = str.replace(new RegExp(`^(?:${sk}: )+`, 'm'), `{${sk}}: `);
34
+ } else {
35
+ str = str.replace(new RegExp(`^(?:'${sk}': )+`, 'm'), `{${sk}}: `);
36
+ }
37
+ });
38
+ return str;
39
+ };
40
+
41
+ const transformKeys = (segments, baseKeys, toMerge, deli) => {
42
+ baseKeys.forEach((bk) => {
43
+ const asObj = toMerge[bk].reduce((mem, k) => {
44
+ const subKey = k.substring((bk + deli).length);
45
+ // special handling for i18next v3
46
+ if (deli === delimiter.i18next && subKey === 'plural' && segments[bk]) {
47
+ mem['__'] = segments[bk];
48
+ delete segments[bk];
49
+ }
50
+ mem[subKey] = segments[k];
51
+ return mem;
52
+ }, {});
53
+ if (Object.keys(asObj).length > 0) {
54
+ const value = stringify(asObj);
55
+ segments[`${bk}__#locize.com/combinedSubkey`] = value;
56
+ toMerge[bk].forEach((k) => {
57
+ delete segments[k];
58
+ });
59
+ }
60
+ });
61
+ return segments;
62
+ };
63
+
64
+ // CLDR
65
+ const pluralForms = [
66
+ 'zero',
67
+ 'one',
68
+ 'two',
69
+ 'few',
70
+ 'many',
71
+ 'other'
72
+ ];
73
+
74
+ const endsWithPluralForm = (k) => !!pluralForms.find((f) => k.endsWith(`.${f}`)) || !!pluralForms.find((f) => k.endsWith(`_${f}`)) || /_\d+$/.test(k) || k.endsWith('_plural');
75
+
76
+ const prepareExport = (refRes, trgRes) => {
77
+ const refLngKeys = Object.keys(refRes);
78
+ const trgLngKeys = Object.keys(trgRes);
79
+
80
+ const nonMatchInRef = refLngKeys.filter((k) => trgLngKeys.indexOf(k) < 0 && endsWithPluralForm(k));
81
+ const nonMatchInTrg = trgLngKeys.filter((k) => refLngKeys.indexOf(k) < 0 && endsWithPluralForm(k));
82
+
83
+ const allMatches = nonMatchInRef.concat(nonMatchInTrg);
84
+
85
+ const format = detectFormat(allMatches);
86
+ if (!format) return { ref: refRes, trg: trgRes };
87
+
88
+ const nonMatchBaseKeysInRef = nonMatchInRef.map(getBaseKey(delimiter[format])).filter(uniq);
89
+ const nonMatchBaseKeysInTrg = nonMatchInTrg.map(getBaseKey(delimiter[format])).filter(uniq);
90
+ const nonMatchBaseKeys = nonMatchBaseKeysInRef.concat(nonMatchBaseKeysInTrg).filter(uniq);
91
+
92
+ const toMergeInRef = nonMatchBaseKeys.reduce((mem, bk) => {
93
+ mem[bk] = refLngKeys.filter((k) => k.indexOf(bk + delimiter[format]) === 0);
94
+ return mem;
95
+ }, {});
96
+ const toMergeInTrg = nonMatchBaseKeys.reduce((mem, bk) => {
97
+ mem[bk] = trgLngKeys.filter((k) => k.indexOf(bk + delimiter[format]) === 0);
98
+ return mem;
99
+ }, {});
100
+
101
+ let falseFlags = nonMatchBaseKeysInRef.filter((k) => toMergeInRef[k].length < 2 && (!toMergeInTrg[k] || toMergeInTrg[k].length < 2));
102
+ falseFlags = falseFlags.concat(nonMatchBaseKeysInTrg.filter((k) => toMergeInTrg[k].length < 2 && (!toMergeInRef[k] || toMergeInRef[k].length < 2)));
103
+ falseFlags.forEach((k) => {
104
+ delete toMergeInRef[k];
105
+ delete toMergeInTrg[k];
106
+ nonMatchBaseKeys.splice(nonMatchBaseKeys.indexOf(k), 1);
107
+ });
108
+
109
+ const transformedRef = transformKeys(refRes, nonMatchBaseKeys, toMergeInRef, delimiter[format]);
110
+ const transformedTrg = transformKeys(trgRes, nonMatchBaseKeys, toMergeInTrg, delimiter[format]);
111
+ return { ref: transformedRef, trg: transformedTrg };
112
+ };
113
+
114
+ const skRegex = new RegExp('^(?:{(.+)})+', 'gm');
115
+ const parse = (s) => {
116
+ let matchArray;
117
+ while ((matchArray = skRegex.exec(s)) !== null) {
118
+ const [match, sk] = matchArray;
119
+ if (isNaN(sk)) {
120
+ s = s.replace(new RegExp(`^(?:${match}: )+`, 'm'), `${sk}: `);
121
+ } else {
122
+ const escapedMatch = match.replace('{', '\\{').replace('}', '\\}');
123
+ s = s.replace(new RegExp(`^(?:${escapedMatch}: )+`, 'm'), `${sk}: `);
124
+ }
125
+ }
126
+ return jsyaml.load(s);
127
+ };
128
+
129
+ const prepareImport = (resources) => {
130
+ const keys = Object.keys(resources);
131
+ keys.forEach((k) => {
132
+ if (k.indexOf('__#locize.com/combinedSubkey') > -1) {
133
+ const baseKey = k.substring(0, k.indexOf('__#locize.com/combinedSubkey'));
134
+ if (resources[k]) {
135
+ const parsed = parse(resources[k]);
136
+ Object.keys(parsed).map((sk) => {
137
+ const skVal = parsed[sk];
138
+ resources[`${baseKey}_${sk}`] = skVal;
139
+ if (sk === '__') {
140
+ resources[baseKey] = resources[`${baseKey}_${sk}`];
141
+ delete resources[`${baseKey}_${sk}`];
142
+ }
143
+ });
144
+ delete resources[k];
145
+ }
146
+ }
147
+ });
148
+ return resources;
149
+ };
150
+
151
+ module.exports = {
152
+ prepareExport,
153
+ prepareImport
154
+ };
@@ -16,6 +16,7 @@ const unflatten = require('./unflatten');
16
16
  const getRemoteNamespace = require('./getRemoteNamespace');
17
17
  const removeUndefinedFromArrays = require('./removeUndefinedFromArrays');
18
18
  const shouldUnflatten = require('./shouldUnflatten');
19
+ const prepareCombinedExport = require('./combineSubkeyPreprocessor').prepareExport;
19
20
 
20
21
  const convertToDesiredFormat = (
21
22
  opt,
@@ -172,7 +173,8 @@ const convertToDesiredFormat = (
172
173
  opt.getNamespace(opt, opt.referenceLanguage, namespace, (err, refNs) => {
173
174
  if (err) return cb(err);
174
175
 
175
- fn(opt.referenceLanguage, lng, refNs, flatten(data), namespace, cb);
176
+ const prepared = prepareCombinedExport(refNs, flatten(data));
177
+ fn(opt.referenceLanguage, lng, prepared.ref, prepared.trg, namespace, cb);
176
178
  });
177
179
  return;
178
180
  }
@@ -14,6 +14,7 @@ const tmx2js = require('tmexchange/cjs/tmx2js');
14
14
  const laravel2js = require('laravelphp/cjs/laravel2js');
15
15
  const javaProperties = require('@js.properties/properties');
16
16
  const flatten = require('flat');
17
+ const prepareCombinedImport = require('./combineSubkeyPreprocessor').prepareImport;
17
18
 
18
19
  const convertToFlatFormat = (opt, data, lng, cb) => {
19
20
  if (!cb) {
@@ -171,34 +172,34 @@ const convertToFlatFormat = (opt, data, lng, cb) => {
171
172
  res.resources = res.resources || {};
172
173
  const ns = Object.keys(res.resources)[0];
173
174
  const orgRes = res.resources[ns] || res.resources;
174
- function checkForContext(nsRes) {
175
+ function checkForPostProcessing(nsRes) {
175
176
  Object.keys(nsRes).forEach((k) => {
176
177
  if (orgRes[k].note && (typeof nsRes[k] === 'string' || !nsRes[k])) {
177
178
  nsRes[k] = {
178
179
  value: nsRes[k],
179
180
  context: {
180
181
  text: orgRes[k].note,
181
- },
182
+ }
182
183
  };
183
184
  }
184
185
  });
185
- return nsRes;
186
+ return prepareCombinedImport(nsRes);
186
187
  }
187
188
  if (!res.targetLanguage) {
188
189
  sourceOfjs(res, (err, ret) => {
189
190
  if (err) return cb(err);
190
- cb(null, checkForContext(ret));
191
+ cb(null, checkForPostProcessing(ret));
191
192
  });
192
193
  } else {
193
194
  let ret = targetOfjs(res);
194
- if (lng !== opt.referenceLanguage) return cb(null, checkForContext(ret));
195
+ if (lng !== opt.referenceLanguage) return cb(null, checkForPostProcessing(ret));
195
196
  ret = ret || {};
196
197
  const keys = Object.keys(ret);
197
- if (keys.length === 0) return cb(null, checkForContext(ret));
198
+ if (keys.length === 0) return cb(null, checkForPostProcessing(ret));
198
199
  const allEmpty = keys.filter((k) => ret[k] !== '').length === 0;
199
- if (!allEmpty) return cb(null, checkForContext(ret));
200
+ if (!allEmpty) return cb(null, checkForPostProcessing(ret));
200
201
  ret = sourceOfjs(res);
201
- cb(null, checkForContext(ret));
202
+ cb(null, checkForPostProcessing(ret));
202
203
  }
203
204
  });
204
205
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "locize-cli",
3
- "version": "7.8.1",
3
+ "version": "7.9.1",
4
4
  "description": "locize cli to import locales",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -11,7 +11,7 @@
11
11
  "android-string-resource": "2.3.4",
12
12
  "async": "3.2.3",
13
13
  "colors": "1.4.0",
14
- "commander": "7.2.0",
14
+ "commander": "9.0.0",
15
15
  "csvjson": "5.1.0",
16
16
  "diff": "5.0.0",
17
17
  "flat": "5.0.2",
@@ -28,8 +28,8 @@
28
28
  "rimraf": "3.0.2",
29
29
  "strings-file": "0.0.5",
30
30
  "tmexchange": "2.0.4",
31
- "xliff": "6.0.1",
32
- "xlsx": "0.18.0"
31
+ "xliff": "6.0.3",
32
+ "xlsx": "0.18.2"
33
33
  },
34
34
  "devDependencies": {
35
35
  "eslint": "7.32.0",