locize-cli
Version:
locize cli to import locales
155 lines (135 loc) • 4.96 kB
JavaScript
const yaml = require('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 = yaml.stringify(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 yaml.parse(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
};