locize-cli 7.8.2 → 7.10.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/CHANGELOG.md +14 -0
- package/combineSubkeyPreprocessor.js +154 -0
- package/convertToDesiredFormat.js +6 -2
- package/convertToFlatFormat.js +12 -9
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ 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.10.0](https://github.com/locize/locize-cli/compare/v7.9.1...v7.10.0) - 2022-03-15
|
|
9
|
+
|
|
10
|
+
- gettext_i18next: try to detect v4 format
|
|
11
|
+
|
|
12
|
+
## [7.9.1](https://github.com/locize/locize-cli/compare/v7.9.0...v7.9.1) - 2022-03-02
|
|
13
|
+
|
|
14
|
+
- xliff: fix combined plural keys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [7.9.0](https://github.com/locize/locize-cli/compare/v7.8.2...v7.9.0) - 2022-03-01
|
|
18
|
+
|
|
19
|
+
- xliff: detect i18n format and merge pluralforms if necessary
|
|
20
|
+
|
|
21
|
+
|
|
8
22
|
## [7.8.2](https://github.com/locize/locize-cli/compare/v7.8.1...v7.8.2) - 2022-02-28
|
|
9
23
|
|
|
10
24
|
- update dependencies
|
|
@@ -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,
|
|
@@ -55,11 +56,13 @@ const convertToDesiredFormat = (
|
|
|
55
56
|
}
|
|
56
57
|
if (opt.format === 'po_i18next' || opt.format === 'gettext_i18next') {
|
|
57
58
|
const flatData = flatten(data);
|
|
59
|
+
const compatibilityJSON = !!Object.keys(flatData).find((k) => /_(zero|one|two|few|many|other)/.test(k)) && 'v4';
|
|
58
60
|
const gettextOpt = {
|
|
59
61
|
project: 'locize',
|
|
60
62
|
language: lng,
|
|
61
63
|
potCreationDate: lastModified,
|
|
62
|
-
poRevisionDate: lastModified
|
|
64
|
+
poRevisionDate: lastModified,
|
|
65
|
+
compatibilityJSON
|
|
63
66
|
};
|
|
64
67
|
cb(null, i18next2po(lng, flatData, gettextOpt));
|
|
65
68
|
return;
|
|
@@ -172,7 +175,8 @@ const convertToDesiredFormat = (
|
|
|
172
175
|
opt.getNamespace(opt, opt.referenceLanguage, namespace, (err, refNs) => {
|
|
173
176
|
if (err) return cb(err);
|
|
174
177
|
|
|
175
|
-
|
|
178
|
+
const prepared = prepareCombinedExport(refNs, flatten(data));
|
|
179
|
+
fn(opt.referenceLanguage, lng, prepared.ref, prepared.trg, namespace, cb);
|
|
176
180
|
});
|
|
177
181
|
return;
|
|
178
182
|
}
|
package/convertToFlatFormat.js
CHANGED
|
@@ -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) {
|
|
@@ -39,7 +40,9 @@ const convertToFlatFormat = (opt, data, lng, cb) => {
|
|
|
39
40
|
}
|
|
40
41
|
if (opt.format === 'po_i18next' || opt.format === 'gettext_i18next') {
|
|
41
42
|
try {
|
|
42
|
-
const
|
|
43
|
+
const potxt = data.toString();
|
|
44
|
+
const compatibilityJSON = /msgctxt "(zero|one|two|few|many|other)"/.test(potxt) && 'v4';
|
|
45
|
+
const ret = po2i18next(potxt, { compatibilityJSON });
|
|
43
46
|
cb(null, flatten(ret));
|
|
44
47
|
} catch (err) {
|
|
45
48
|
cb(err);
|
|
@@ -171,34 +174,34 @@ const convertToFlatFormat = (opt, data, lng, cb) => {
|
|
|
171
174
|
res.resources = res.resources || {};
|
|
172
175
|
const ns = Object.keys(res.resources)[0];
|
|
173
176
|
const orgRes = res.resources[ns] || res.resources;
|
|
174
|
-
function
|
|
177
|
+
function checkForPostProcessing(nsRes) {
|
|
175
178
|
Object.keys(nsRes).forEach((k) => {
|
|
176
179
|
if (orgRes[k].note && (typeof nsRes[k] === 'string' || !nsRes[k])) {
|
|
177
180
|
nsRes[k] = {
|
|
178
181
|
value: nsRes[k],
|
|
179
182
|
context: {
|
|
180
183
|
text: orgRes[k].note,
|
|
181
|
-
}
|
|
184
|
+
}
|
|
182
185
|
};
|
|
183
186
|
}
|
|
184
187
|
});
|
|
185
|
-
return nsRes;
|
|
188
|
+
return prepareCombinedImport(nsRes);
|
|
186
189
|
}
|
|
187
190
|
if (!res.targetLanguage) {
|
|
188
191
|
sourceOfjs(res, (err, ret) => {
|
|
189
192
|
if (err) return cb(err);
|
|
190
|
-
cb(null,
|
|
193
|
+
cb(null, checkForPostProcessing(ret));
|
|
191
194
|
});
|
|
192
195
|
} else {
|
|
193
196
|
let ret = targetOfjs(res);
|
|
194
|
-
if (lng !== opt.referenceLanguage) return cb(null,
|
|
197
|
+
if (lng !== opt.referenceLanguage) return cb(null, checkForPostProcessing(ret));
|
|
195
198
|
ret = ret || {};
|
|
196
199
|
const keys = Object.keys(ret);
|
|
197
|
-
if (keys.length === 0) return cb(null,
|
|
200
|
+
if (keys.length === 0) return cb(null, checkForPostProcessing(ret));
|
|
198
201
|
const allEmpty = keys.filter((k) => ret[k] !== '').length === 0;
|
|
199
|
-
if (!allEmpty) return cb(null,
|
|
202
|
+
if (!allEmpty) return cb(null, checkForPostProcessing(ret));
|
|
200
203
|
ret = sourceOfjs(res);
|
|
201
|
-
cb(null,
|
|
204
|
+
cb(null, checkForPostProcessing(ret));
|
|
202
205
|
}
|
|
203
206
|
});
|
|
204
207
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "locize-cli",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.10.0",
|
|
4
4
|
"description": "locize cli to import locales",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"diff": "5.0.0",
|
|
17
17
|
"flat": "5.0.2",
|
|
18
18
|
"fluent_conv": "3.1.0",
|
|
19
|
-
"gettext-converter": "1.2.
|
|
19
|
+
"gettext-converter": "1.2.2",
|
|
20
20
|
"https-proxy-agent": "5.0.0",
|
|
21
21
|
"ini": "2.0.0",
|
|
22
22
|
"js-yaml": "4.1.0",
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"strings-file": "0.0.5",
|
|
30
30
|
"tmexchange": "2.0.4",
|
|
31
31
|
"xliff": "6.0.3",
|
|
32
|
-
"xlsx": "0.18.
|
|
32
|
+
"xlsx": "0.18.3"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"eslint": "7.32.0",
|
|
36
|
-
"gh-release": "6.0.
|
|
36
|
+
"gh-release": "6.0.2",
|
|
37
37
|
"pkg": "5.5.2"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|