2 * Number-related utilities for mediawiki.language.
10 * Replicate a string 'n' times.
13 * @param {string} str The string to replicate
14 * @param {number} num Number of times to replicate the string
17 function replicate( str, num ) {
18 if ( num <= 0 || !str ) {
26 return buf.join( '' );
30 * Pad a string to guarantee that it is at least `size` length by
31 * filling with the character `ch` at either the start or end of the
32 * string. Pads at the start, by default.
34 * Example: Fill the string to length 10 with '+' characters on the right.
36 * pad( 'blah', 10, '+', true ); // => 'blah++++++'
39 * @param {string} text The string to pad
40 * @param {number} size The length to pad to
41 * @param {string} [ch='0'] Character to pad with
42 * @param {boolean} [end=false] Adds padding at the end if true, otherwise pads at start
45 function pad( text, size, ch, end ) {
50 var out = String( text ),
51 padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) );
53 return end ? out + padStr : padStr + out;
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 = String( num );
192 // Ignore transform table if wgTranslateNumerals is false
193 if ( !mw.config.get( 'wgTranslateNumerals' ) ) {
196 numberString = mw.language.commafy( num, pattern );
199 convertedNumber = '';
200 for ( i = 0; i < numberString.length; i++ ) {
201 if ( transformTable[ numberString[ i ] ] ) {
202 convertedNumber += transformTable[ numberString[ i ] ];
204 convertedNumber += numberString[ i ];
207 return integer ? parseInt( convertedNumber, 10 ) : convertedNumber;
211 * Get the digit transform table for current UI language.
213 * @return {Object|Array}
215 getDigitTransformTable: function () {
216 return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
217 'digitTransformTable' ) || [];
221 * Get the separator transform table for current UI language.
223 * @return {Object|Array}
225 getSeparatorTransformTable: function () {
226 return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
227 'separatorTransformTable' ) || [];
231 * Apply pattern to format value as a string.
233 * Using patterns from [Unicode TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns).
235 * @param {number} value
236 * @param {string} pattern Pattern string as described by Unicode TR35
237 * @throws {Error} If unable to find a number expression in `pattern`.
240 commafy: function ( value, pattern ) {
242 transformTable = mw.language.getSeparatorTransformTable(),
243 group = transformTable[ ',' ] || ',',
244 numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/, // not precise, but good enough
245 decimal = transformTable[ '.' ] || '.',
246 patternList = pattern.split( ';' ),
247 positivePattern = patternList[ 0 ];
249 pattern = patternList[ ( value < 0 ) ? 1 : 0 ] || ( '-' + positivePattern );
250 numberPattern = positivePattern.match( numberPatternRE );
252 if ( !numberPattern ) {
253 throw new Error( 'unable to find a number expression in pattern: ' + pattern );
256 return pattern.replace( numberPatternRE, commafyNumber( value, numberPattern[ 0 ], {
264 }( mediaWiki, jQuery ) );