wuchale
Version:
Protobuf-like i18n from plain code
108 lines (107 loc) • 3.85 kB
JavaScript
const someHeuDet = { file: '', scope: 'markup' };
export class Message {
msgStr; // array for plurals
plural = false;
context;
comments = [];
details;
type = 'message';
constructor(msgStr, heuristicDetails = someHeuDet, context) {
if (typeof msgStr === 'string') {
this.msgStr = [msgStr];
}
else {
this.msgStr = msgStr.filter(str => str != null);
}
this.msgStr = this.msgStr.map(msg => msg.split('\n').map(line => line.trim()).join('\n'));
this.details = heuristicDetails;
this.context = context;
}
toKey = () => `${this.msgStr.slice(0, 2).join('\n')}\n${this.context ?? ''}`.trim();
}
export const defaultHeuristicOpts = {
ignoreElements: ['script', 'style', 'path', 'code', 'pre'],
ignoreAttribs: [['form', 'method']],
ignoreCalls: ['fetch'],
urlAttribs: [['a', 'href']],
urlCalls: [],
};
export function createHeuristic(opts) {
return msg => {
if (msg.details.element && opts.ignoreElements.includes(msg.details.element)) {
return false;
}
if (msg.details.scope === 'attribute') {
for (const [element, attrib] of opts.ignoreAttribs) {
if (msg.details.element === element && msg.details.attribute === attrib) {
return false;
}
}
}
const msgStr = msg.msgStr.join('\n');
const looksLikeUrlPath = msgStr.startsWith('/') && !msgStr.includes(' ');
if (looksLikeUrlPath && (msg.details.scope === 'script' || msg.details.scope === 'attribute')) {
if (msg.details.call) {
for (const call of opts.urlCalls) {
if (msg.details.call === call) {
return 'url';
}
}
}
if (msg.details.attribute) {
for (const [element, attrib] of opts.urlAttribs) {
if (msg.details.element === element && msg.details.attribute === attrib) {
return 'url';
}
}
}
}
if (!/\p{L}/u.test(msgStr)) {
return false;
}
if (msg.details.scope === 'markup') {
return 'message';
}
// script and attribute
// ignore:
// non-letter beginnings
// lower-case English letter beginnings
// containing only upper-case English and non-letters
if (!/\p{L}/u.test(msgStr[0]) || /[a-z]/.test(msgStr[0]) || /^([A-Z]|\P{L})+$/u.test(msgStr)) {
return false;
}
if (msg.details.scope === 'attribute') {
return 'message';
}
if (msg.details.declaring === 'expression' && !msg.details.funcName) {
return false;
}
if (!msg.details.call || !msg.details.call.startsWith('console.') && !opts.ignoreCalls.includes(msg.details.call)) {
return 'message';
}
return false;
};
}
/** Default heuristic */
export const defaultHeuristic = createHeuristic(defaultHeuristicOpts);
/** Default heuristic which ignores messages outside functions in the `script` scope */
export const defaultHeuristicFuncOnly = msg => {
if (defaultHeuristic(msg) && (msg.details.scope !== 'script' || msg.details.funcName != null)) {
return 'message';
}
return false;
};
export const defaultGenerateLoadID = (filename) => filename.replace(/[^a-zA-Z0-9_]+/g, '_');
export class IndexTracker {
indices = {};
nextIndex = 0;
get = (msgStr) => {
if (msgStr in this.indices) {
return this.indices[msgStr];
}
const index = this.nextIndex;
this.indices[msgStr] = index;
this.nextIndex++;
return index;
};
}