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