2 * Number-related utilities for mediawiki.language.
10 * Pad a string to guarantee that it is at least `size` length by
11 * filling with the character `ch` at either the start or end of the
12 * string. Pads at the start, by default.
14 * Example: Fill the string to length 10 with '+' characters on the right.
16 * pad( 'blah', 10, '+', true ); // => 'blah++++++'
19 * @param {string} text The string to pad
20 * @param {number} size The length to pad to
21 * @param {string} [ch='0'] Character to pad with
22 * @param {boolean} [end=false] Adds padding at the end if true, otherwise pads at start
25 function pad( text, size, ch, end ) {
30 var out = String( text ),
31 padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) );
33 return end ? out + padStr : padStr + out;
37 * Replicate a string 'n' times.
40 * @param {string} str The string to replicate
41 * @param {number} num Number of times to replicate the string
44 function replicate( str, num ) {
45 if ( num <= 0 || !str ) {
53 return buf.join( '' );
57 * Apply numeric pattern to absolute value using options. Gives no
58 * consideration to local customs.
60 * Adapted from dojo/number library with thanks
61 * <http://dojotoolkit.org/reference-guide/1.8/dojo/number.html>
64 * @param {number} value the number to be formatted, ignores sign
65 * @param {string} pattern the number portion of a pattern (e.g. `#,##0.00`)
66 * @param {Object} [options] If provided, both option keys must be present:
67 * @param {string} options.decimal The decimal separator. Defaults to: `'.'`.
68 * @param {string} options.group The group separator. Defaults to: `','`.
71 function commafyNumber( value, pattern, options ) {
72 options = options || {
77 if ( isNaN( value) ) {
87 patternParts = pattern.split( '.' ),
88 maxPlaces = ( patternParts[1] || [] ).length,
89 valueParts = String( Math.abs( value ) ).split( '.' ),
90 fractional = valueParts[1] || '',
95 if ( patternParts[1] ) {
96 // Pad fractional with trailing zeros
97 padLength = ( patternParts[1] && patternParts[1].lastIndexOf( '0' ) + 1 );
99 if ( padLength > fractional.length ) {
100 valueParts[1] = pad( fractional, padLength, '0', true );
103 // Truncate fractional
104 if ( maxPlaces < fractional.length ) {
105 valueParts[1] = fractional.slice( 0, maxPlaces );
108 if ( valueParts[1] ) {
113 // Pad whole with leading zeros
114 patternDigits = patternParts[0].replace( ',', '' );
116 padLength = patternDigits.indexOf( '0' );
118 if ( padLength !== -1 ) {
119 padLength = patternDigits.length - padLength;
121 if ( padLength > valueParts[0].length ) {
122 valueParts[0] = pad( valueParts[0], padLength );
126 if ( patternDigits.indexOf( '#' ) === -1 ) {
127 valueParts[0] = valueParts[0].slice( valueParts[0].length - padLength );
131 // Add group separators
132 index = patternParts[0].lastIndexOf( ',' );
134 if ( index !== -1 ) {
135 groupSize = patternParts[0].length - index - 1;
136 remainder = patternParts[0].slice( 0, index );
137 index = remainder.lastIndexOf( ',' );
138 if ( index !== -1 ) {
139 groupSize2 = remainder.length - index - 1;
143 for ( whole = valueParts[0]; whole; ) {
144 off = groupSize ? whole.length - groupSize : 0;
145 pieces.push( ( off > 0 ) ? whole.slice( off ) : whole );
146 whole = ( off > 0 ) ? whole.slice( 0, off ) : '';
149 groupSize = groupSize2;
153 valueParts[0] = pieces.reverse().join( options.group );
155 return valueParts.join( options.decimal );
158 $.extend( mw.language, {
161 * Converts a number using #getDigitTransformTable.
163 * @param {number} num Value to be converted
164 * @param {boolean} [integer=false] Whether to convert the return value to an integer
165 * @return {number|string} Formatted number
167 convertNumber: function ( num, integer ) {
168 var i, tmp, transformTable, numberString, convertedNumber, pattern;
170 pattern = mw.language.getData( mw.config.get( 'wgUserLanguage' ),
171 'digitGroupingPattern' ) || '#,##0.###';
173 // Set the target transform table:
174 transformTable = mw.language.getDigitTransformTable();
176 if ( !transformTable ) {
180 // Check if the 'restore' to Latin number flag is set:
182 if ( parseInt( num, 10 ) === num ) {
186 for ( i in transformTable ) {
187 tmp[ transformTable[ i ] ] = i;
189 transformTable = tmp;
190 numberString = num + '';
192 numberString = mw.language.commafy( num, pattern );
195 convertedNumber = '';
196 for ( i = 0; i < numberString.length; i++ ) {
197 if ( transformTable[ numberString[i] ] ) {
198 convertedNumber += transformTable[numberString[i]];
200 convertedNumber += numberString[i];
203 return integer ? parseInt( convertedNumber, 10 ) : convertedNumber;
207 * Get the digit transform table for current UI language.
208 * @return {Object|Array}
210 getDigitTransformTable: function () {
211 return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
212 'digitTransformTable' ) || [];
216 * Get the separator transform table for current UI language.
217 * @return {Object|Array}
219 getSeparatorTransformTable: function () {
220 return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
221 'separatorTransformTable' ) || [];
225 * Apply pattern to format value as a string.
227 * Using patterns from [Unicode TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns).
229 * @param {number} value
230 * @param {string} pattern Pattern string as described by Unicode TR35
231 * @throws {Error} If unable to find a number expression in `pattern`.
234 commafy: function ( value, pattern ) {
236 transformTable = mw.language.getSeparatorTransformTable(),
237 group = transformTable[','] || ',',
238 numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/, // not precise, but good enough
239 decimal = transformTable['.'] || '.',
240 patternList = pattern.split( ';' ),
241 positivePattern = patternList[0];
243 pattern = patternList[ ( value < 0 ) ? 1 : 0] || ( '-' + positivePattern );
244 numberPattern = positivePattern.match( numberPatternRE );
246 if ( !numberPattern ) {
247 throw new Error( 'unable to find a number expression in pattern: ' + pattern );
250 return pattern.replace( numberPatternRE, commafyNumber( value, numberPattern[0], {
258 }( mediaWiki, jQuery ) );