Initial commit

This commit is contained in:
2020-10-07 10:37:15 +02:00
commit ce5f440392
28157 changed files with 4429172 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
class LocalizationException {
constructor(message) {
this.message = message;
this.name = 'LocalizationException';
}
}
export default LocalizationException;

View File

@@ -0,0 +1,29 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
import NumberFormatter from './number-formatter';
import NumberSymbol from './number-symbol';
import PriceSpecification from './specifications/price';
import NumberSpecification from './specifications/number';
export {
PriceSpecification,
NumberSpecification,
NumberFormatter,
NumberSymbol,
};

View File

@@ -0,0 +1,312 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
/**
* These placeholders are used in CLDR number formatting templates.
* They are meant to be replaced by the correct localized symbols in the number formatting process.
*/
import NumberSymbol from './number-symbol';
import PriceSpecification from './specifications/price';
import NumberSpecification from './specifications/number';
const escapeRE = require('lodash.escaperegexp');
const CURRENCY_SYMBOL_PLACEHOLDER = '¤';
const DECIMAL_SEPARATOR_PLACEHOLDER = '.';
const GROUP_SEPARATOR_PLACEHOLDER = ',';
const MINUS_SIGN_PLACEHOLDER = '-';
const PERCENT_SYMBOL_PLACEHOLDER = '%';
const PLUS_SIGN_PLACEHOLDER = '+';
class NumberFormatter {
/**
* @param NumberSpecification specification Number specification to be used
* (can be a number spec, a price spec, a percentage spec)
*/
constructor(specification) {
this.numberSpecification = specification;
}
/**
* Formats the passed number according to specifications.
*
* @param int|float|string number The number to format
* @param NumberSpecification specification Number specification to be used
* (can be a number spec, a price spec, a percentage spec)
*
* @return string The formatted number
* You should use this this value for display, without modifying it
*/
format(number, specification) {
if (specification !== undefined) {
this.numberSpecification = specification;
}
/*
* We need to work on the absolute value first.
* Then the CLDR pattern will add the sign if relevant (at the end).
*/
const num = Math.abs(number).toFixed(this.numberSpecification.getMaxFractionDigits());
let [majorDigits, minorDigits] = this.extractMajorMinorDigits(num);
majorDigits = this.splitMajorGroups(majorDigits);
minorDigits = this.adjustMinorDigitsZeroes(minorDigits);
// Assemble the final number
let formattedNumber = majorDigits;
if (minorDigits) {
formattedNumber += DECIMAL_SEPARATOR_PLACEHOLDER + minorDigits;
}
// Get the good CLDR formatting pattern. Sign is important here !
const pattern = this.getCldrPattern(number < 0);
formattedNumber = this.addPlaceholders(formattedNumber, pattern);
formattedNumber = this.replaceSymbols(formattedNumber);
formattedNumber = this.performSpecificReplacements(formattedNumber);
return formattedNumber;
}
/**
* Get number's major and minor digits.
*
* Major digits are the "integer" part (before decimal separator),
* minor digits are the fractional part
* Result will be an array of exactly 2 items: [majorDigits, minorDigits]
*
* Usage example:
* list(majorDigits, minorDigits) = this.getMajorMinorDigits(decimalNumber);
*
* @param DecimalNumber number
*
* @return string[]
*/
extractMajorMinorDigits(number) {
// Get the number's major and minor digits.
const result = number.toString().split('.');
const majorDigits = result[0];
const minorDigits = (result[1] === undefined) ? '' : result[1];
return [majorDigits, minorDigits];
}
/**
* Splits major digits into groups.
*
* e.g.: Given the major digits "1234567", and major group size
* configured to 3 digits, the result would be "1 234 567"
*
* @param string majorDigits The major digits to be grouped
*
* @return string The grouped major digits
*/
splitMajorGroups(digit) {
if (!this.numberSpecification.isGroupingUsed()) {
return digit;
}
// Reverse the major digits, since they are grouped from the right.
const majorDigits = digit.split('').reverse();
// Group the major digits.
let groups = [];
groups.push(majorDigits.splice(0, this.numberSpecification.getPrimaryGroupSize()));
while (majorDigits.length) {
groups.push(majorDigits.splice(0, this.numberSpecification.getSecondaryGroupSize()));
}
// Reverse back the digits and the groups
groups = groups.reverse();
const newGroups = [];
groups.forEach((group) => {
newGroups.push(group.reverse().join(''));
});
// Reconstruct the major digits.
return newGroups.join(GROUP_SEPARATOR_PLACEHOLDER);
}
/**
* Adds or remove trailing zeroes, depending on specified min and max fraction digits numbers.
*
* @param string minorDigits Digits to be adjusted with (trimmed or padded) zeroes
*
* @return string The adjusted minor digits
*/
adjustMinorDigitsZeroes(minorDigits) {
let digit = minorDigits;
if (digit.length > this.numberSpecification.getMaxFractionDigits()) {
// Strip any trailing zeroes.
digit = digit.replace(/0+$/, '');
}
if (digit.length < this.numberSpecification.getMinFractionDigits()) {
// Re-add needed zeroes
digit = digit.padEnd(
this.numberSpecification.getMinFractionDigits(),
'0',
);
}
return digit;
}
/**
* Get the CLDR formatting pattern.
*
* @see http://cldr.unicode.org/translation/number-patterns
*
* @param bool isNegative If true, the negative pattern
* will be returned instead of the positive one
*
* @return string The CLDR formatting pattern
*/
getCldrPattern(isNegative) {
if (isNegative) {
return this.numberSpecification.getNegativePattern();
}
return this.numberSpecification.getPositivePattern();
}
/**
* Replace placeholder number symbols with relevant numbering system's symbols.
*
* @param string number
* The number to process
*
* @return string
* The number with replaced symbols
*/
replaceSymbols(number) {
const symbols = this.numberSpecification.getSymbol();
const map = {};
map[DECIMAL_SEPARATOR_PLACEHOLDER] = symbols.getDecimal();
map[GROUP_SEPARATOR_PLACEHOLDER] = symbols.getGroup();
map[MINUS_SIGN_PLACEHOLDER] = symbols.getMinusSign();
map[PERCENT_SYMBOL_PLACEHOLDER] = symbols.getPercentSign();
map[PLUS_SIGN_PLACEHOLDER] = symbols.getPlusSign();
return this.strtr(number, map);
}
/**
* strtr() for JavaScript
* Translate characters or replace substrings
*/
strtr(str, pairs) {
const substrs = Object.keys(pairs).map(escapeRE);
return str.split(RegExp(`(${substrs.join('|')})`))
.map(part => pairs[part] || part)
.join('');
}
/**
* Add missing placeholders to the number using the passed CLDR pattern.
*
* Missing placeholders can be the percent sign, currency symbol, etc.
*
* e.g. with a currency CLDR pattern:
* - Passed number (partially formatted): 1,234.567
* - Returned number: 1,234.567 ¤
* ("¤" symbol is the currency symbol placeholder)
*
* @see http://cldr.unicode.org/translation/number-patterns
*
* @param formattedNumber
* Number to process
* @param pattern
* CLDR formatting pattern to use
*
* @return string
*/
addPlaceholders(formattedNumber, pattern) {
/*
* Regex groups explanation:
* # : literal "#" character. Once.
* (,#+)* : any other "#" characters group, separated by ",". Zero to infinity times.
* 0 : literal "0" character. Once.
* (\.[0#]+)* : any combination of "0" and "#" characters groups, separated by '.'.
* Zero to infinity times.
*/
return pattern.replace(/#?(,#+)*0(\.[0#]+)*/, formattedNumber);
}
/**
* Perform some more specific replacements.
*
* Specific replacements are needed when number specification is extended.
* For instance, prices have an extended number specification in order to
* add currency symbol to the formatted number.
*
* @param string formattedNumber
*
* @return mixed
*/
performSpecificReplacements(formattedNumber) {
if (this.numberSpecification instanceof PriceSpecification) {
return formattedNumber
.split(CURRENCY_SYMBOL_PLACEHOLDER)
.join(this.numberSpecification.getCurrencySymbol());
}
return formattedNumber;
}
static build(specifications) {
let symbol;
if (undefined !== specifications.numberSymbols) {
symbol = new NumberSymbol(...specifications.numberSymbols);
} else {
symbol = new NumberSymbol(...specifications.symbol);
}
let specification;
if (specifications.currencySymbol) {
specification = new PriceSpecification(
specifications.positivePattern,
specifications.negativePattern,
symbol,
parseInt(specifications.maxFractionDigits, 10),
parseInt(specifications.minFractionDigits, 10),
specifications.groupingUsed,
specifications.primaryGroupSize,
specifications.secondaryGroupSize,
specifications.currencySymbol,
specifications.currencyCode,
);
} else {
specification = new NumberSpecification(
specifications.positivePattern,
specifications.negativePattern,
symbol,
parseInt(specifications.maxFractionDigits, 10),
parseInt(specifications.minFractionDigits, 10),
specifications.groupingUsed,
specifications.primaryGroupSize,
specifications.secondaryGroupSize,
);
}
return new NumberFormatter(specification);
}
}
export default NumberFormatter;

View File

@@ -0,0 +1,222 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
import LocalizationException from './exception/localization';
class NumberSymbol {
/**
* NumberSymbolList constructor.
*
* @param string decimal Decimal separator character
* @param string group Digits group separator character
* @param string list List elements separator character
* @param string percentSign Percent sign character
* @param string minusSign Minus sign character
* @param string plusSign Plus sign character
* @param string exponential Exponential character
* @param string superscriptingExponent Superscripting exponent character
* @param string perMille Permille sign character
* @param string infinity The infinity sign. Corresponds to the IEEE infinity bit pattern.
* @param string nan The NaN (Not A Number) sign. Corresponds to the IEEE NaN bit pattern.
*
* @throws LocalizationException
*/
constructor(
decimal,
group,
list,
percentSign,
minusSign,
plusSign,
exponential,
superscriptingExponent,
perMille,
infinity,
nan,
) {
this.decimal = decimal;
this.group = group;
this.list = list;
this.percentSign = percentSign;
this.minusSign = minusSign;
this.plusSign = plusSign;
this.exponential = exponential;
this.superscriptingExponent = superscriptingExponent;
this.perMille = perMille;
this.infinity = infinity;
this.nan = nan;
this.validateData();
}
/**
* Get the decimal separator.
*
* @return string
*/
getDecimal() {
return this.decimal;
}
/**
* Get the digit groups separator.
*
* @return string
*/
getGroup() {
return this.group;
}
/**
* Get the list elements separator.
*
* @return string
*/
getList() {
return this.list;
}
/**
* Get the percent sign.
*
* @return string
*/
getPercentSign() {
return this.percentSign;
}
/**
* Get the minus sign.
*
* @return string
*/
getMinusSign() {
return this.minusSign;
}
/**
* Get the plus sign.
*
* @return string
*/
getPlusSign() {
return this.plusSign;
}
/**
* Get the exponential character.
*
* @return string
*/
getExponential() {
return this.exponential;
}
/**
* Get the exponent character.
*
* @return string
*/
getSuperscriptingExponent() {
return this.superscriptingExponent;
}
/**
* Gert the per mille symbol (often "‰").
*
* @see https://en.wikipedia.org/wiki/Per_mille
*
* @return string
*/
getPerMille() {
return this.perMille;
}
/**
* Get the infinity symbol (often "∞").
*
* @see https://en.wikipedia.org/wiki/Infinity_symbol
*
* @return string
*/
getInfinity() {
return this.infinity;
}
/**
* Get the NaN (not a number) sign.
*
* @return string
*/
getNan() {
return this.nan;
}
/**
* Symbols list validation.
*
* @throws LocalizationException
*/
validateData() {
if (!this.decimal || typeof this.decimal !== 'string') {
throw new LocalizationException('Invalid decimal');
}
if (!this.group || typeof this.group !== 'string') {
throw new LocalizationException('Invalid group');
}
if (!this.list || typeof this.list !== 'string') {
throw new LocalizationException('Invalid symbol list');
}
if (!this.percentSign || typeof this.percentSign !== 'string') {
throw new LocalizationException('Invalid percentSign');
}
if (!this.minusSign || typeof this.minusSign !== 'string') {
throw new LocalizationException('Invalid minusSign');
}
if (!this.plusSign || typeof this.plusSign !== 'string') {
throw new LocalizationException('Invalid plusSign');
}
if (!this.exponential || typeof this.exponential !== 'string') {
throw new LocalizationException('Invalid exponential');
}
if (!this.superscriptingExponent || typeof this.superscriptingExponent !== 'string') {
throw new LocalizationException('Invalid superscriptingExponent');
}
if (!this.perMille || typeof this.perMille !== 'string') {
throw new LocalizationException('Invalid perMille');
}
if (!this.infinity || typeof this.infinity !== 'string') {
throw new LocalizationException('Invalid infinity');
}
if (!this.nan || typeof this.nan !== 'string') {
throw new LocalizationException('Invalid nan');
}
}
}
export default NumberSymbol;

View File

@@ -0,0 +1,170 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
import LocalizationException from '../exception/localization';
import NumberSymbol from '../number-symbol';
class NumberSpecification {
/**
* Number specification constructor.
*
* @param string positivePattern CLDR formatting pattern for positive amounts
* @param string negativePattern CLDR formatting pattern for negative amounts
* @param NumberSymbol symbol Number symbol
* @param int maxFractionDigits Maximum number of digits after decimal separator
* @param int minFractionDigits Minimum number of digits after decimal separator
* @param bool groupingUsed Is digits grouping used ?
* @param int primaryGroupSize Size of primary digits group in the number
* @param int secondaryGroupSize Size of secondary digits group in the number
*
* @throws LocalizationException
*/
constructor(
positivePattern,
negativePattern,
symbol,
maxFractionDigits,
minFractionDigits,
groupingUsed,
primaryGroupSize,
secondaryGroupSize,
) {
this.positivePattern = positivePattern;
this.negativePattern = negativePattern;
this.symbol = symbol;
this.maxFractionDigits = maxFractionDigits;
// eslint-disable-next-line
this.minFractionDigits = maxFractionDigits < minFractionDigits ? maxFractionDigits : minFractionDigits;
this.groupingUsed = groupingUsed;
this.primaryGroupSize = primaryGroupSize;
this.secondaryGroupSize = secondaryGroupSize;
if (!this.positivePattern || typeof this.positivePattern !== 'string') {
throw new LocalizationException('Invalid positivePattern');
}
if (!this.negativePattern || typeof this.negativePattern !== 'string') {
throw new LocalizationException('Invalid negativePattern');
}
if (!this.symbol || !(this.symbol instanceof NumberSymbol)) {
throw new LocalizationException('Invalid symbol');
}
if (typeof this.maxFractionDigits !== 'number') {
throw new LocalizationException('Invalid maxFractionDigits');
}
if (typeof this.minFractionDigits !== 'number') {
throw new LocalizationException('Invalid minFractionDigits');
}
if (typeof this.groupingUsed !== 'boolean') {
throw new LocalizationException('Invalid groupingUsed');
}
if (typeof this.primaryGroupSize !== 'number') {
throw new LocalizationException('Invalid primaryGroupSize');
}
if (typeof this.secondaryGroupSize !== 'number') {
throw new LocalizationException('Invalid secondaryGroupSize');
}
}
/**
* Get symbol.
*
* @return NumberSymbol
*/
getSymbol() {
return this.symbol;
}
/**
* Get the formatting rules for this number (when positive).
*
* This pattern uses the Unicode CLDR number pattern syntax
*
* @return string
*/
getPositivePattern() {
return this.positivePattern;
}
/**
* Get the formatting rules for this number (when negative).
*
* This pattern uses the Unicode CLDR number pattern syntax
*
* @return string
*/
getNegativePattern() {
return this.negativePattern;
}
/**
* Get the maximum number of digits after decimal separator (rounding if needed).
*
* @return int
*/
getMaxFractionDigits() {
return this.maxFractionDigits;
}
/**
* Get the minimum number of digits after decimal separator (fill with "0" if needed).
*
* @return int
*/
getMinFractionDigits() {
return this.minFractionDigits;
}
/**
* Get the "grouping" flag. This flag defines if digits
* grouping should be used when formatting this number.
*
* @return bool
*/
isGroupingUsed() {
return this.groupingUsed;
}
/**
* Get the size of primary digits group in the number.
*
* @return int
*/
getPrimaryGroupSize() {
return this.primaryGroupSize;
}
/**
* Get the size of secondary digits groups in the number.
*
* @return int
*/
getSecondaryGroupSize() {
return this.secondaryGroupSize;
}
}
export default NumberSpecification;

View File

@@ -0,0 +1,109 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
import LocalizationException from '../exception/localization';
import NumberSpecification from './number';
/**
* Currency display option: symbol notation.
*/
const CURRENCY_DISPLAY_SYMBOL = 'symbol';
class PriceSpecification extends NumberSpecification {
/**
* Price specification constructor.
*
* @param string positivePattern CLDR formatting pattern for positive amounts
* @param string negativePattern CLDR formatting pattern for negative amounts
* @param NumberSymbol symbol Number symbol
* @param int maxFractionDigits Maximum number of digits after decimal separator
* @param int minFractionDigits Minimum number of digits after decimal separator
* @param bool groupingUsed Is digits grouping used ?
* @param int primaryGroupSize Size of primary digits group in the number
* @param int secondaryGroupSize Size of secondary digits group in the number
* @param string currencySymbol Currency symbol of this price (eg. : €)
* @param currencyCode Currency code of this price (e.g.: EUR)
*
* @throws LocalizationException
*/
constructor(
positivePattern,
negativePattern,
symbol,
maxFractionDigits,
minFractionDigits,
groupingUsed,
primaryGroupSize,
secondaryGroupSize,
currencySymbol,
currencyCode,
) {
super(
positivePattern,
negativePattern,
symbol,
maxFractionDigits,
minFractionDigits,
groupingUsed,
primaryGroupSize,
secondaryGroupSize,
);
this.currencySymbol = currencySymbol;
this.currencyCode = currencyCode;
if (!this.currencySymbol || typeof this.currencySymbol !== 'string') {
throw new LocalizationException('Invalid currencySymbol');
}
if (!this.currencyCode || typeof this.currencyCode !== 'string') {
throw new LocalizationException('Invalid currencyCode');
}
}
/**
* Get type of display for currency symbol.
*
* @return string
*/
static getCurrencyDisplay() {
return CURRENCY_DISPLAY_SYMBOL;
}
/**
* Get the currency symbol
* e.g.: €.
*
* @return string
*/
getCurrencySymbol() {
return this.currencySymbol;
}
/**
* Get the currency ISO code
* e.g.: EUR.
*
* @return string
*/
getCurrencyCode() {
return this.currencyCode;
}
}
export default PriceSpecification;