Rank aliases in search in order they appear in the messages file.
[lhc/web/wiklou.git] / resources / src / mediawiki.language / mediawiki.language.numbers.js
1 /*
2 * Number-related utilities for mediawiki.language.
3 */
4 ( function ( mw, $ ) {
5 /**
6 * @class mw.language
7 */
8
9 /**
10 * Replicate a string 'n' times.
11 *
12 * @private
13 * @param {string} str The string to replicate
14 * @param {number} num Number of times to replicate the string
15 * @return {string}
16 */
17 function replicate( str, num ) {
18 var buf = [];
19
20 if ( num <= 0 || !str ) {
21 return '';
22 }
23
24 while ( num-- ) {
25 buf.push( str );
26 }
27 return buf.join( '' );
28 }
29
30 /**
31 * Pad a string to guarantee that it is at least `size` length by
32 * filling with the character `ch` at either the start or end of the
33 * string. Pads at the start, by default.
34 *
35 * Example: Fill the string to length 10 with '+' characters on the right.
36 *
37 * pad( 'blah', 10, '+', true ); // => 'blah++++++'
38 *
39 * @private
40 * @param {string} text The string to pad
41 * @param {number} size The length to pad to
42 * @param {string} [ch='0'] Character to pad with
43 * @param {boolean} [end=false] Adds padding at the end if true, otherwise pads at start
44 * @return {string}
45 */
46 function pad( text, size, ch, end ) {
47 var out, padStr;
48
49 if ( !ch ) {
50 ch = '0';
51 }
52
53 out = String( text );
54 padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) );
55
56 return end ? out + padStr : padStr + out;
57 }
58
59 /**
60 * Apply numeric pattern to absolute value using options. Gives no
61 * consideration to local customs.
62 *
63 * Adapted from dojo/number library with thanks
64 * <http://dojotoolkit.org/reference-guide/1.8/dojo/number.html>
65 *
66 * @private
67 * @param {number} value the number to be formatted, ignores sign
68 * @param {string} pattern the number portion of a pattern (e.g. `#,##0.00`)
69 * @param {Object} [options] If provided, both option keys must be present:
70 * @param {string} options.decimal The decimal separator. Defaults to: `'.'`.
71 * @param {string} options.group The group separator. Defaults to: `','`.
72 * @return {string}
73 */
74 function commafyNumber( value, pattern, options ) {
75 var padLength,
76 patternDigits,
77 index,
78 whole,
79 off,
80 remainder,
81 patternParts = pattern.split( '.' ),
82 maxPlaces = ( patternParts[ 1 ] || [] ).length,
83 valueParts = String( Math.abs( value ) ).split( '.' ),
84 fractional = valueParts[ 1 ] || '',
85 groupSize = 0,
86 groupSize2 = 0,
87 pieces = [];
88
89 options = options || {
90 group: ',',
91 decimal: '.'
92 };
93
94 if ( isNaN( value ) ) {
95 return value;
96 }
97
98 if ( patternParts[ 1 ] ) {
99 // Pad fractional with trailing zeros
100 padLength = ( patternParts[ 1 ] && patternParts[ 1 ].lastIndexOf( '0' ) + 1 );
101
102 if ( padLength > fractional.length ) {
103 valueParts[ 1 ] = pad( fractional, padLength, '0', true );
104 }
105
106 // Truncate fractional
107 if ( maxPlaces < fractional.length ) {
108 valueParts[ 1 ] = fractional.slice( 0, maxPlaces );
109 }
110 } else {
111 if ( valueParts[ 1 ] ) {
112 valueParts.pop();
113 }
114 }
115
116 // Pad whole with leading zeros
117 patternDigits = patternParts[ 0 ].replace( ',', '' );
118
119 padLength = patternDigits.indexOf( '0' );
120
121 if ( padLength !== -1 ) {
122 padLength = patternDigits.length - padLength;
123
124 if ( padLength > valueParts[ 0 ].length ) {
125 valueParts[ 0 ] = pad( valueParts[ 0 ], padLength );
126 }
127
128 // Truncate whole
129 if ( patternDigits.indexOf( '#' ) === -1 ) {
130 valueParts[ 0 ] = valueParts[ 0 ].slice( valueParts[ 0 ].length - padLength );
131 }
132 }
133
134 // Add group separators
135 index = patternParts[ 0 ].lastIndexOf( ',' );
136
137 if ( index !== -1 ) {
138 groupSize = patternParts[ 0 ].length - index - 1;
139 remainder = patternParts[ 0 ].slice( 0, index );
140 index = remainder.lastIndexOf( ',' );
141 if ( index !== -1 ) {
142 groupSize2 = remainder.length - index - 1;
143 }
144 }
145
146 for ( whole = valueParts[ 0 ]; whole; ) {
147 off = groupSize ? whole.length - groupSize : 0;
148 pieces.push( ( off > 0 ) ? whole.slice( off ) : whole );
149 whole = ( off > 0 ) ? whole.slice( 0, off ) : '';
150
151 if ( groupSize2 ) {
152 groupSize = groupSize2;
153 groupSize2 = null;
154 }
155 }
156 valueParts[ 0 ] = pieces.reverse().join( options.group );
157
158 return valueParts.join( options.decimal );
159 }
160
161 $.extend( mw.language, {
162
163 /**
164 * Converts a number using #getDigitTransformTable.
165 *
166 * @param {number} num Value to be converted
167 * @param {boolean} [integer=false] Whether to convert the return value to an integer
168 * @return {number|string} Formatted number
169 */
170 convertNumber: function ( num, integer ) {
171 var i, tmp, transformTable, numberString, convertedNumber, pattern;
172
173 pattern = mw.language.getData( mw.config.get( 'wgUserLanguage' ),
174 'digitGroupingPattern' ) || '#,##0.###';
175
176 // Set the target transform table:
177 transformTable = mw.language.getDigitTransformTable();
178
179 if ( !transformTable ) {
180 return num;
181 }
182
183 // Check if the 'restore' to Latin number flag is set:
184 if ( integer ) {
185 if ( parseInt( num, 10 ) === num ) {
186 return num;
187 }
188 tmp = [];
189 for ( i in transformTable ) {
190 tmp[ transformTable[ i ] ] = i;
191 }
192 transformTable = tmp;
193 numberString = String( num );
194 } else {
195 // Ignore transform table if wgTranslateNumerals is false
196 if ( !mw.config.get( 'wgTranslateNumerals' ) ) {
197 transformTable = [];
198 }
199 numberString = mw.language.commafy( num, pattern );
200 }
201
202 convertedNumber = '';
203 for ( i = 0; i < numberString.length; i++ ) {
204 if ( transformTable[ numberString[ i ] ] ) {
205 convertedNumber += transformTable[ numberString[ i ] ];
206 } else {
207 convertedNumber += numberString[ i ];
208 }
209 }
210 return integer ? parseInt( convertedNumber, 10 ) : convertedNumber;
211 },
212
213 /**
214 * Get the digit transform table for current UI language.
215 *
216 * @return {Object|Array}
217 */
218 getDigitTransformTable: function () {
219 return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
220 'digitTransformTable' ) || [];
221 },
222
223 /**
224 * Get the separator transform table for current UI language.
225 *
226 * @return {Object|Array}
227 */
228 getSeparatorTransformTable: function () {
229 return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
230 'separatorTransformTable' ) || [];
231 },
232
233 /**
234 * Apply pattern to format value as a string.
235 *
236 * Using patterns from [Unicode TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns).
237 *
238 * @param {number} value
239 * @param {string} pattern Pattern string as described by Unicode TR35
240 * @throws {Error} If unable to find a number expression in `pattern`.
241 * @return {string}
242 */
243 commafy: function ( value, pattern ) {
244 var numberPattern,
245 transformTable = mw.language.getSeparatorTransformTable(),
246 group = transformTable[ ',' ] || ',',
247 numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/, // not precise, but good enough
248 decimal = transformTable[ '.' ] || '.',
249 patternList = pattern.split( ';' ),
250 positivePattern = patternList[ 0 ];
251
252 pattern = patternList[ ( value < 0 ) ? 1 : 0 ] || ( '-' + positivePattern );
253 numberPattern = positivePattern.match( numberPatternRE );
254
255 if ( !numberPattern ) {
256 throw new Error( 'unable to find a number expression in pattern: ' + pattern );
257 }
258
259 return pattern.replace( numberPatternRE, commafyNumber( value, numberPattern[ 0 ], {
260 decimal: decimal,
261 group: group
262 } ) );
263 }
264
265 } );
266
267 }( mediaWiki, jQuery ) );