com.force.i18n.grammar.offline.grammaticus.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grammaticus Show documentation
Show all versions of grammaticus Show documentation
Localization Framework that allows grammatically correct renaming of nouns
// Grammaticus.js
/*
* Copyright (c) 2019, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
var Grammaticus = function() {
};
Grammaticus.prototype = {
NOUN_TYPE: 'n',
ADJ_TYPE: 'a',
ART_TYPE: 'd',
GENDER_TYPE: 'g',
PLURAL_TYPE: 'p',
COUNTER_TYPE: 'c',
MISSING: 'MISSING',
parts: function() {
return this._parts;
},
isString: function (obj) {
return (typeof obj === 'string');
},
getLabel: function(label, nouns, args) {
if (!this._labels) {
return this.MISSING;
}
var str = this._labels[label];
if (!str) {
return this.MISSING;
}
return this.replaceTerms(str, nouns, args);
},
getString: function(label, nouns, args) {
return this.formatArgs(this.getLabel(label, nouns, args), args);
},
/**
* Format the message with the given arguments and renameable nouns.
* @param messageObject is assumed to be an object with the parts in a predefined format
* @param args is an array of strings or numbers to substitute for various parts (if keyed by numbers) or a map if keyed by name
* Note: The Grammaticus object will be per-language and will not be
*
*/
replaceTerms: function(messageObject, nouns, args) {
var str = messageObject;
if (Array.isArray(messageObject)) {
str = '';
for (var i = 0; i < messageObject.length; i += 1) {
var part = messageObject[i];
if (this.isString(part)) {
str += part;
} else {
// It's an object, figure out what.
str += this.formatTerm(part, nouns, args, messageObject);
}
}
} else {
str = '' + messageObject;
}
return str;
},
getNoun: function(noun, form) {
return noun && noun.v[form];
},
getModifier: function(modifier, form) {
return modifier && modifier.v[form];
},
// OVERRIDE SECTION: These allow the individual declension to override
// Allow the child object to override
formLowercaseNoun: function(value, form) {
return !this.dont_capitalize ? value.toLowerCase() : value;
},
getModifierForm: function(termType, termForm, nounForm, noun, nextTerm) {
// This gets modified based on the noun per declension
return termForm;
},
getDefaultCounterWord: function() {
return "";
},
getPluralCategory: function(val) {
// Use the native if available.
if (typeof Intl !== 'undefined' && typeof Intl.PluralRules !== 'undefined') {
return new Intl.PluralRules(this.locale).select(val);
}
return 'other';
},
// END OVERRIDE SECTION
formatNounTerm: function (term, nouns) {
// If dynamic noun, i will be defined, but it could be 0
var nounStr;
if (term.i || term.i === 0) {
nounStr = nouns[term.i];
} else {
nounStr = term.l;
}
if (!nounStr) {
return '';
}
var noun = this.parts().n[nounStr.toLowerCase()];
if (!noun) {
return '';
}
var value = this.getNoun(noun, term.f);
if (value && !term.c) { // term.c = is capitalized
value = this.formLowercaseNoun(value, term.f);
}
return value;
},
formatModifierTerm: function (term, nouns, termList) {
var noun, value;
// Get the noun ref
var associatedNoun = termList[term.an];
if (associatedNoun === null) {
return ''; // If the adjective doesn't have any associated noun, return null to match java
}
// Get the relevant noun to see get the right value for StartsWith/Gender
if ((associatedNoun.i || associatedNoun.i === 0) && nouns) {
noun = this.parts().n[nouns[associatedNoun.i].toLowerCase()];
} else {
noun = this.parts().n[associatedNoun.l];
}
var nextTerm;
if (term.nt === term.an) {
nextTerm = noun;
} else {
var nextTermTag = termList[term.nt];
if (nextTermTag) {
nextTerm = this.parts()[nextTermTag.t][nextTermTag.l];
} else {
nextTerm = noun;
}
}
// GetForm
var form = this.getModifierForm(term.t, term.f, associatedNoun.f, noun, nextTerm);
var part = this.parts()[term.t];
if (!part) {
return ''; // Missing part, just return '' to match Java
}
value = this.getModifier(part[term.l], form);
if (value && !term.c) {
value = value.toLowerCase(); // This isn't 100% correct
}
return value;
},
formatGenderTerm: function (term, nouns, args, termList) {
var noun, associatedNoun = termList[term.an];
if (associatedNoun === null) {
return this.replaceTerms(term.def, nouns); // If we can't find the noun, default
}
// Get the relevant noun to see get the right value for StartsWith/Gender
if ((associatedNoun.i || associatedNoun.i === 0) && nouns) {
noun = this.parts().n[nouns[associatedNoun.i].toLowerCase()];
} else {
noun = this.parts().n[associatedNoun.l];
}
if (!noun) {
return this.replaceTerms(term.def, nouns); // If we can't find the noun, default
}
// Get the "per gender", and if it's defined, use that for replacement.
var perGender = term.v[noun.g];
return this.replaceTerms(perGender != null ? perGender : term.def, nouns, args);
},
formatPluralTerm: function (term, nouns, args) {
// TODO: Implement once we know what Intl looks like.
var result = term.def;
var arg = args[term.i];
// Allow override for zero if provided
if (arg === 0 && term.v.zero) {
result = term.v.zero;
} else {
var type = this.getPluralCategory(arg);
if (term.v && term.v[type]) {
result = term.v[type];
}
}
return this.replaceTerms(result, nouns);
},
formatCounterTerm: function (term, nouns, termList) {
var noun, associatedNoun = termList[term.an];
if (associatedNoun === null) {
return this.getDefaultCounterWord(); // If we can't find the noun, default
}
// Get the relevant noun to see get the right value for StartsWith/Gender
if ((associatedNoun.i || associatedNoun.i === 0) && nouns) {
noun = this.parts().n[nouns[associatedNoun.i].toLowerCase()];
} else {
noun = this.parts().n[associatedNoun.l];
}
if (!noun) {
return this.getDefaultCounterWord(); // If we can't find the noun, default
}
return noun.c || this.getDefaultCounterWord();
},
// Switch for formalTerm based on type
formatTerm: function(term, nouns, args, termList) {
var value = '';
switch (term.t) {
case this.NOUN_TYPE: // noun
value = this.formatNounTerm(term, nouns);
break;
case this.ADJ_TYPE: // adjective
case this.ART_TYPE: // article
value = this.formatModifierTerm(term, nouns, termList);
break;
case this.GENDER_TYPE:
value = this.formatGenderTerm(term, nouns, args, termList);
break;
case this.PLURAL_TYPE:
value = this.formatPluralTerm(term, nouns, args);
break;
case this.COUNTER_TYPE:
value = this.formatCounterTerm(term, nouns, termList);
break;
default:
}
if (!value) {
return ''; // In case anything ends up null or undefined, return empty string to prevent "Null" leakage
}
return value;
},
formatArgs: function(msg, parameters) {
var f = function(str, match, offset, all) {
var obj = parameters[parseInt(match, 10)];
return Array.isArray(obj) ? obj[0] : obj; // rhino issue
};
var res = msg.replace(/\{([0-9]*)\}/g, f);
return res;
},
/**
* Dynamic components may need to add grammatical parts for their particular labels.
*/
addTerms: function(grammaticalParts) {
if (!this._parts) {
this._parts = grammaticalParts;
}
for (var type in grammaticalParts) {
for (var part in grammaticalParts[type]) {
this._parts[type][part] = grammaticalParts[type][part];
}
}
},
/**
* Dynamic components may need to add grammatical parts for their particular labels.
*/
addLabels: function(labels) {
if (!this._labels) {
this._labels = labels;
}
for (var k in labels) {
this._labels[k] = labels[k];
}
}
};
if (typeof module != 'undefined' && module.exports) {
module.exports = Grammaticus;
// Browser.
} else {
Grammaticus
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy