-
-
Notifications
You must be signed in to change notification settings - Fork 19
/
combineSubkeyPreprocessor.js
154 lines (135 loc) · 4.85 KB
/
combineSubkeyPreprocessor.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
const jsyaml = require('js-yaml');
const delimiter = {
i18next: '_',
i18njs: '.'
};
const detectFormat = (keys) => {
const i18nextMatches = keys.filter((k) => k.indexOf(delimiter.i18next) > 0).length;
const i18njsMatches = keys.filter((k) => k.indexOf(delimiter.i18njs) > 0).length;
if (i18nextMatches > i18njsMatches) {
return 'i18next';
}
if (i18nextMatches < i18njsMatches) {
return 'i18njs';
}
};
const getBaseKey = (delimiter) => (k) => {
const parts = k.split(delimiter);
parts.pop();
const baseKey = parts.join(delimiter);
return baseKey;
};
const uniq = (value, index, self) => self.indexOf(value) === index;
const stringify = (o) => {
let str = jsyaml.dump(o);
const subKeys = Object.keys(o);
subKeys.forEach((sk) => {
if (isNaN(sk)) {
str = str.replace(new RegExp(`^(?:${sk}: )+`, 'm'), `{${sk}}: `);
} else {
str = str.replace(new RegExp(`^(?:'${sk}': )+`, 'm'), `{${sk}}: `);
}
});
return str;
};
const transformKeys = (segments, baseKeys, toMerge, deli) => {
baseKeys.forEach((bk) => {
const asObj = toMerge[bk].reduce((mem, k) => {
const subKey = k.substring((bk + deli).length);
// special handling for i18next v3
if (deli === delimiter.i18next && subKey === 'plural' && segments[bk]) {
mem['__'] = segments[bk];
delete segments[bk];
}
mem[subKey] = segments[k];
return mem;
}, {});
if (Object.keys(asObj).length > 0) {
const value = stringify(asObj);
segments[`${bk}__#locize.com/combinedSubkey`] = value;
toMerge[bk].forEach((k) => {
delete segments[k];
});
}
});
return segments;
};
// CLDR
const pluralForms = [
'zero',
'one',
'two',
'few',
'many',
'other'
];
const endsWithPluralForm = (k) => !!pluralForms.find((f) => k.endsWith(`.${f}`)) || !!pluralForms.find((f) => k.endsWith(`_${f}`)) || /_\d+$/.test(k) || k.endsWith('_plural');
const prepareExport = (refRes, trgRes) => {
const refLngKeys = Object.keys(refRes);
const trgLngKeys = Object.keys(trgRes);
const nonMatchInRef = refLngKeys.filter((k) => trgLngKeys.indexOf(k) < 0 && endsWithPluralForm(k));
const nonMatchInTrg = trgLngKeys.filter((k) => refLngKeys.indexOf(k) < 0 && endsWithPluralForm(k));
const allMatches = nonMatchInRef.concat(nonMatchInTrg);
const format = detectFormat(allMatches);
if (!format) return { ref: refRes, trg: trgRes };
const nonMatchBaseKeysInRef = nonMatchInRef.map(getBaseKey(delimiter[format])).filter(uniq);
const nonMatchBaseKeysInTrg = nonMatchInTrg.map(getBaseKey(delimiter[format])).filter(uniq);
const nonMatchBaseKeys = nonMatchBaseKeysInRef.concat(nonMatchBaseKeysInTrg).filter(uniq);
const toMergeInRef = nonMatchBaseKeys.reduce((mem, bk) => {
mem[bk] = refLngKeys.filter((k) => k.indexOf(bk + delimiter[format]) === 0);
return mem;
}, {});
const toMergeInTrg = nonMatchBaseKeys.reduce((mem, bk) => {
mem[bk] = trgLngKeys.filter((k) => k.indexOf(bk + delimiter[format]) === 0);
return mem;
}, {});
let falseFlags = nonMatchBaseKeysInRef.filter((k) => toMergeInRef[k].length < 2 && (!toMergeInTrg[k] || toMergeInTrg[k].length < 2));
falseFlags = falseFlags.concat(nonMatchBaseKeysInTrg.filter((k) => toMergeInTrg[k].length < 2 && (!toMergeInRef[k] || toMergeInRef[k].length < 2)));
falseFlags.forEach((k) => {
delete toMergeInRef[k];
delete toMergeInTrg[k];
nonMatchBaseKeys.splice(nonMatchBaseKeys.indexOf(k), 1);
});
const transformedRef = transformKeys(refRes, nonMatchBaseKeys, toMergeInRef, delimiter[format]);
const transformedTrg = transformKeys(trgRes, nonMatchBaseKeys, toMergeInTrg, delimiter[format]);
return { ref: transformedRef, trg: transformedTrg };
};
const skRegex = new RegExp('^(?:{(.+)})+', 'gm');
const parse = (s) => {
let matchArray;
while ((matchArray = skRegex.exec(s)) !== null) {
const [match, sk] = matchArray;
if (isNaN(sk)) {
s = s.replace(new RegExp(`^(?:${match}: )+`, 'm'), `${sk}: `);
} else {
const escapedMatch = match.replace('{', '\\{').replace('}', '\\}');
s = s.replace(new RegExp(`^(?:${escapedMatch}: )+`, 'm'), `${sk}: `);
}
}
return jsyaml.load(s);
};
const prepareImport = (resources) => {
const keys = Object.keys(resources);
keys.forEach((k) => {
if (k.indexOf('__#locize.com/combinedSubkey') > -1) {
const baseKey = k.substring(0, k.indexOf('__#locize.com/combinedSubkey'));
if (resources[k]) {
const parsed = parse(resources[k]);
Object.keys(parsed).map((sk) => {
const skVal = parsed[sk];
resources[`${baseKey}_${sk}`] = skVal;
if (sk === '__') {
resources[baseKey] = resources[`${baseKey}_${sk}`];
delete resources[`${baseKey}_${sk}`];
}
});
delete resources[k];
}
}
});
return resources;
};
module.exports = {
prepareExport,
prepareImport
};