locize-cli 11.0.0 → 12.0.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 +12 -0
- package/LICENSE +1 -1
- package/README.md +1 -0
- package/dist/cjs/add.js +90 -0
- package/{bin/locize → dist/cjs/cli.js} +390 -670
- package/dist/cjs/combineSubkeyPreprocessor.js +155 -0
- package/dist/cjs/convertToDesiredFormat.js +205 -0
- package/dist/cjs/convertToFlatFormat.js +231 -0
- package/dist/cjs/copyVersion.js +60 -0
- package/dist/cjs/createBranch.js +59 -0
- package/dist/cjs/deleteBranch.js +89 -0
- package/dist/cjs/deleteNamespace.js +37 -0
- package/dist/cjs/download.js +376 -0
- package/dist/cjs/filterNamespaces.js +13 -0
- package/dist/cjs/format.js +156 -0
- package/dist/cjs/formats.js +33 -0
- package/dist/cjs/get.js +66 -0
- package/dist/cjs/getBranches.js +37 -0
- package/dist/cjs/getJob.js +37 -0
- package/dist/cjs/getProjectStats.js +37 -0
- package/dist/cjs/getRemoteLanguages.js +38 -0
- package/dist/cjs/getRemoteNamespace.js +125 -0
- package/dist/cjs/index.js +37 -0
- package/dist/cjs/isValidUuid.js +6 -0
- package/dist/cjs/lngs.js +215 -0
- package/dist/cjs/mapLimit.js +22 -0
- package/dist/cjs/mergeBranch.js +80 -0
- package/dist/cjs/migrate.js +239 -0
- package/dist/cjs/missing.js +162 -0
- package/dist/cjs/package.json +5 -0
- package/{parseLocalLanguage.js → dist/cjs/parseLocalLanguage.js} +135 -142
- package/dist/cjs/parseLocalLanguages.js +18 -0
- package/dist/cjs/parseLocalReference.js +11 -0
- package/dist/cjs/publishVersion.js +42 -0
- package/dist/cjs/removeUndefinedFromArrays.js +19 -0
- package/dist/cjs/removeVersion.js +42 -0
- package/dist/cjs/request.js +66 -0
- package/dist/cjs/shouldUnflatten.js +21 -0
- package/dist/cjs/sortFlatResources.js +13 -0
- package/dist/cjs/sync.js +772 -0
- package/dist/cjs/unflatten.js +81 -0
- package/dist/esm/add.js +88 -0
- package/dist/esm/cli.js +1020 -0
- package/{combineSubkeyPreprocessor.js → dist/esm/combineSubkeyPreprocessor.js} +70 -73
- package/dist/esm/convertToDesiredFormat.js +203 -0
- package/dist/esm/convertToFlatFormat.js +229 -0
- package/dist/esm/copyVersion.js +58 -0
- package/dist/esm/createBranch.js +57 -0
- package/dist/esm/deleteBranch.js +87 -0
- package/dist/esm/deleteNamespace.js +35 -0
- package/dist/esm/download.js +374 -0
- package/{filterNamespaces.js → dist/esm/filterNamespaces.js} +4 -4
- package/dist/esm/format.js +154 -0
- package/{formats.js → dist/esm/formats.js} +7 -11
- package/dist/esm/get.js +64 -0
- package/dist/esm/getBranches.js +35 -0
- package/dist/esm/getJob.js +35 -0
- package/dist/esm/getProjectStats.js +35 -0
- package/dist/esm/getRemoteLanguages.js +36 -0
- package/dist/esm/getRemoteNamespace.js +123 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/isValidUuid.js +4 -0
- package/dist/esm/lngs.js +213 -0
- package/dist/esm/mapLimit.js +20 -0
- package/dist/esm/mergeBranch.js +78 -0
- package/dist/esm/migrate.js +237 -0
- package/dist/esm/missing.js +160 -0
- package/dist/esm/parseLocalLanguage.js +194 -0
- package/dist/esm/parseLocalLanguages.js +16 -0
- package/dist/esm/parseLocalReference.js +9 -0
- package/dist/esm/publishVersion.js +40 -0
- package/{removeUndefinedFromArrays.js → dist/esm/removeUndefinedFromArrays.js} +5 -5
- package/dist/esm/removeVersion.js +40 -0
- package/dist/esm/request.js +64 -0
- package/{shouldUnflatten.js → dist/esm/shouldUnflatten.js} +7 -7
- package/dist/esm/sortFlatResources.js +11 -0
- package/dist/esm/sync.js +770 -0
- package/{unflatten.js → dist/esm/unflatten.js} +36 -34
- package/package.json +39 -18
- package/rollup.config.js +57 -0
- package/add.js +0 -105
- package/convertToDesiredFormat.js +0 -268
- package/convertToFlatFormat.js +0 -322
- package/copyVersion.js +0 -69
- package/createBranch.js +0 -61
- package/deleteBranch.js +0 -97
- package/deleteNamespace.js +0 -39
- package/download.js +0 -516
- package/format.js +0 -206
- package/get.js +0 -81
- package/getBranches.js +0 -40
- package/getJob.js +0 -40
- package/getProjectStats.js +0 -40
- package/getRemoteLanguages.js +0 -40
- package/getRemoteNamespace.js +0 -122
- package/index.js +0 -9
- package/isValidUuid.js +0 -2
- package/lngs.json +0 -211
- package/mergeBranch.js +0 -102
- package/migrate.js +0 -314
- package/missing.js +0 -169
- package/parseLocalLanguages.js +0 -22
- package/parseLocalReference.js +0 -10
- package/publishVersion.js +0 -64
- package/removeVersion.js +0 -64
- package/request.js +0 -64
- package/sortFlatResources.js +0 -9
- package/sync.js +0 -786
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var yaml = require('yaml');
|
|
4
|
+
|
|
5
|
+
const delimiter = {
|
|
6
|
+
i18next: '_',
|
|
7
|
+
i18njs: '.'
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const detectFormat = (keys) => {
|
|
11
|
+
const i18nextMatches = keys.filter((k) => k.indexOf(delimiter.i18next) > 0).length;
|
|
12
|
+
const i18njsMatches = keys.filter((k) => k.indexOf(delimiter.i18njs) > 0).length;
|
|
13
|
+
if (i18nextMatches > i18njsMatches) {
|
|
14
|
+
return 'i18next'
|
|
15
|
+
}
|
|
16
|
+
if (i18nextMatches < i18njsMatches) {
|
|
17
|
+
return 'i18njs'
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getBaseKey = (delimiter) => (k) => {
|
|
22
|
+
const parts = k.split(delimiter);
|
|
23
|
+
parts.pop();
|
|
24
|
+
const baseKey = parts.join(delimiter);
|
|
25
|
+
return baseKey
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const uniq = (value, index, self) => self.indexOf(value) === index;
|
|
29
|
+
|
|
30
|
+
const stringify = (o) => {
|
|
31
|
+
let str = yaml.stringify(o);
|
|
32
|
+
const subKeys = Object.keys(o);
|
|
33
|
+
subKeys.forEach((sk) => {
|
|
34
|
+
if (isNaN(sk)) {
|
|
35
|
+
str = str.replace(new RegExp(`^(?:${sk}: )+`, 'm'), `{${sk}}: `);
|
|
36
|
+
} else {
|
|
37
|
+
str = str.replace(new RegExp(`^(?:'${sk}': )+`, 'm'), `{${sk}}: `);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return str
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const transformKeys = (segments, baseKeys, toMerge, deli) => {
|
|
44
|
+
baseKeys.forEach((bk) => {
|
|
45
|
+
const asObj = toMerge[bk].reduce((mem, k) => {
|
|
46
|
+
const subKey = k.substring((bk + deli).length);
|
|
47
|
+
// special handling for i18next v3
|
|
48
|
+
if (deli === delimiter.i18next && subKey === 'plural' && segments[bk]) {
|
|
49
|
+
mem['__'] = segments[bk];
|
|
50
|
+
delete segments[bk];
|
|
51
|
+
}
|
|
52
|
+
mem[subKey] = segments[k];
|
|
53
|
+
return mem
|
|
54
|
+
}, {});
|
|
55
|
+
if (Object.keys(asObj).length > 0) {
|
|
56
|
+
const value = stringify(asObj);
|
|
57
|
+
segments[`${bk}__#locize.com/combinedSubkey`] = value;
|
|
58
|
+
toMerge[bk].forEach((k) => {
|
|
59
|
+
delete segments[k];
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return segments
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// CLDR
|
|
67
|
+
const pluralForms = [
|
|
68
|
+
'zero',
|
|
69
|
+
'one',
|
|
70
|
+
'two',
|
|
71
|
+
'few',
|
|
72
|
+
'many',
|
|
73
|
+
'other'
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const endsWithPluralForm = (k) => !!pluralForms.find((f) => k.endsWith(`.${f}`)) || !!pluralForms.find((f) => k.endsWith(`_${f}`)) || /_\d+$/.test(k) || k.endsWith('_plural');
|
|
77
|
+
|
|
78
|
+
const prepareExport = (refRes, trgRes) => {
|
|
79
|
+
const refLngKeys = Object.keys(refRes);
|
|
80
|
+
const trgLngKeys = Object.keys(trgRes);
|
|
81
|
+
|
|
82
|
+
const nonMatchInRef = refLngKeys.filter((k) => trgLngKeys.indexOf(k) < 0 && endsWithPluralForm(k));
|
|
83
|
+
const nonMatchInTrg = trgLngKeys.filter((k) => refLngKeys.indexOf(k) < 0 && endsWithPluralForm(k));
|
|
84
|
+
|
|
85
|
+
const allMatches = nonMatchInRef.concat(nonMatchInTrg);
|
|
86
|
+
|
|
87
|
+
const format = detectFormat(allMatches);
|
|
88
|
+
if (!format) return { ref: refRes, trg: trgRes }
|
|
89
|
+
|
|
90
|
+
const nonMatchBaseKeysInRef = nonMatchInRef.map(getBaseKey(delimiter[format])).filter(uniq);
|
|
91
|
+
const nonMatchBaseKeysInTrg = nonMatchInTrg.map(getBaseKey(delimiter[format])).filter(uniq);
|
|
92
|
+
const nonMatchBaseKeys = nonMatchBaseKeysInRef.concat(nonMatchBaseKeysInTrg).filter(uniq);
|
|
93
|
+
|
|
94
|
+
const toMergeInRef = nonMatchBaseKeys.reduce((mem, bk) => {
|
|
95
|
+
mem[bk] = refLngKeys.filter((k) => k.indexOf(bk + delimiter[format]) === 0);
|
|
96
|
+
return mem
|
|
97
|
+
}, {});
|
|
98
|
+
const toMergeInTrg = nonMatchBaseKeys.reduce((mem, bk) => {
|
|
99
|
+
mem[bk] = trgLngKeys.filter((k) => k.indexOf(bk + delimiter[format]) === 0);
|
|
100
|
+
return mem
|
|
101
|
+
}, {});
|
|
102
|
+
|
|
103
|
+
let falseFlags = nonMatchBaseKeysInRef.filter((k) => toMergeInRef[k].length < 2 && (!toMergeInTrg[k] || toMergeInTrg[k].length < 2));
|
|
104
|
+
falseFlags = falseFlags.concat(nonMatchBaseKeysInTrg.filter((k) => toMergeInTrg[k].length < 2 && (!toMergeInRef[k] || toMergeInRef[k].length < 2)));
|
|
105
|
+
falseFlags.forEach((k) => {
|
|
106
|
+
delete toMergeInRef[k];
|
|
107
|
+
delete toMergeInTrg[k];
|
|
108
|
+
nonMatchBaseKeys.splice(nonMatchBaseKeys.indexOf(k), 1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const transformedRef = transformKeys(refRes, nonMatchBaseKeys, toMergeInRef, delimiter[format]);
|
|
112
|
+
const transformedTrg = transformKeys(trgRes, nonMatchBaseKeys, toMergeInTrg, delimiter[format]);
|
|
113
|
+
return { ref: transformedRef, trg: transformedTrg }
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// eslint-disable-next-line prefer-regex-literals
|
|
117
|
+
const skRegex = new RegExp('^(?:{(.+)})+', 'gm');
|
|
118
|
+
const parse = (s) => {
|
|
119
|
+
let matchArray;
|
|
120
|
+
while ((matchArray = skRegex.exec(s)) !== null) {
|
|
121
|
+
const [match, sk] = matchArray;
|
|
122
|
+
if (isNaN(sk)) {
|
|
123
|
+
s = s.replace(new RegExp(`^(?:${match}: )+`, 'm'), `${sk}: `);
|
|
124
|
+
} else {
|
|
125
|
+
const escapedMatch = match.replace('{', '\\{').replace('}', '\\}');
|
|
126
|
+
s = s.replace(new RegExp(`^(?:${escapedMatch}: )+`, 'm'), `${sk}: `);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return yaml.parse(s)
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const prepareImport = (resources) => {
|
|
133
|
+
const keys = Object.keys(resources);
|
|
134
|
+
keys.forEach((k) => {
|
|
135
|
+
if (k.indexOf('__#locize.com/combinedSubkey') > -1) {
|
|
136
|
+
const baseKey = k.substring(0, k.indexOf('__#locize.com/combinedSubkey'));
|
|
137
|
+
if (resources[k]) {
|
|
138
|
+
const parsed = parse(resources[k]);
|
|
139
|
+
Object.keys(parsed).forEach((sk) => {
|
|
140
|
+
const skVal = parsed[sk];
|
|
141
|
+
resources[`${baseKey}_${sk}`] = skVal;
|
|
142
|
+
if (sk === '__') {
|
|
143
|
+
resources[baseKey] = resources[`${baseKey}_${sk}`];
|
|
144
|
+
delete resources[`${baseKey}_${sk}`];
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
delete resources[k];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return resources
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
exports.prepareExport = prepareExport;
|
|
155
|
+
exports.prepareImport = prepareImport;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var flatten = require('flat');
|
|
4
|
+
var i18next2po = require('gettext-converter/i18next2po');
|
|
5
|
+
var csv = require('fast-csv');
|
|
6
|
+
var xlsx = require('xlsx');
|
|
7
|
+
var yaml = require('yaml');
|
|
8
|
+
var js2asr = require('android-string-resource/js2asr');
|
|
9
|
+
var stringsFile = require('strings-file');
|
|
10
|
+
var createxliff = require('xliff/createxliff');
|
|
11
|
+
var createxliff12 = require('xliff/createxliff12');
|
|
12
|
+
var js2resx = require('resx/js2resx');
|
|
13
|
+
var js2ftl = require('fluent_conv/js2ftl');
|
|
14
|
+
var js2tmx = require('tmexchange/js2tmx');
|
|
15
|
+
var js2laravel = require('laravelphp/js2laravel');
|
|
16
|
+
var javaProperties = require('@js.properties/properties');
|
|
17
|
+
var unflatten = require('./unflatten.js');
|
|
18
|
+
var getRemoteNamespace = require('./getRemoteNamespace.js');
|
|
19
|
+
var removeUndefinedFromArrays = require('./removeUndefinedFromArrays.js');
|
|
20
|
+
var shouldUnflatten = require('./shouldUnflatten.js');
|
|
21
|
+
var combineSubkeyPreprocessor = require('./combineSubkeyPreprocessor.js');
|
|
22
|
+
|
|
23
|
+
const convertToDesiredFormat = async (
|
|
24
|
+
opt,
|
|
25
|
+
namespace,
|
|
26
|
+
lng,
|
|
27
|
+
data,
|
|
28
|
+
lastModified
|
|
29
|
+
) => {
|
|
30
|
+
opt.getNamespace = opt.getNamespace || getRemoteNamespace;
|
|
31
|
+
const isEmpty = !data || Object.keys(data).length === 0;
|
|
32
|
+
if (opt.format === 'json') {
|
|
33
|
+
try {
|
|
34
|
+
data = unflatten(data, true);
|
|
35
|
+
} catch (err) {}
|
|
36
|
+
return JSON.stringify(data, null, 2)
|
|
37
|
+
}
|
|
38
|
+
if (opt.format === 'nested') {
|
|
39
|
+
try {
|
|
40
|
+
data = unflatten(data);
|
|
41
|
+
} catch (err) {}
|
|
42
|
+
return JSON.stringify(data, null, 2)
|
|
43
|
+
}
|
|
44
|
+
if (opt.format === 'flat') {
|
|
45
|
+
return JSON.stringify(flatten(data), null, 2)
|
|
46
|
+
}
|
|
47
|
+
if (opt.format === 'po' || opt.format === 'gettext') {
|
|
48
|
+
const flatData = flatten(data);
|
|
49
|
+
const gettextOpt = {
|
|
50
|
+
project: 'locize',
|
|
51
|
+
language: lng,
|
|
52
|
+
potCreationDate: lastModified,
|
|
53
|
+
poRevisionDate: lastModified,
|
|
54
|
+
ctxSeparator: '_ is default but we set it to something that is never found!!!',
|
|
55
|
+
persistMsgIdPlural: true
|
|
56
|
+
};
|
|
57
|
+
return i18next2po(lng, flatData, gettextOpt)
|
|
58
|
+
}
|
|
59
|
+
if (opt.format === 'po_i18next' || opt.format === 'gettext_i18next') {
|
|
60
|
+
const flatData = flatten(data);
|
|
61
|
+
const compatibilityJSON = !!Object.keys(flatData).find((k) => /_(zero|one|two|few|many|other)/.test(k)) && 'v4';
|
|
62
|
+
const gettextOpt = {
|
|
63
|
+
project: 'locize',
|
|
64
|
+
language: lng,
|
|
65
|
+
potCreationDate: lastModified,
|
|
66
|
+
poRevisionDate: lastModified,
|
|
67
|
+
compatibilityJSON
|
|
68
|
+
};
|
|
69
|
+
return i18next2po(lng, flatData, gettextOpt)
|
|
70
|
+
}
|
|
71
|
+
if (opt.format === 'csv') {
|
|
72
|
+
const refNs = await opt.getNamespace(opt, opt.referenceLanguage, namespace);
|
|
73
|
+
const js2CsvData = Object.keys(flatten(data)).reduce((mem, k) => {
|
|
74
|
+
const value = data[k] || '';
|
|
75
|
+
const line = {
|
|
76
|
+
key: k,
|
|
77
|
+
[opt.referenceLanguage]: refNs[k] || '',
|
|
78
|
+
[lng]: value
|
|
79
|
+
};
|
|
80
|
+
mem.push(line);
|
|
81
|
+
return mem
|
|
82
|
+
}, []);
|
|
83
|
+
return `\ufeff${await csv.writeToString(js2CsvData, { headers: true, quoteColumns: true })}`
|
|
84
|
+
}
|
|
85
|
+
if (opt.format === 'xlsx') {
|
|
86
|
+
const refNs = await opt.getNamespace(opt, opt.referenceLanguage, namespace);
|
|
87
|
+
const js2XlsxData = Object.keys(flatten(data)).reduce((mem, k) => {
|
|
88
|
+
const value = data[k] || '';
|
|
89
|
+
const line = {
|
|
90
|
+
key: k,
|
|
91
|
+
[opt.referenceLanguage]: refNs[k] || '',
|
|
92
|
+
[lng]: value
|
|
93
|
+
};
|
|
94
|
+
mem.push(line);
|
|
95
|
+
return mem
|
|
96
|
+
}, []);
|
|
97
|
+
const worksheet = xlsx.utils.json_to_sheet(js2XlsxData);
|
|
98
|
+
const workbook = xlsx.utils.book_new();
|
|
99
|
+
let workSheetName = namespace;
|
|
100
|
+
if (workSheetName.length > 31) workSheetName = workSheetName.substring(0, 31);
|
|
101
|
+
workbook.SheetNames.push(workSheetName);
|
|
102
|
+
workbook.Sheets[workSheetName] = worksheet;
|
|
103
|
+
return xlsx.write(workbook, { type: 'buffer' })
|
|
104
|
+
}
|
|
105
|
+
if (
|
|
106
|
+
opt.format === 'yaml' ||
|
|
107
|
+
opt.format === 'yml'
|
|
108
|
+
) {
|
|
109
|
+
if (isEmpty) return ''
|
|
110
|
+
return yaml.stringify(flatten(data))
|
|
111
|
+
}
|
|
112
|
+
if (
|
|
113
|
+
opt.format === 'yaml-nested' ||
|
|
114
|
+
opt.format === 'yml-nested'
|
|
115
|
+
) {
|
|
116
|
+
if (isEmpty) return ''
|
|
117
|
+
return yaml.stringify(shouldUnflatten(data) ? unflatten(data) : data)
|
|
118
|
+
}
|
|
119
|
+
if (
|
|
120
|
+
opt.format === 'yaml-rails' ||
|
|
121
|
+
opt.format === 'yml-rails'
|
|
122
|
+
) {
|
|
123
|
+
if (isEmpty) return ''
|
|
124
|
+
const newData = {};
|
|
125
|
+
newData[lng] = shouldUnflatten(data) ? unflatten(data) : data;
|
|
126
|
+
return yaml.stringify(removeUndefinedFromArrays(newData))
|
|
127
|
+
}
|
|
128
|
+
if (
|
|
129
|
+
opt.format === 'yaml-rails-ns' ||
|
|
130
|
+
opt.format === 'yml-rails-ns'
|
|
131
|
+
) {
|
|
132
|
+
if (isEmpty) return ''
|
|
133
|
+
const newDataNs = {};
|
|
134
|
+
newDataNs[lng] = {};
|
|
135
|
+
newDataNs[lng][namespace] = shouldUnflatten(data) ? unflatten(data) : data;
|
|
136
|
+
return yaml.stringify(removeUndefinedFromArrays(newDataNs))
|
|
137
|
+
}
|
|
138
|
+
if (opt.format === 'android') {
|
|
139
|
+
return await js2asr(flatten(data))
|
|
140
|
+
}
|
|
141
|
+
if (opt.format === 'strings') {
|
|
142
|
+
Object.keys(data).forEach((k) => {
|
|
143
|
+
if (data[k] === null) delete data[k];
|
|
144
|
+
});
|
|
145
|
+
return stringsFile.compile(data)
|
|
146
|
+
}
|
|
147
|
+
if (
|
|
148
|
+
opt.format === 'xliff2' ||
|
|
149
|
+
opt.format === 'xliff12' ||
|
|
150
|
+
opt.format === 'xlf2' ||
|
|
151
|
+
opt.format === 'xlf12'
|
|
152
|
+
) {
|
|
153
|
+
const fn =
|
|
154
|
+
opt.format === 'xliff12' || opt.format === 'xlf12'
|
|
155
|
+
? createxliff12
|
|
156
|
+
: createxliff;
|
|
157
|
+
const refNs = await opt.getNamespace(opt, opt.referenceLanguage, namespace);
|
|
158
|
+
const prepared = combineSubkeyPreprocessor.prepareExport(refNs, flatten(data));
|
|
159
|
+
return await fn(opt.referenceLanguage, lng, prepared.ref, prepared.trg, namespace)
|
|
160
|
+
}
|
|
161
|
+
if (opt.format === 'resx') {
|
|
162
|
+
return await js2resx(flatten(data))
|
|
163
|
+
}
|
|
164
|
+
if (opt.format === 'fluent') {
|
|
165
|
+
Object.keys(data).forEach((k) => {
|
|
166
|
+
if (!data[k] || data[k] === '') delete data[k];
|
|
167
|
+
data[k] = data[k].replace(
|
|
168
|
+
new RegExp(String.fromCharCode(160), 'g'),
|
|
169
|
+
String.fromCharCode(32)
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
return js2ftl(unflatten(data))
|
|
173
|
+
}
|
|
174
|
+
if (opt.format === 'tmx') {
|
|
175
|
+
const refNs = await opt.getNamespace(opt, opt.referenceLanguage, namespace);
|
|
176
|
+
const js = flatten(data);
|
|
177
|
+
const js2TmxData = Object.keys(js).reduce(
|
|
178
|
+
(mem, k) => {
|
|
179
|
+
const refItem = refNs[k];
|
|
180
|
+
if (!refItem) return mem
|
|
181
|
+
const value = js[k] || '';
|
|
182
|
+
mem.resources[namespace][k] = {};
|
|
183
|
+
mem.resources[namespace][k][opt.referenceLanguage] = refItem;
|
|
184
|
+
mem.resources[namespace][k][lng] = value;
|
|
185
|
+
return mem
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
resources: {
|
|
189
|
+
[namespace]: {}
|
|
190
|
+
},
|
|
191
|
+
sourceLanguage: opt.referenceLanguage
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
return await js2tmx(js2TmxData)
|
|
195
|
+
}
|
|
196
|
+
if (opt.format === 'laravel') {
|
|
197
|
+
return await js2laravel(unflatten(data))
|
|
198
|
+
}
|
|
199
|
+
if (opt.format === 'properties') {
|
|
200
|
+
return javaProperties.stringifyFromProperties(data, { eol: '\n' })
|
|
201
|
+
}
|
|
202
|
+
throw new Error(`${opt.format} is not a valid format!`)
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
module.exports = convertToDesiredFormat;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var po2i18next = require('gettext-converter/po2i18next');
|
|
4
|
+
var csv = require('fast-csv');
|
|
5
|
+
var xlsx = require('xlsx');
|
|
6
|
+
var yaml = require('yaml');
|
|
7
|
+
var asr2js = require('android-string-resource/asr2js');
|
|
8
|
+
var stringsFile = require('strings-file');
|
|
9
|
+
var xliff2js = require('xliff/xliff2js');
|
|
10
|
+
var xliff12ToJs = require('xliff/xliff12ToJs');
|
|
11
|
+
var targetOfjs = require('xliff/targetOfjs');
|
|
12
|
+
var sourceOfjs = require('xliff/sourceOfjs');
|
|
13
|
+
var resx2js = require('resx/resx2js');
|
|
14
|
+
var ftl2js = require('fluent_conv/ftl2js');
|
|
15
|
+
var tmx2js = require('tmexchange/tmx2js');
|
|
16
|
+
var laravel2js = require('laravelphp/laravel2js');
|
|
17
|
+
var javaProperties = require('@js.properties/properties');
|
|
18
|
+
var xcstrings2locize = require('locize-xcstrings/xcstrings2locize');
|
|
19
|
+
var flatten = require('flat');
|
|
20
|
+
var combineSubkeyPreprocessor = require('./combineSubkeyPreprocessor.js');
|
|
21
|
+
|
|
22
|
+
const convertToFlatFormat = async (opt, data, lng) => {
|
|
23
|
+
if (lng && typeof lng !== 'string') lng = undefined;
|
|
24
|
+
if (opt.format === 'json' || opt.format === 'nested' || opt.format === 'flat') {
|
|
25
|
+
const dataString = data.toString().trim();
|
|
26
|
+
if (dataString[0] !== '{' && dataString[0] !== '[') {
|
|
27
|
+
throw new Error(`Not a valid json file: Content starts with "${dataString[0]}" but should start with "{"`)
|
|
28
|
+
}
|
|
29
|
+
const jsonParsed = JSON.parse(dataString);
|
|
30
|
+
return flatten(jsonParsed)
|
|
31
|
+
}
|
|
32
|
+
if (opt.format === 'po' || opt.format === 'gettext') {
|
|
33
|
+
const ret = po2i18next(data.toString(), {
|
|
34
|
+
persistMsgIdPlural: true,
|
|
35
|
+
ignoreCtx: true
|
|
36
|
+
});
|
|
37
|
+
return flatten(ret)
|
|
38
|
+
}
|
|
39
|
+
if (opt.format === 'po_i18next' || opt.format === 'gettext_i18next') {
|
|
40
|
+
const potxt = data.toString();
|
|
41
|
+
const compatibilityJSON = /msgctxt "(zero|one|two|few|many|other)"/.test(potxt) && 'v4';
|
|
42
|
+
const ret = po2i18next(potxt, { compatibilityJSON });
|
|
43
|
+
return flatten(ret)
|
|
44
|
+
}
|
|
45
|
+
if (opt.format === 'csv') {
|
|
46
|
+
// CRLF => LF
|
|
47
|
+
const text = data.toString().replace(/\r\n/g, '\n');
|
|
48
|
+
const rows = await csv.parseString(text, { headers: true, ignoreEmpty: true }).promise();
|
|
49
|
+
const result = rows.reduce((mem, entry) => {
|
|
50
|
+
if (entry.key && typeof entry[opt.referenceLanguage] === 'string') {
|
|
51
|
+
mem[entry.key] = entry[opt.referenceLanguage];
|
|
52
|
+
}
|
|
53
|
+
return mem
|
|
54
|
+
}, {});
|
|
55
|
+
return result
|
|
56
|
+
}
|
|
57
|
+
if (opt.format === 'xlsx') {
|
|
58
|
+
const wb = xlsx.read(data, { type: 'buffer' });
|
|
59
|
+
const jsonData = xlsx.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
|
|
60
|
+
const result = jsonData.reduce((mem, entry) => {
|
|
61
|
+
if (entry.key && typeof entry[opt.referenceLanguage] === 'string') {
|
|
62
|
+
mem[entry.key] = entry[opt.referenceLanguage];
|
|
63
|
+
}
|
|
64
|
+
return mem
|
|
65
|
+
}, {});
|
|
66
|
+
return result
|
|
67
|
+
}
|
|
68
|
+
if (
|
|
69
|
+
opt.format === 'yaml' ||
|
|
70
|
+
opt.format === 'yml'
|
|
71
|
+
) {
|
|
72
|
+
const d = data.toString();
|
|
73
|
+
if (!d || d === '' || d === '\n') return {}
|
|
74
|
+
return flatten(yaml.parse(d))
|
|
75
|
+
}
|
|
76
|
+
if (
|
|
77
|
+
opt.format === 'yaml-nested' ||
|
|
78
|
+
opt.format === 'yml-nested'
|
|
79
|
+
) {
|
|
80
|
+
const d = data.toString();
|
|
81
|
+
if (!d || d === '' || d === '\n') return {}
|
|
82
|
+
return flatten(yaml.parse(d))
|
|
83
|
+
}
|
|
84
|
+
if (
|
|
85
|
+
opt.format === 'yaml-rails' ||
|
|
86
|
+
opt.format === 'yml-rails'
|
|
87
|
+
) {
|
|
88
|
+
const d = data.toString();
|
|
89
|
+
if (!d || d.trim() === '') return {}
|
|
90
|
+
const jsObj = yaml.parse(d);
|
|
91
|
+
return flatten(jsObj[Object.keys(jsObj)[0]])
|
|
92
|
+
}
|
|
93
|
+
if (
|
|
94
|
+
opt.format === 'yaml-rails-ns' ||
|
|
95
|
+
opt.format === 'yml-rails-ns'
|
|
96
|
+
) {
|
|
97
|
+
const dn = data.toString();
|
|
98
|
+
if (!dn || dn.trim() === '') return {}
|
|
99
|
+
const jsObjn = yaml.parse(dn);
|
|
100
|
+
return flatten(jsObjn[Object.keys(jsObjn)[0]][Object.keys(jsObjn[Object.keys(jsObjn)[0]])[0]])
|
|
101
|
+
}
|
|
102
|
+
if (opt.format === 'android') {
|
|
103
|
+
const res = await asr2js(data.toString(), { comment: 'right' });
|
|
104
|
+
Object.keys(res).forEach((k) => {
|
|
105
|
+
if (res[k] !== 'string' && typeof res[k].comment === 'string') {
|
|
106
|
+
res[k] = {
|
|
107
|
+
value: res[k].value,
|
|
108
|
+
context: {
|
|
109
|
+
text: res[k].comment,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
} else {
|
|
113
|
+
res[k] = { value: res[k].value || res[k] };
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return res
|
|
117
|
+
}
|
|
118
|
+
if (opt.format === 'strings') {
|
|
119
|
+
// CRLF => LF
|
|
120
|
+
return stringsFile.parse(data.toString().replace(/\r\n/g, '\n'), false)
|
|
121
|
+
}
|
|
122
|
+
if (
|
|
123
|
+
opt.format === 'xliff2' ||
|
|
124
|
+
opt.format === 'xliff12' ||
|
|
125
|
+
opt.format === 'xlf2' ||
|
|
126
|
+
opt.format === 'xlf12'
|
|
127
|
+
) {
|
|
128
|
+
const fn =
|
|
129
|
+
opt.format === 'xliff12' || opt.format === 'xlf12'
|
|
130
|
+
? xliff12ToJs
|
|
131
|
+
: xliff2js;
|
|
132
|
+
const res = await fn(data.toString());
|
|
133
|
+
res.resources = res.resources || {};
|
|
134
|
+
const ns = Object.keys(res.resources)[0];
|
|
135
|
+
const orgRes = res.resources[ns] || res.resources;
|
|
136
|
+
function checkForPostProcessing (nsRes) {
|
|
137
|
+
Object.keys(nsRes).forEach((k) => {
|
|
138
|
+
if (orgRes[k].note && (typeof nsRes[k] === 'string' || !nsRes[k])) {
|
|
139
|
+
nsRes[k] = {
|
|
140
|
+
value: nsRes[k],
|
|
141
|
+
context: {
|
|
142
|
+
text: orgRes[k].note,
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
return combineSubkeyPreprocessor.prepareImport(nsRes)
|
|
148
|
+
}
|
|
149
|
+
if (!res.targetLanguage) {
|
|
150
|
+
const ret = await sourceOfjs(res);
|
|
151
|
+
return checkForPostProcessing(ret)
|
|
152
|
+
} else {
|
|
153
|
+
let ret = targetOfjs(res);
|
|
154
|
+
if (lng !== opt.referenceLanguage) return checkForPostProcessing(ret)
|
|
155
|
+
ret = ret || {};
|
|
156
|
+
const keys = Object.keys(ret);
|
|
157
|
+
if (keys.length === 0) return checkForPostProcessing(ret)
|
|
158
|
+
const allEmpty = keys.filter((k) => ret[k] !== '').length === 0;
|
|
159
|
+
if (!allEmpty) return checkForPostProcessing(ret)
|
|
160
|
+
ret = await sourceOfjs(res);
|
|
161
|
+
return checkForPostProcessing(ret)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (opt.format === 'resx') {
|
|
165
|
+
let res = await resx2js(data.toString());
|
|
166
|
+
res = Object.keys(res).reduce((mem, k) => {
|
|
167
|
+
const value = res[k];
|
|
168
|
+
if (typeof value === 'string') {
|
|
169
|
+
mem[k] = value;
|
|
170
|
+
} else if (value.value) {
|
|
171
|
+
mem[k] = {
|
|
172
|
+
value: value.value,
|
|
173
|
+
context: value.comment ? { text: value.comment } : null,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return mem
|
|
177
|
+
}, {});
|
|
178
|
+
return res
|
|
179
|
+
}
|
|
180
|
+
if (opt.format === 'fluent') {
|
|
181
|
+
const fluentJS = ftl2js(
|
|
182
|
+
data
|
|
183
|
+
.toString()
|
|
184
|
+
.replace(
|
|
185
|
+
new RegExp(String.fromCharCode(160), 'g'),
|
|
186
|
+
String.fromCharCode(32)
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
const comments = {};
|
|
190
|
+
Object.keys(fluentJS).forEach((prop) => {
|
|
191
|
+
if (fluentJS[prop] && fluentJS[prop].comment) {
|
|
192
|
+
comments[prop] = fluentJS[prop].comment;
|
|
193
|
+
delete fluentJS[prop].comment;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
const res = flatten(fluentJS);
|
|
197
|
+
if (res && comments) {
|
|
198
|
+
Object.keys(comments).forEach((prop) => {
|
|
199
|
+
res[`${prop}.val`] = {
|
|
200
|
+
value: res[`${prop}.val`],
|
|
201
|
+
context: comments[prop] ? { text: comments[prop] } : null,
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return res
|
|
206
|
+
}
|
|
207
|
+
if (opt.format === 'tmx') {
|
|
208
|
+
const jsonData = await tmx2js(data.toString());
|
|
209
|
+
const tmxJsRes = jsonData.resources[Object.keys(jsonData.resources)[0]];
|
|
210
|
+
const res = {};
|
|
211
|
+
if (tmxJsRes) {
|
|
212
|
+
Object.keys(tmxJsRes).forEach((k) => {
|
|
213
|
+
res[k] = tmxJsRes[k][opt.referenceLanguage];
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return res
|
|
217
|
+
}
|
|
218
|
+
if (opt.format === 'laravel') {
|
|
219
|
+
const res = await laravel2js(data.toString());
|
|
220
|
+
return flatten(res)
|
|
221
|
+
}
|
|
222
|
+
if (opt.format === 'properties') {
|
|
223
|
+
return javaProperties.parseToProperties(data.toString())
|
|
224
|
+
}
|
|
225
|
+
if (opt.format === 'xcstrings') {
|
|
226
|
+
return xcstrings2locize(data.toString())
|
|
227
|
+
}
|
|
228
|
+
throw new Error(`${opt.format} is not a valid format!`)
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
module.exports = convertToFlatFormat;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var colors = require('colors');
|
|
4
|
+
var request = require('./request.js');
|
|
5
|
+
var getJob = require('./getJob.js');
|
|
6
|
+
|
|
7
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
8
|
+
|
|
9
|
+
const copyVersion = async (opt) => {
|
|
10
|
+
const queryParams = new URLSearchParams();
|
|
11
|
+
if (opt.ignoreIfVersionExists) {
|
|
12
|
+
queryParams.append('ignoreIfVersionExists', 'true');
|
|
13
|
+
}
|
|
14
|
+
const queryString = queryParams.size > 0 ? '?' + queryParams.toString() : '';
|
|
15
|
+
const { res, obj, err } = await request(opt.apiEndpoint + '/copy/' + opt.projectId + '/version/' + opt.fromVersion + '/' + opt.toVersion + queryString, {
|
|
16
|
+
method: 'post',
|
|
17
|
+
headers: {
|
|
18
|
+
Authorization: opt.apiKey
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (err || (obj && (obj.errorMessage || obj.message))) {
|
|
23
|
+
console.log(colors.red(`copy failed from ${opt.fromVersion} to ${opt.toVersion}...`));
|
|
24
|
+
if (err) {
|
|
25
|
+
console.error(colors.red(err.message));
|
|
26
|
+
throw err
|
|
27
|
+
}
|
|
28
|
+
if (obj && (obj.errorMessage || obj.message)) {
|
|
29
|
+
console.error(colors.red((obj.errorMessage || obj.message)));
|
|
30
|
+
throw new Error((obj.errorMessage || obj.message))
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (res.status >= 300) {
|
|
34
|
+
console.error(colors.red(res.statusText + ' (' + res.status + ')'));
|
|
35
|
+
throw new Error(res.statusText + ' (' + res.status + ')')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!obj || !obj.jobId) {
|
|
39
|
+
console.error(colors.red('No jobId! Something went wrong!'));
|
|
40
|
+
throw new Error('No jobId! Something went wrong!')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let job;
|
|
44
|
+
while (true) {
|
|
45
|
+
job = await getJob(opt, obj.jobId);
|
|
46
|
+
if (job && !job.timeouted) {
|
|
47
|
+
await sleep(2000);
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
if (job && job.timeouted) {
|
|
51
|
+
console.error(colors.red('Job timeouted!'));
|
|
52
|
+
throw new Error('Job timeouted!')
|
|
53
|
+
}
|
|
54
|
+
break
|
|
55
|
+
}
|
|
56
|
+
console.log(colors.green(`copy from ${opt.fromVersion} to ${opt.toVersion} succesfully requested`));
|
|
57
|
+
// done
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
module.exports = copyVersion;
|