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 +15 -0
- package/combineSubkeyPreprocessor.js +154 -0
- package/convertToDesiredFormat.js +3 -1
- package/convertToFlatFormat.js +9 -8
- package/package.json +4 -4
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
|
-
|
|
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
|
}
|
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) {
|
|
@@ -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
|
|
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,
|
|
191
|
+
cb(null, checkForPostProcessing(ret));
|
|
191
192
|
});
|
|
192
193
|
} else {
|
|
193
194
|
let ret = targetOfjs(res);
|
|
194
|
-
if (lng !== opt.referenceLanguage) return cb(null,
|
|
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,
|
|
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,
|
|
200
|
+
if (!allEmpty) return cb(null, checkForPostProcessing(ret));
|
|
200
201
|
ret = sourceOfjs(res);
|
|
201
|
-
cb(null,
|
|
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.
|
|
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": "
|
|
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.
|
|
32
|
-
"xlsx": "0.18.
|
|
31
|
+
"xliff": "6.0.3",
|
|
32
|
+
"xlsx": "0.18.2"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"eslint": "7.32.0",
|