Update Codex from v1.20.0 to v1.20.1
[mediawiki.git] / tests / phpunit / includes / language / LanguageIntegrationTest.php
blob91b9e9ca4fc9afe5632089e03486bffe34a9369e
1 <?php
3 use MediaWiki\Config\HashConfig;
4 use MediaWiki\Config\MultiConfig;
5 use MediaWiki\Config\ServiceOptions;
6 use MediaWiki\HookContainer\HookContainer;
7 use MediaWiki\Language\Language;
8 use MediaWiki\Languages\LanguageConverterFactory;
9 use MediaWiki\Languages\LanguageFallback;
10 use MediaWiki\Languages\LanguageNameUtils;
11 use MediaWiki\MainConfigNames;
12 use MediaWiki\MediaWikiServices;
13 use MediaWiki\Registration\ExtensionRegistry;
14 use MediaWiki\Tests\Unit\DummyServicesTrait;
15 use MediaWiki\Title\NamespaceInfo;
16 use MediaWiki\User\UserIdentityValue;
17 use Wikimedia\TestingAccessWrapper;
19 /**
20 * @group Language
21 * @covers \MediaWiki\Language\Language
22 * @covers \MediaWiki\Languages\LanguageNameUtils
24 class LanguageIntegrationTest extends LanguageClassesTestCase {
25 use DummyServicesTrait;
26 use LanguageNameUtilsTestTrait;
28 private function newLanguage( $class = Language::class, $code = 'en' ) {
29 // Needed to support the setMwGlobals calls for the various tests, but this should
30 // probably be changed to have the configuration injected into this method instead
31 // at some point
32 $config = $this->getServiceContainer()->getMainConfig();
33 return new $class(
34 $code,
35 $this->createNoOpMock( NamespaceInfo::class ),
36 $this->createNoOpMock( LocalisationCache::class ),
37 $this->createNoOpMock( LanguageNameUtils::class ),
38 $this->createNoOpMock( LanguageFallback::class ),
39 $this->createNoOpMock( LanguageConverterFactory::class ),
40 $this->createHookContainer(),
41 $config
45 protected function setUp(): void {
46 parent::setUp();
48 $this->overrideConfigValue( MainConfigNames::UsePigLatinVariant, true );
51 public function testLanguageConvertDoubleWidthToSingleWidth() {
52 $this->assertSame(
53 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
54 $this->getLang()->normalizeForSearch(
55 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
57 'convertDoubleWidth() with the full alphabet and digits'
61 /**
62 * @dataProvider provideFormattableTimes
64 public function testFormatTimePeriod( $seconds, $format, $expected, $desc ) {
65 $this->assertEquals( $expected, $this->getLang()->formatTimePeriod( $seconds, $format ), $desc );
68 public static function provideFormattableTimes() {
69 return [
71 9.45,
72 [],
73 '9.5 s',
74 'formatTimePeriod() rounding (<10s)'
77 9.45,
78 [ 'noabbrevs' => true ],
79 '9.5 seconds',
80 'formatTimePeriod() rounding (<10s)'
83 9.95,
84 [],
85 '10 s',
86 'formatTimePeriod() rounding (<10s)'
89 9.95,
90 [ 'noabbrevs' => true ],
91 '10 seconds',
92 'formatTimePeriod() rounding (<10s)'
95 59.55,
96 [],
97 '1 min 0 s',
98 'formatTimePeriod() rounding (<60s)'
101 59.55,
102 [ 'noabbrevs' => true ],
103 '1 minute 0 seconds',
104 'formatTimePeriod() rounding (<60s)'
107 119.55,
109 '2 min 0 s',
110 'formatTimePeriod() rounding (<1h)'
113 119.55,
114 [ 'noabbrevs' => true ],
115 '2 minutes 0 seconds',
116 'formatTimePeriod() rounding (<1h)'
119 3599.55,
121 '1 h 0 min 0 s',
122 'formatTimePeriod() rounding (<1h)'
125 3599.55,
126 [ 'noabbrevs' => true ],
127 '1 hour 0 minutes 0 seconds',
128 'formatTimePeriod() rounding (<1h)'
131 7199.55,
133 '2 h 0 min 0 s',
134 'formatTimePeriod() rounding (>=1h)'
137 7199.55,
138 [ 'noabbrevs' => true ],
139 '2 hours 0 minutes 0 seconds',
140 'formatTimePeriod() rounding (>=1h)'
143 7199.55,
144 'avoidseconds',
145 '2 h 0 min',
146 'formatTimePeriod() rounding (>=1h), avoidseconds'
149 7199.55,
150 [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ],
151 '2 hours 0 minutes',
152 'formatTimePeriod() rounding (>=1h), avoidseconds'
155 7199.55,
156 'avoidminutes',
157 '2 h 0 min',
158 'formatTimePeriod() rounding (>=1h), avoidminutes'
161 7199.55,
162 [ 'avoid' => 'avoidminutes', 'noabbrevs' => true ],
163 '2 hours 0 minutes',
164 'formatTimePeriod() rounding (>=1h), avoidminutes'
167 172799.55,
168 'avoidseconds',
169 '48 h 0 min',
170 'formatTimePeriod() rounding (=48h), avoidseconds'
173 172799.55,
174 [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ],
175 '48 hours 0 minutes',
176 'formatTimePeriod() rounding (=48h), avoidseconds'
179 259199.55,
180 'avoidhours',
181 '3 d',
182 'formatTimePeriod() rounding (>48h), avoidhours'
185 259199.55,
186 [ 'avoid' => 'avoidhours', 'noabbrevs' => true ],
187 '3 days',
188 'formatTimePeriod() rounding (>48h), avoidhours'
191 259199.55,
192 'avoidminutes',
193 '3 d 0 h',
194 'formatTimePeriod() rounding (>48h), avoidminutes'
197 259199.55,
198 [ 'avoid' => 'avoidminutes', 'noabbrevs' => true ],
199 '3 days 0 hours',
200 'formatTimePeriod() rounding (>48h), avoidminutes'
203 176399.55,
204 'avoidseconds',
205 '2 d 1 h 0 min',
206 'formatTimePeriod() rounding (>48h), avoidseconds'
209 176399.55,
210 [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ],
211 '2 days 1 hour 0 minutes',
212 'formatTimePeriod() rounding (>48h), avoidseconds'
215 176399.55,
216 'avoidminutes',
217 '2 d 1 h',
218 'formatTimePeriod() rounding (>48h), avoidminutes'
221 176399.55,
222 [ 'avoid' => 'avoidminutes', 'noabbrevs' => true ],
223 '2 days 1 hour',
224 'formatTimePeriod() rounding (>48h), avoidminutes'
227 259199.55,
228 'avoidseconds',
229 '3 d 0 h 0 min',
230 'formatTimePeriod() rounding (>48h), avoidseconds'
233 259199.55,
234 [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ],
235 '3 days 0 hours 0 minutes',
236 'formatTimePeriod() rounding (>48h), avoidseconds'
239 172801.55,
240 'avoidseconds',
241 '2 d 0 h 0 min',
242 'formatTimePeriod() rounding, (>48h), avoidseconds'
245 172801.55,
246 [ 'avoid' => 'avoidseconds', 'noabbrevs' => true ],
247 '2 days 0 hours 0 minutes',
248 'formatTimePeriod() rounding, (>48h), avoidseconds'
251 176460.55,
253 '2 d 1 h 1 min 1 s',
254 'formatTimePeriod() rounding, recursion, (>48h)'
257 176460.55,
258 [ 'noabbrevs' => true ],
259 '2 days 1 hour 1 minute 1 second',
260 'formatTimePeriod() rounding, recursion, (>48h)'
265 public function testTruncateForDatabase() {
266 $this->assertEquals(
267 "XXX",
268 $this->getLang()->truncateForDatabase( "1234567890", 0, 'XXX' ),
269 'truncate prefix, len 0, small ellipsis'
272 $this->assertEquals(
273 "12345XXX",
274 $this->getLang()->truncateForDatabase( "1234567890", 8, 'XXX' ),
275 'truncate prefix, small ellipsis'
278 $this->assertSame(
279 "123456789",
280 $this->getLang()->truncateForDatabase( "123456789", 5, 'XXXXXXXXXXXXXXX' ),
281 'truncate prefix, large ellipsis'
284 $this->assertEquals(
285 "XXX67890",
286 $this->getLang()->truncateForDatabase( "1234567890", -8, 'XXX' ),
287 'truncate suffix, small ellipsis'
290 $this->assertSame(
291 "123456789",
292 $this->getLang()->truncateForDatabase( "123456789", -5, 'XXXXXXXXXXXXXXX' ),
293 'truncate suffix, large ellipsis'
295 $this->assertEquals(
296 "123XXX",
297 $this->getLang()->truncateForDatabase( "123 ", 9, 'XXX' ),
298 'truncate prefix, with spaces'
300 $this->assertEquals(
301 "12345XXX",
302 $this->getLang()->truncateForDatabase( "12345 8", 11, 'XXX' ),
303 'truncate prefix, with spaces and non-space ending'
305 $this->assertEquals(
306 "XXX234",
307 $this->getLang()->truncateForDatabase( "1 234", -8, 'XXX' ),
308 'truncate suffix, with spaces'
310 $this->assertEquals(
311 "12345XXX",
312 $this->getLang()->truncateForDatabase( "1234567890", 5, 'XXX', false ),
313 'truncate without adjustment'
315 $this->assertEquals(
316 "泰乐菌...",
317 $this->getLang()->truncateForDatabase( "泰乐菌素123456789", 11, '...', false ),
318 'truncate does not chop Unicode characters in half'
320 $this->assertEquals(
321 "\n泰乐菌...",
322 $this->getLang()->truncateForDatabase( "\n泰乐菌素123456789", 12, '...', false ),
323 'truncate does not chop Unicode characters in half if there is a preceding newline'
328 * @dataProvider provideTruncateData
330 public function testTruncateForVisual(
331 $expected, $string, $length, $ellipsis = '...', $adjustLength = true
333 $this->assertEquals(
334 $expected,
335 $this->getLang()->truncateForVisual( $string, $length, $ellipsis, $adjustLength )
340 * @return array Format is ($expected, $string, $length, $ellipsis, $adjustLength)
342 public static function provideTruncateData() {
343 return [
344 [ "XXX", "тестирам да ли ради", 0, "XXX" ],
345 [ "testnXXX", "testni scenarij", 8, "XXX" ],
346 [ "حالة اختبار", "حالة اختبار", 5, "XXXXXXXXXXXXXXX" ],
347 [ "XXXедент", "прецедент", -8, "XXX" ],
348 [ "XXപിൾ", "ആപ്പിൾ", -5, "XX" ],
349 [ "神秘XXX", "神秘 ", 9, "XXX" ],
350 [ "ΔημιουργXXX", "Δημιουργία Σύμπαντος", 11, "XXX" ],
351 [ "XXXの家です", "地球は私たちの唯 の家です", -8, "XXX" ],
352 [ "زندگیXXX", "زندگی زیباست", 6, "XXX", false ],
353 [ "ცხოვრება...", "ცხოვრება არის საოცარი", 8, "...", false ],
354 [ "\nທ່ານ...", "\nທ່ານບໍ່ຮູ້ຫນັງສື", 5, "...", false ],
359 * @dataProvider provideHTMLTruncateData
361 public function testTruncateHtml( $len, $ellipsis, $input, $expected ) {
362 // Actual HTML...
363 $this->assertEquals(
364 $expected,
365 $this->getLang()->truncateHtml( $input, $len, $ellipsis )
370 * @return array Format is ($len, $ellipsis, $input, $expected)
372 public static function provideHTMLTruncateData() {
373 return [
374 [ 0, 'XXX', "1234567890", "XXX" ],
375 [ 8, 'XXX', "1234567890", "12345XXX" ],
376 [ 5, 'XXXXXXXXXXXXXXX', '1234567890', "1234567890" ],
377 [ 2, '***',
378 '<p><span style="font-weight:bold;"></span></p>',
379 '<p><span style="font-weight:bold;"></span></p>',
381 [ 2, '***',
382 '<p><span style="font-weight:bold;">123456789</span></p>',
383 '<p><span style="font-weight:bold;">***</span></p>',
385 [ 2, '***',
386 '<p><span style="font-weight:bold;">&nbsp;23456789</span></p>',
387 '<p><span style="font-weight:bold;">***</span></p>',
389 [ 3, '***',
390 '<p><span style="font-weight:bold;">123456789</span></p>',
391 '<p><span style="font-weight:bold;">***</span></p>',
393 [ 4, '***',
394 '<p><span style="font-weight:bold;">123456789</span></p>',
395 '<p><span style="font-weight:bold;">1***</span></p>',
397 [ 5, '***',
398 '<tt><span style="font-weight:bold;">123456789</span></tt>',
399 '<tt><span style="font-weight:bold;">12***</span></tt>',
401 [ 6, '***',
402 '<p><a href="www.mediawiki.org">123456789</a></p>',
403 '<p><a href="www.mediawiki.org">123***</a></p>',
405 [ 6, '***',
406 '<p><a href="www.mediawiki.org">12&nbsp;456789</a></p>',
407 '<p><a href="www.mediawiki.org">12&nbsp;***</a></p>',
409 [ 7, '***',
410 '<small><span style="font-weight:bold;">123<p id="#moo">456</p>789</span></small>',
411 '<small><span style="font-weight:bold;">123<p id="#moo">4***</p></span></small>',
413 [ 8, '***',
414 '<div><span style="font-weight:bold;">123<span>4</span>56789</span></div>',
415 '<div><span style="font-weight:bold;">123<span>4</span>5***</span></div>',
417 [ 9, '***',
418 '<p><table style="font-weight:bold;"><tr><td>123456789</td></tr></table></p>',
419 '<p><table style="font-weight:bold;"><tr><td>123456789</td></tr></table></p>',
421 [ 10, '***',
422 '<p><font style="font-weight:bold;">123456789</font></p>',
423 '<p><font style="font-weight:bold;">123456789</font></p>',
425 [ 10, '***',
426 '<p><font style="font-weight:bold;">123456789</font',
427 '<p><font style="font-weight:bold;">123456789</font</p>',
433 * Test too short timestamp
435 public function testSprintfDateTooShortTimestamp() {
436 $this->expectException( InvalidArgumentException::class );
437 $this->getLang()->sprintfDate( 'xiY', '1234567890123' );
441 * Test too long timestamp
443 public function testSprintfDateTooLongTimestamp() {
444 $this->expectException( InvalidArgumentException::class );
445 $this->getLang()->sprintfDate( 'xiY', '123456789012345' );
449 * Test too short timestamp
451 public function testSprintfDateNotAllDigitTimestamp() {
452 $this->expectException( InvalidArgumentException::class );
453 $this->getLang()->sprintfDate( 'xiY', '-1234567890123' );
457 * @dataProvider provideSprintfDateSamples
459 public function testSprintfDate( $format, $ts, $expected, $msg ) {
460 $ttl = null;
461 $this->assertSame(
462 $expected,
463 $this->getLang()->sprintfDate( $format, $ts, null, $ttl ),
464 "sprintfDate('$format', '$ts'): $msg"
466 if ( $ttl ) {
467 $dt = new DateTime( $ts );
468 $lastValidTS = $dt->add( new DateInterval( 'PT' . ( $ttl - 1 ) . 'S' ) )->format( 'YmdHis' );
469 $this->assertSame(
470 $expected,
471 $this->getLang()->sprintfDate( $format, $lastValidTS, null ),
472 "sprintfDate('$format', '$ts'): TTL $ttl too high (output was different at $lastValidTS)"
474 } else {
475 // advance the time enough to make all of the possible outputs different (except possibly L)
476 $dt = new DateTime( $ts );
477 $newTS = $dt->add( new DateInterval( 'P1Y1M8DT13H1M1S' ) )->format( 'YmdHis' );
478 $this->assertSame(
479 $expected,
480 $this->getLang()->sprintfDate( $format, $newTS, null ),
481 "sprintfDate('$format', '$ts'): Missing TTL (output was different at $newTS)"
487 * sprintfDate should always use UTC when no zone is given.
488 * @dataProvider provideSprintfDateSamples
490 public function testSprintfDateNoZone( $format, $ts, $expected, $ignore, $msg ) {
491 $oldTZ = date_default_timezone_get();
492 $res = date_default_timezone_set( 'Asia/Seoul' );
493 if ( !$res ) {
494 $this->markTestSkipped( "Error setting Timezone" );
497 $this->assertEquals(
498 $expected,
499 $this->getLang()->sprintfDate( $format, $ts ),
500 "sprintfDate('$format', '$ts'): $msg"
503 date_default_timezone_set( $oldTZ );
507 * sprintfDate should use passed timezone
508 * @dataProvider provideSprintfDateSamples
510 public function testSprintfDateTZ( $format, $ts, $ignore, $expected, $msg ) {
511 $tz = new DateTimeZone( 'Asia/Seoul' );
512 if ( !$tz ) {
513 $this->markTestSkipped( "Error getting Timezone" );
516 $this->assertEquals(
517 $expected,
518 $this->getLang()->sprintfDate( $format, $ts, $tz ),
519 "sprintfDate('$format', '$ts', 'Asia/Seoul'): $msg"
524 * sprintfDate should only calculate a TTL if the caller is going to use it.
526 public function testSprintfDateNoTtlIfNotNeeded() {
527 $noTtl = 'unused'; // Value used to represent that the caller didn't pass a variable in.
528 $ttl = null;
529 $this->getLang()->sprintfDate( 'YmdHis', wfTimestampNow(), null, $noTtl );
530 $this->getLang()->sprintfDate( 'YmdHis', wfTimestampNow(), null, $ttl );
532 $this->assertSame(
533 'unused',
534 $noTtl,
535 'If the caller does not set the $ttl variable, do not compute it.'
537 $this->assertIsInt( $ttl, 'TTL should have been computed.' );
540 public static function provideSprintfDateSamples() {
541 return [
543 'xiY',
544 '20111212000000',
545 '1390', // note because we're testing English locale we get Latin-standard digits
546 '1390',
547 'Iranian calendar full year'
550 'xiy',
551 '20111212000000',
552 '90',
553 '90',
554 'Iranian calendar short year'
557 'o',
558 '20120101235000',
559 '2011',
560 '2011',
561 'ISO 8601 (week) year'
564 'W',
565 '20120101235000',
566 '52',
567 '52',
568 'Week number'
571 'W',
572 '20120102235000',
573 '01',
574 '01',
575 'Week number'
578 'o-\\WW-N',
579 '20091231235000',
580 '2009-W53-4',
581 '2009-W53-4',
582 'leap week'
584 // What follows is mostly copied from
585 // https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time
587 'Y',
588 '20120102090705',
589 '2012',
590 '2012',
591 'Full year'
594 'y',
595 '20120102090705',
596 '12',
597 '12',
598 '2 digit year'
601 'L',
602 '20120102090705',
603 '1',
604 '1',
605 'Leap year'
608 'n',
609 '20120102090705',
610 '1',
611 '1',
612 'Month index, not zero pad'
615 'N',
616 '20120102090705',
617 '1',
618 '1',
619 'Day of the week'
622 'M',
623 '20120102090705',
624 'Jan',
625 'Jan',
626 'Month abbrev'
629 'F',
630 '20120102090705',
631 'January',
632 'January',
633 'Full month'
636 'xg',
637 '20120102090705',
638 'January',
639 'January',
640 'Genitive month name (same in EN)'
643 'j',
644 '20120102090705',
645 '2',
646 '2',
647 'Day of month (not zero pad)'
650 'd',
651 '20120102090705',
652 '02',
653 '02',
654 'Day of month (zero-pad)'
657 'z',
658 '20120102090705',
659 '1',
660 '1',
661 'Day of year (zero-indexed)'
664 'D',
665 '20120102090705',
666 'Mon',
667 'Mon',
668 'Day of week (abbrev)'
671 'l',
672 '20120102090705',
673 'Monday',
674 'Monday',
675 'Full day of week'
678 'N',
679 '20120101090705',
680 '7',
681 '7',
682 'Day of week (Mon=1, Sun=7)'
685 'w',
686 '20120101090705',
687 '0',
688 '0',
689 'Day of week (Sun=0, Sat=6)'
692 'N',
693 '20120102090705',
694 '1',
695 '1',
696 'Day of week'
699 'a',
700 '20120102090705',
701 'am',
702 'am',
703 'am vs pm'
706 'A',
707 '20120102120000',
708 'PM',
709 'PM',
710 'AM vs PM'
713 'a',
714 '20120102000000',
715 'am',
716 'am',
717 'AM vs PM'
720 'g',
721 '20120102090705',
722 '9',
723 '9',
724 '12 hour, not Zero'
727 'h',
728 '20120102090705',
729 '09',
730 '09',
731 '12 hour, zero padded'
734 'G',
735 '20120102090705',
736 '9',
737 '9',
738 '24 hour, not zero'
741 'H',
742 '20120102090705',
743 '09',
744 '09',
745 '24 hour, zero'
748 'H',
749 '20120102110705',
750 '11',
751 '11',
752 '24 hour, zero'
755 'i',
756 '20120102090705',
757 '07',
758 '07',
759 'Minutes'
762 's',
763 '20120102090705',
764 '05',
765 '05',
766 'seconds'
769 'U',
770 '20120102090705',
771 '1325495225',
772 '1325462825',
773 'unix time'
776 't',
777 '20120102090705',
778 '31',
779 '31',
780 'Days in current month'
783 'c',
784 '20120102090705',
785 '2012-01-02T09:07:05+00:00',
786 '2012-01-02T09:07:05+09:00',
787 'ISO 8601 timestamp'
790 'r',
791 '20120102090705',
792 'Mon, 02 Jan 2012 09:07:05 +0000',
793 'Mon, 02 Jan 2012 09:07:05 +0900',
794 'RFC 5322'
797 'e',
798 '20120102090705',
799 'UTC',
800 'Asia/Seoul',
801 'Timezone identifier'
804 'I',
805 '19880602090705',
806 '0',
807 '1',
808 'DST indicator'
811 'O',
812 '20120102090705',
813 '+0000',
814 '+0900',
815 'Timezone offset'
818 'P',
819 '20120102090705',
820 '+00:00',
821 '+09:00',
822 'Timezone offset with colon'
825 'T',
826 '20120102090705',
827 'UTC',
828 'KST',
829 'Timezone abbreviation'
832 'Z',
833 '20120102090705',
834 '0',
835 '32400',
836 'Timezone offset in seconds'
839 'xmj xmF xmn xmY',
840 '20120102090705',
841 '7 Safar 2 1433',
842 '7 Safar 2 1433',
843 'Islamic'
846 'xij xiF xin xiY',
847 '20120102090705',
848 '12 Dey 10 1390',
849 '12 Dey 10 1390',
850 'Iranian'
853 'xjj xjF xjn xjY',
854 '20120102090705',
855 '7 Tevet 4 5772',
856 '7 Tevet 4 5772',
857 'Hebrew'
860 'xjt',
861 '20120102090705',
862 '29',
863 '29',
864 'Hebrew number of days in month'
867 'xjx',
868 '20120102090705',
869 'Tevet',
870 'Tevet',
871 'Hebrew genitive month name (No difference in EN)'
874 'xkY',
875 '20120102090705',
876 '2555',
877 '2555',
878 'Thai year'
881 'xkY',
882 '19410101090705',
883 '2484',
884 '2484',
885 'Thai year'
888 'xoY',
889 '20120102090705',
890 '101',
891 '101',
892 'Minguo'
895 'xtY',
896 '18660101000000',
897 '西暦1866',
898 '西暦1866',
899 'nengo - before meiji'
902 'xtY',
903 '18670101000000',
904 '西暦1867',
905 '西暦1867',
906 'nengo - before meiji'
909 'xtY',
910 '18721231235959',
911 '西暦1872',
912 '西暦1872',
913 'nengo - meiji, but Lunisolar calendar'
916 'xtY',
917 '18730101000000',
918 '明治6',
919 '明治6',
920 'nengo - meiji 6th'
923 'xtY',
924 '19120729235959',
925 '明治45',
926 '明治45',
927 'nengo - meiji 45th last day'
930 'xtY',
931 '19120730000000',
932 '大正元',
933 '大正元',
934 'nengo - taisho first day'
937 'xtY',
938 '19130101000000',
939 '大正2',
940 '大正2',
941 'nengo - taisho 2nd'
944 'xtY',
945 '19261224235959',
946 '大正15',
947 '大正15',
948 'nengo - taisho last day'
951 'xtY',
952 '19261225000000',
953 '昭和元',
954 '昭和元',
955 'nengo - first day of Showa'
958 'xtY',
959 '19270101000000',
960 '昭和2',
961 '昭和2',
962 'nengo - second year of Showa'
965 'xtY',
966 '19890107235959',
967 '昭和64',
968 '昭和64',
969 'nengo - last day of Showa'
972 'xtY',
973 '19890108000000',
974 '平成元',
975 '平成元',
976 'nengo - first day of Heisei'
979 'xtY',
980 '19900101000000',
981 '平成2',
982 '平成2',
983 'nengo - second year of Heisei'
986 'xtY',
987 '20190430235959',
988 '平成31',
989 '平成31',
990 'nengo - last day of Heisei'
993 'xtY',
994 '20190501000000',
995 '令和元',
996 '令和元',
997 'nengo - first day of Reiwa'
1000 'xtY',
1001 '20200501000000',
1002 '令和2',
1003 '令和2',
1004 'nengo - second year of Reiwa'
1007 'xrxkYY',
1008 '20120102090705',
1009 'MMDLV2012',
1010 'MMDLV2012',
1011 'Roman numerals'
1014 'xhxjYY',
1015 '20120102090705',
1016 \'תשע"ב2012',
1017 \'תשע"ב2012',
1018 'Hebrew numberals'
1021 'xnY',
1022 '20120102090705',
1023 '2012',
1024 '2012',
1025 'Raw numerals (doesn\'t mean much in EN)'
1028 '[[Y "(yea"\\r)]] \\"xx\\"',
1029 '20120102090705',
1030 '[[2012 (year)]] "x"',
1031 '[[2012 (year)]] "x"',
1032 'Various escaping'
1039 * @dataProvider provideFormatSizes
1041 public function testFormatSize( $size, $expected, $msg ) {
1042 $this->assertEquals(
1043 $expected,
1044 $this->getLang()->formatSize( $size ),
1045 "formatSize('$size'): $msg"
1049 public static function provideFormatSizes() {
1050 return [
1053 "0 bytes",
1054 "Zero bytes"
1057 1024,
1058 "1 KB",
1059 "1 kilobyte"
1062 1024 * 1024,
1063 "1 MB",
1064 "1 megabyte"
1067 1024 * 1024 * 1024,
1068 "1 GB",
1069 "1 gigabyte"
1072 1024 ** 4,
1073 "1 TB",
1074 "1 terabyte"
1077 1024 ** 5,
1078 "1 PB",
1079 "1 petabyte"
1082 1024 ** 6,
1083 "1 EB",
1084 "1 exabyte"
1087 1024 ** 7,
1088 "1 ZB",
1089 "1 zettabyte"
1092 1024 ** 8,
1093 "1 YB",
1094 "1 yottabyte"
1097 1024 ** 9,
1098 "1 RB",
1099 "1 ronnabyte"
1102 1024 ** 10,
1103 "1 QB",
1104 "1 quettabyte"
1107 1024 ** 11,
1108 "1,024 QB",
1109 "1,024 quettabytes"
1111 // How big!? THIS BIG!
1116 * @dataProvider provideFormatBitrate
1118 public function testFormatBitrate( $bps, $expected, $msg ) {
1119 $this->assertEquals(
1120 $expected,
1121 $this->getLang()->formatBitrate( $bps ),
1122 "formatBitrate('$bps'): $msg"
1126 public static function provideFormatBitrate() {
1127 return [
1130 "0 bps",
1131 "0 bits per second"
1134 999,
1135 "999 bps",
1136 "999 bits per second"
1139 1000,
1140 "1 kbps",
1141 "1 kilobit per second"
1144 1000 * 1000,
1145 "1 Mbps",
1146 "1 megabit per second"
1149 10 ** 9,
1150 "1 Gbps",
1151 "1 gigabit per second"
1154 10 ** 12,
1155 "1 Tbps",
1156 "1 terabit per second"
1159 10 ** 15,
1160 "1 Pbps",
1161 "1 petabit per second"
1164 10 ** 18,
1165 "1 Ebps",
1166 "1 exabit per second"
1169 10 ** 21,
1170 "1 Zbps",
1171 "1 zettabit per second"
1174 10 ** 24,
1175 "1 Ybps",
1176 "1 yottabit per second"
1179 10 ** 27,
1180 "1 Rbps",
1181 "1 ronnabits per second"
1184 10 ** 30,
1185 "1 Qbps",
1186 "1 quettabit per second"
1189 10 ** 33,
1190 "1,000 Qbps",
1191 "1,000 quettabits per second"
1197 * @dataProvider provideFormatDuration
1199 public function testFormatDuration( $duration, $expected, $intervals = [] ) {
1200 $this->assertEquals(
1201 $expected,
1202 $this->getLang()->formatDuration( $duration, $intervals ),
1203 "formatDuration('$duration'): $expected"
1207 public static function provideFormatDuration() {
1208 return [
1211 '0 seconds',
1215 '1 second',
1219 '2 seconds',
1223 '1 minute',
1226 2 * 60,
1227 '2 minutes',
1230 3600,
1231 '1 hour',
1234 2 * 3600,
1235 '2 hours',
1238 24 * 3600,
1239 '1 day',
1242 2 * 86400,
1243 '2 days',
1246 365.2425 * 24 * 3600 / 12,
1247 '1 month',
1248 [ 'months', 'days' ]
1251 365.2425 * 24 * 3600 / 12 * 2,
1252 '2 months',
1253 [ 'months', 'days' ]
1256 ( 365.2425 * 24 * 3600 / 12 * 2 ) + 24 * 3600,
1257 '2 months and 1 day',
1258 [ 'months', 'days' ]
1261 // ( 365 + ( 24 * 3 + 25 ) / 400 ) * 86400 = 31556952
1262 ( 365 + ( 24 * 3 + 25 ) / 400.0 ) * 86400,
1263 '1 year',
1264 [ 'months', 'years' ]
1267 2 * 31556952,
1268 '2 years',
1271 10 * 31556952,
1272 '1 decade',
1275 20 * 31556952,
1276 '2 decades',
1279 100 * 31556952,
1280 '1 century',
1283 200 * 31556952,
1284 '2 centuries',
1287 1000 * 31556952,
1288 '1 millennium',
1291 2000 * 31556952,
1292 '2 millennia',
1295 9001,
1296 '2 hours, 30 minutes and 1 second'
1299 3601,
1300 '1 hour and 1 second'
1303 31556952 + 2 * 86400 + 9000,
1304 '1 year, 2 days, 2 hours and 30 minutes'
1307 42 * 1000 * 31556952 + 42,
1308 '42 millennia and 42 seconds'
1312 '60 seconds',
1313 [ 'seconds' ],
1317 '61 seconds',
1318 [ 'seconds' ],
1322 '1 second',
1323 [ 'seconds' ],
1326 31556952 + 2 * 86400 + 9000,
1327 '1 year, 2 days and 150 minutes',
1328 [ 'years', 'days', 'minutes' ],
1332 '0 days',
1333 [ 'years', 'days' ],
1336 31556952 + 2 * 86400 + 9000,
1337 '1 year, 2 days and 150 minutes',
1338 [ 'minutes', 'days', 'years' ],
1342 '0 days',
1343 [ 'days', 'years' ],
1346 ( new DateTime( '2025-05-03 20:00:00' ) )->getTimestamp() - ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1347 '11 months',
1348 [ 'months' ],
1351 ( new DateTime( '2025-05-03 20:00:00' ) )->getTimestamp() - ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1352 '11 months, 30 days, 4 hours, 39 minutes and 54 seconds',
1353 [ 'years', 'months', 'days', 'hours', 'minutes', 'seconds' ],
1359 * @dataProvider provideFormatDurationBetweenTimestamps
1361 public function testFormatDurationBetweenTimestamps(
1362 int $timestamp1,
1363 int $timestamp2,
1364 ?int $precision,
1365 string $expected
1366 ): void {
1367 $this->assertSame(
1368 $expected,
1369 $this->getLang()->formatDurationBetweenTimestamps( $timestamp1, $timestamp2, $precision )
1371 $this->assertSame(
1372 $expected,
1373 $this->getLang()->formatDurationBetweenTimestamps( $timestamp2, $timestamp1, $precision )
1377 public function provideFormatDurationBetweenTimestamps(): array {
1378 return [
1379 // most test cases ported from provideFormatDuration()
1381 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1382 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1383 null,
1384 '0 seconds',
1387 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1388 ( new DateTime( '2024-05-03 20:00:01' ) )->getTimestamp(),
1389 null,
1390 '1 second',
1393 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1394 ( new DateTime( '2024-05-03 20:00:02' ) )->getTimestamp(),
1395 null,
1396 '2 seconds',
1399 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1400 ( new DateTime( '2024-05-03 20:01:00' ) )->getTimestamp(),
1401 null,
1402 '1 minute',
1405 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1406 ( new DateTime( '2024-05-03 20:02:00' ) )->getTimestamp(),
1407 null,
1408 '2 minutes',
1411 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1412 ( new DateTime( '2024-05-03 21:00:00' ) )->getTimestamp(),
1413 null,
1414 '1 hour',
1417 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1418 ( new DateTime( '2024-05-03 22:00:00' ) )->getTimestamp(),
1419 null,
1420 '2 hours',
1423 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1424 ( new DateTime( '2024-05-04 20:00:00' ) )->getTimestamp(),
1425 null,
1426 '1 day',
1429 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1430 ( new DateTime( '2024-05-05 20:00:00' ) )->getTimestamp(),
1431 null,
1432 '2 days',
1435 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1436 ( new DateTime( '2024-06-03 20:00:00' ) )->getTimestamp(),
1438 '1 month',
1441 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1442 ( new DateTime( '2024-07-03 20:00:00' ) )->getTimestamp(),
1444 '2 months',
1447 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1448 ( new DateTime( '2024-07-04 20:00:00' ) )->getTimestamp(),
1450 '2 months and 1 day',
1453 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1454 ( new DateTime( '2025-05-03 20:00:00' ) )->getTimestamp(),
1456 '1 year',
1459 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1460 ( new DateTime( '2026-05-03 20:00:00' ) )->getTimestamp(),
1461 null,
1462 '2 years',
1465 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1466 ( new DateTime( '2034-05-03 20:00:00' ) )->getTimestamp(),
1467 null,
1468 '1 decade',
1471 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1472 ( new DateTime( '2044-05-03 20:00:00' ) )->getTimestamp(),
1473 null,
1474 '2 decades',
1477 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1478 ( new DateTime( '2124-05-03 20:00:00' ) )->getTimestamp(),
1479 null,
1480 '1 century',
1483 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1484 ( new DateTime( '2224-05-03 20:00:00' ) )->getTimestamp(),
1485 null,
1486 '2 centuries',
1489 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1490 ( new DateTime( '3024-05-03 20:00:00' ) )->getTimestamp(),
1491 null,
1492 '1 millennium',
1495 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1496 ( new DateTime( '4024-05-03 20:00:00' ) )->getTimestamp(),
1497 null,
1498 '2 millennia',
1502 9001,
1503 null,
1504 '2 hours, 30 minutes and 1 second',
1508 3601,
1509 null,
1510 '1 hour and 1 second',
1513 ( new DateTime( '2024-05-03 20:00:00' ) )->getTimestamp(),
1514 ( new DateTime( '2025-05-05 22:30:00' ) )->getTimestamp(),
1515 null,
1516 '1 year, 2 days, 2 hours and 30 minutes',
1519 ( new DateTimeImmutable( '2024-05-03 20:00:00' ) )->getTimestamp(),
1520 ( new DateTimeImmutable() )->setDate( 44024, 05, 03 )->setTime( 20, 0, 42 )->getTimestamp(),
1521 null,
1522 '42 millennia and 42 seconds',
1527 null,
1528 '1 minute',
1533 null,
1534 '1 minute and 1 second',
1537 ( new DateTimeImmutable( '2024-05-03 20:00:00' ) )->getTimestamp(),
1538 ( new DateTimeImmutable() )->setDate( 2025, 05, 05 )->setTime( 22, 30, 0 )->getTimestamp(),
1539 null,
1540 '1 year, 2 days, 2 hours and 30 minutes',
1543 ( new DateTimeImmutable( '2024-05-03 20:00:00' ) )->getTimestamp(),
1544 ( new DateTimeImmutable( '2024-10-09 20:15:37' ) )->getTimestamp(),
1546 '5 months',
1549 ( new DateTime( '2022-01-01 10:00:00' ) )->getTimestamp(),
1550 ( new DateTime( '2022-01-01 12:30:00' ) )->getTimestamp(),
1552 '2 hours and 30 minutes',
1555 ( new DateTime( '2022-01-01 10:00:00' ) )->getTimestamp(),
1556 ( new DateTime( '2022-01-02 12:30:00' ) )->getTimestamp(),
1558 '1 day, 2 hours and 30 minutes',
1561 ( new DateTime( '2022-01-01 10:00:00' ) )->getTimestamp(),
1562 ( new DateTime( '2022-01-01 10:30:27' ) )->getTimestamp(),
1564 '30 minutes',
1567 ( new DateTime( '2024-05-03 10:00:00' ) )->getTimestamp(),
1568 ( new DateTime( '2025-05-03 10:00:00' ) )->getTimestamp(),
1570 '1 year',
1573 ( new DateTime( '2024-01-28 10:00:00' ) )->getTimestamp(),
1574 ( new DateTime( '2024-03-01 10:00:00' ) )->getTimestamp(),
1576 '1 month and 2 days',
1579 ( new DateTime( '2023-01-28 10:00:00' ) )->getTimestamp(),
1580 ( new DateTime( '2023-03-01 10:00:00' ) )->getTimestamp(),
1582 '1 month and 1 day',
1585 ( new DateTime( '2023-01-29 20:00:00' ) )->getTimestamp(),
1586 ( new DateTime( '2023-02-28 20:00:00' ) )->getTimestamp(),
1588 '30 days',
1591 ( new DateTime( '2023-01-29 20:00:00' ) )->getTimestamp(),
1592 ( new DateTime( '2023-03-01 20:00:00' ) )->getTimestamp(),
1594 '1 month',
1597 ( new DateTime( '2023-01-30 20:00:00' ) )->getTimestamp(),
1598 ( new DateTime( '2023-03-01 20:00:00' ) )->getTimestamp(),
1600 '30 days',
1603 ( new DateTime( '2023-01-31 20:00:00' ) )->getTimestamp(),
1604 ( new DateTime( '2023-03-01 20:00:00' ) )->getTimestamp(),
1606 '29 days',
1609 ( new DateTime( '2023-01-31 20:00:00' ) )->getTimestamp(),
1610 ( new DateTime( '2023-01-31 20:00:01' ) )->getTimestamp(),
1612 '1 second',
1615 ( new DateTime( '2023-01-31 20:00:00' ) )->getTimestamp(),
1616 ( new DateTime( '2023-01-31 20:00:02' ) )->getTimestamp(),
1618 '2 seconds',
1621 ( new DateTime( '2023-01-31 20:00:00' ) )->getTimestamp(),
1622 ( new DateTime( '2023-01-31 20:01:00' ) )->getTimestamp(),
1624 '1 minute',
1627 ( new DateTime( '2023-01-31 20:00:00' ) )->getTimestamp(),
1628 ( new DateTime( '2023-01-31 20:02:00' ) )->getTimestamp(),
1630 '2 minutes',
1633 ( new DateTime( '2023-01-31 20:00:00' ) )->getTimestamp(),
1634 ( new DateTime( '2023-01-31 21:00:00' ) )->getTimestamp(),
1636 '1 hour',
1639 ( new DateTime( '2023-01-31 20:00:00' ) )->getTimestamp(),
1640 ( new DateTime( '2023-01-31 22:00:00' ) )->getTimestamp(),
1642 '2 hours',
1645 ( new DateTime( '2023-01-31 20:00:00' ) )->getTimestamp(),
1646 ( new DateTime( '2023-02-01 20:00:00' ) )->getTimestamp(),
1648 '1 day',
1651 ( new DateTime( '2023-01-31 20:00:00' ) )->getTimestamp(),
1652 ( new DateTime( '2023-02-02 20:00:00' ) )->getTimestamp(),
1654 '2 days',
1657 ( new DateTime( '2023-03-31 20:00:00' ) )->getTimestamp(),
1658 ( new DateTime( '2023-04-31 20:00:00' ) )->getTimestamp(),
1660 '1 month',
1663 ( new DateTime( '2023-01-31 20:00:00' ) )->getTimestamp(),
1664 ( new DateTime( '2023-03-31 20:00:00' ) )->getTimestamp(),
1666 '2 months',
1669 ( new DateTime( '2023-01-31 20:00:00' ) )->getTimestamp(),
1670 ( new DateTime( '2024-01-31 20:00:00' ) )->getTimestamp(),
1672 '1 year',
1675 ( new DateTime( '2023-01-27 15:00:00' ) )->getTimestamp(),
1676 ( new DateTime( '2025-04-31 20:06:00' ) )->getTimestamp(),
1678 '2 years, 3 months, 4 days, 5 hours and 6 minutes',
1681 ( new DateTime( '2023-01-31 15:00:00' ) )->getTimestamp(),
1682 ( new DateTime( '3025-04-31 20:06:07' ) )->getTimestamp(),
1684 '1 millennium, 2 years, 3 months, 5 hours, 6 minutes and 7 seconds',
1687 ( new DateTime( '2023-01-28 20:00:00' ) )->getTimestamp(),
1688 ( new DateTime( '4030-05-31 22:01:14' ) )->getTimestamp(),
1690 '2 millennia, 7 years, 4 months, 3 days, 2 hours, 1 minute and 14 seconds',
1696 * @dataProvider provideCheckTitleEncodingData
1698 public function testCheckTitleEncoding( $s ) {
1699 $this->assertEquals(
1701 $this->getLang()->checkTitleEncoding( $s ),
1702 "checkTitleEncoding('$s')"
1706 public static function provideCheckTitleEncodingData() {
1707 return [
1708 [ "" ],
1709 [ "United States of America" ], // 7bit ASCII
1710 [ rawurldecode( "S%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e" ) ],
1712 rawurldecode(
1713 "Acteur%7CAlbert%20Robbins%7CAnglais%7CAnn%20Donahue%7CAnthony%20E.%20Zuiker%7CCarol%20Mendelsohn"
1716 // The following two data sets come from T38839. They fail if checkTitleEncoding uses a regexp to test for
1717 // valid UTF-8 encoding and the pcre.recursion_limit is low (like, say, 1024). They succeed if checkTitleEncoding
1718 // uses mb_check_encoding for its test.
1720 rawurldecode(
1721 "Acteur%7CAlbert%20Robbins%7CAnglais%7CAnn%20Donahue%7CAnthony%20E.%20Zuiker%7CCarol%20Mendelsohn%7C"
1722 . "Catherine%20Willows%7CDavid%20Hodges%7CDavid%20Phillips%7CGil%20Grissom%7CGreg%20Sanders%7CHodges%7C"
1723 . "Internet%20Movie%20Database%7CJim%20Brass%7CLady%20Heather%7C"
1724 . "Les%20Experts%20(s%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e)%7CLes%20Experts%20:%20Manhattan%7C"
1725 . "Les%20Experts%20:%20Miami%7CListe%20des%20personnages%20des%20Experts%7C"
1726 . "Liste%20des%20%C3%A9pisodes%20des%20Experts%7CMod%C3%A8le%20discussion:Palette%20Les%20Experts%7C"
1727 . "Nick%20Stokes%7CPersonnage%20de%20fiction%7CPersonnage%20fictif%7CPersonnage%20de%20fiction%7C"
1728 . "Personnages%20r%C3%A9currents%20dans%20Les%20Experts%7CRaymond%20Langston%7CRiley%20Adams%7C"
1729 . "Saison%201%20des%20Experts%7CSaison%2010%20des%20Experts%7CSaison%2011%20des%20Experts%7C"
1730 . "Saison%2012%20des%20Experts%7CSaison%202%20des%20Experts%7CSaison%203%20des%20Experts%7C"
1731 . "Saison%204%20des%20Experts%7CSaison%205%20des%20Experts%7CSaison%206%20des%20Experts%7C"
1732 . "Saison%207%20des%20Experts%7CSaison%208%20des%20Experts%7CSaison%209%20des%20Experts%7C"
1733 . "Sara%20Sidle%7CSofia%20Curtis%7CS%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e%7CWallace%20Langham%7C"
1734 . "Warrick%20Brown%7CWendy%20Simms%7C%C3%89tats-Unis"
1738 rawurldecode(
1739 "Mod%C3%A8le%3AArrondissements%20homonymes%7CMod%C3%A8le%3ABandeau%20standard%20pour%20page%20d'homonymie%7C"
1740 . "Mod%C3%A8le%3ABatailles%20homonymes%7CMod%C3%A8le%3ACantons%20homonymes%7C"
1741 . "Mod%C3%A8le%3ACommunes%20fran%C3%A7aises%20homonymes%7CMod%C3%A8le%3AFilms%20homonymes%7C"
1742 . "Mod%C3%A8le%3AGouvernements%20homonymes%7CMod%C3%A8le%3AGuerres%20homonymes%7CMod%C3%A8le%3AHomonymie%7C"
1743 . "Mod%C3%A8le%3AHomonymie%20bateau%7CMod%C3%A8le%3AHomonymie%20d'%C3%A9tablissements%20scolaires%20ou"
1744 . "%20universitaires%7CMod%C3%A8le%3AHomonymie%20d'%C3%AEles%7CMod%C3%A8le%3AHomonymie%20de%20clubs%20sportifs%7C"
1745 . "Mod%C3%A8le%3AHomonymie%20de%20comt%C3%A9s%7CMod%C3%A8le%3AHomonymie%20de%20monument%7C"
1746 . "Mod%C3%A8le%3AHomonymie%20de%20nom%20romain%7CMod%C3%A8le%3AHomonymie%20de%20parti%20politique%7C"
1747 . "Mod%C3%A8le%3AHomonymie%20de%20route%7CMod%C3%A8le%3AHomonymie%20dynastique%7C"
1748 . "Mod%C3%A8le%3AHomonymie%20vid%C3%A9oludique%7CMod%C3%A8le%3AHomonymie%20%C3%A9difice%20religieux%7C"
1749 . "Mod%C3%A8le%3AInternationalisation%7CMod%C3%A8le%3AIsom%C3%A9rie%7CMod%C3%A8le%3AParonymie%7C"
1750 . "Mod%C3%A8le%3APatronyme%7CMod%C3%A8le%3APatronyme%20basque%7CMod%C3%A8le%3APatronyme%20italien%7C"
1751 . "Mod%C3%A8le%3APatronymie%7CMod%C3%A8le%3APersonnes%20homonymes%7CMod%C3%A8le%3ASaints%20homonymes%7C"
1752 . "Mod%C3%A8le%3ATitres%20homonymes%7CMod%C3%A8le%3AToponymie%7CMod%C3%A8le%3AUnit%C3%A9s%20homonymes%7C"
1753 . "Mod%C3%A8le%3AVilles%20homonymes%7CMod%C3%A8le%3A%C3%89difices%20religieux%20homonymes"
1757 // phpcs:enable
1761 * @dataProvider provideRomanNumeralsData
1763 public function testRomanNumerals( $num, $numerals ) {
1764 $this->assertEquals(
1765 $numerals,
1766 Language::romanNumeral( $num ),
1767 "romanNumeral('$num')"
1771 public static function provideRomanNumeralsData() {
1772 return [
1773 [ 1, 'I' ],
1774 [ 2, 'II' ],
1775 [ 3, 'III' ],
1776 [ 4, 'IV' ],
1777 [ 5, 'V' ],
1778 [ 6, 'VI' ],
1779 [ 7, 'VII' ],
1780 [ 8, 'VIII' ],
1781 [ 9, 'IX' ],
1782 [ 10, 'X' ],
1783 [ 20, 'XX' ],
1784 [ 30, 'XXX' ],
1785 [ 40, 'XL' ],
1786 [ 49, 'XLIX' ],
1787 [ 50, 'L' ],
1788 [ 60, 'LX' ],
1789 [ 70, 'LXX' ],
1790 [ 80, 'LXXX' ],
1791 [ 90, 'XC' ],
1792 [ 99, 'XCIX' ],
1793 [ 100, 'C' ],
1794 [ 200, 'CC' ],
1795 [ 300, 'CCC' ],
1796 [ 400, 'CD' ],
1797 [ 500, 'D' ],
1798 [ 600, 'DC' ],
1799 [ 700, 'DCC' ],
1800 [ 800, 'DCCC' ],
1801 [ 900, 'CM' ],
1802 [ 999, 'CMXCIX' ],
1803 [ 1000, 'M' ],
1804 [ 1989, 'MCMLXXXIX' ],
1805 [ 2000, 'MM' ],
1806 [ 3000, 'MMM' ],
1807 [ 4000, 'MMMM' ],
1808 [ 5000, 'MMMMM' ],
1809 [ 6000, 'MMMMMM' ],
1810 [ 7000, 'MMMMMMM' ],
1811 [ 8000, 'MMMMMMMM' ],
1812 [ 9000, 'MMMMMMMMM' ],
1813 [ 9999, 'MMMMMMMMMCMXCIX' ],
1814 [ 10000, 'MMMMMMMMMM' ],
1819 * @dataProvider provideHebrewNumeralsData
1821 public function testHebrewNumeral( $num, $numerals ) {
1822 $this->assertEquals(
1823 $numerals,
1824 Language::hebrewNumeral( $num ),
1825 "hebrewNumeral('$num')"
1829 public static function provideHebrewNumeralsData() {
1830 return [
1831 [ -1, -1 ],
1832 [ 0, 0 ],
1833 [ 1, "א'" ],
1834 [ 2, "ב'" ],
1835 [ 3, "ג'" ],
1836 [ 4, "ד'" ],
1837 [ 5, "ה'" ],
1838 [ 6, "ו'" ],
1839 [ 7, "ז'" ],
1840 [ 8, "ח'" ],
1841 [ 9, "ט'" ],
1842 [ 10, "י'" ],
1843 [ 11, 'י"א' ],
1844 [ 14, 'י"ד' ],
1845 [ 15, 'ט"ו' ],
1846 [ 16, 'ט"ז' ],
1847 [ 17, 'י"ז' ],
1848 [ 20, "כ'" ],
1849 [ 21, 'כ"א' ],
1850 [ 30, "ל'" ],
1851 [ 40, "מ'" ],
1852 [ 50, "נ'" ],
1853 [ 60, "ס'" ],
1854 [ 70, "ע'" ],
1855 [ 80, "פ'" ],
1856 [ 90, "צ'" ],
1857 [ 99, 'צ"ט' ],
1858 [ 100, "ק'" ],
1859 [ 101, 'ק"א' ],
1860 [ 110, 'ק"י' ],
1861 [ 200, "ר'" ],
1862 [ 300, "ש'" ],
1863 [ 400, "ת'" ],
1864 [ 500, 'ת"ק' ],
1865 [ 800, 'ת"ת' ],
1866 [ 1000, "א' אלף" ],
1867 [ 1001, "א'א'" ],
1868 [ 1012, "א'י\"ב" ],
1869 [ 1020, "א'ך'" ],
1870 [ 1030, "א'ל'" ],
1871 [ 1081, "א'פ\"א" ],
1872 [ 2000, "ב' אלפים" ],
1873 [ 2016, "ב'ט\"ז" ],
1874 [ 3000, "ג' אלפים" ],
1875 [ 4000, "ד' אלפים" ],
1876 [ 4904, "ד'תתק\"ד" ],
1877 [ 5000, "ה' אלפים" ],
1878 [ 5680, "ה'תר\"ף" ],
1879 [ 5690, "ה'תר\"ץ" ],
1880 [ 5708, "ה'תש\"ח" ],
1881 [ 5720, "ה'תש\"ך" ],
1882 [ 5740, "ה'תש\"ם" ],
1883 [ 5750, "ה'תש\"ן" ],
1884 [ 5775, "ה'תשע\"ה" ],
1889 * @dataProvider providePluralData
1891 public function testConvertPlural( $expected, $number, $forms ) {
1892 $chosen = $this->getLang()->convertPlural( $number, $forms );
1893 $this->assertEquals( $expected, $chosen );
1896 public static function providePluralData() {
1897 // Params are: [expected text, number given, [the plural forms]]
1898 return [
1899 [ 'plural', 0, [
1900 'singular', 'plural'
1901 ] ],
1902 [ 'explicit zero', 0, [
1903 '0=explicit zero', 'singular', 'plural'
1904 ] ],
1905 [ 'explicit one', 1, [
1906 'singular', 'plural', '1=explicit one',
1907 ] ],
1908 [ 'singular', 1, [
1909 'singular', 'plural', '0=explicit zero',
1910 ] ],
1911 [ 'plural', 3, [
1912 '0=explicit zero', '1=explicit one', 'singular', 'plural'
1913 ] ],
1914 [ 'explicit eleven', 11, [
1915 'singular', 'plural', '11=explicit eleven',
1916 ] ],
1917 [ 'plural', 12, [
1918 'singular', 'plural', '11=explicit twelve',
1919 ] ],
1920 [ 'plural', 12, [
1921 'singular', 'plural', '=explicit form',
1922 ] ],
1923 [ 'other', 2, [
1924 'kissa=kala', '1=2=3', 'other',
1925 ] ],
1926 [ '', 2, [
1927 '0=explicit zero', '1=explicit one',
1928 ] ],
1932 public function testEmbedBidi() {
1933 $lre = "\u{202A}"; // U+202A LEFT-TO-RIGHT EMBEDDING
1934 $rle = "\u{202B}"; // U+202B RIGHT-TO-LEFT EMBEDDING
1935 $pdf = "\u{202C}"; // U+202C POP DIRECTIONAL FORMATTING
1936 $lang = $this->getLang();
1937 $this->assertSame(
1938 '123',
1939 $lang->embedBidi( '123' ),
1940 'embedBidi with neutral argument'
1942 $this->assertEquals(
1943 $lre . 'Ben_(WMF)' . $pdf,
1944 $lang->embedBidi( 'Ben_(WMF)' ),
1945 'embedBidi with LTR argument'
1947 $this->assertEquals(
1948 $rle . 'יהודי (מנוחין)' . $pdf,
1949 $lang->embedBidi( 'יהודי (מנוחין)' ),
1950 'embedBidi with RTL argument'
1955 * @dataProvider provideTranslateBlockExpiry
1957 public function testTranslateBlockExpiry( $expectedData, $str, $now, $desc ) {
1958 $lang = $this->getLang();
1959 if ( is_array( $expectedData ) ) {
1960 $func = array_shift( $expectedData );
1961 $expected = $lang->$func( ...$expectedData );
1962 } else {
1963 $expected = $expectedData;
1965 // HACK:
1966 date_default_timezone_set( 'UTC' );
1967 $this->assertSame( $expected, $lang->translateBlockExpiry( $str, null, $now ), $desc );
1970 public static function provideTranslateBlockExpiry() {
1971 return [
1972 [ '2 hours', '2 hours', 0, 'simple data from ipboptions' ],
1973 [ 'indefinite', 'infinite', 0, 'infinite from ipboptions' ],
1974 [ 'indefinite', 'infinity', 0, 'alternative infinite from ipboptions' ],
1975 [ 'indefinite', 'indefinite', 0, 'another alternative infinite from ipboptions' ],
1976 [ [ 'formatDurationBetweenTimestamps', 0, 1023 * 60 * 60 ], '1023 hours', 0, 'relative' ],
1977 [ [ 'formatDurationBetweenTimestamps', 0, -1023 ], '-1023 seconds', 0, 'negative relative' ],
1979 [ 'formatDurationBetweenTimestamps', 665553906, 665553906 + ( 1023 * 60 * 60 ) ],
1980 '1023 hours',
1981 wfTimestamp( TS_UNIX, '1991-02-03 04:05:06' ),
1982 'relative with initial timestamp'
1984 [ [ 'formatDurationBetweenTimestamps', 0, 0 ], 'now', 0, 'now' ],
1986 [ 'timeanddate', '20120102070000' ],
1987 '2012-1-1 7:00 +1 day',
1989 'mixed, handled as absolute'
1991 [ [ 'timeanddate', '19910203040506' ], '1991-2-3 4:05:06', 0, 'absolute' ],
1992 [ [ 'timeanddate', '19700101000000' ], '1970-1-1 0:00:00', 0, 'absolute at epoch' ],
1993 [ [ 'timeanddate', '19691231235959' ], '1969-12-31 23:59:59', 0, 'time before epoch' ],
1995 [ 'timeanddate', '19910910000000' ],
1996 '10 september',
1997 wfTimestamp( TS_UNIX, '19910203040506' ),
1998 'partial'
2000 [ 'dummy', 'dummy', 0, 'return garbage as is' ],
2001 'Relative timestamp that causes negative number from strtotime' => [
2002 '-0.000000000000000001 seconds',
2003 '-0.000000000000000001 seconds',
2004 wfTimestamp( TS_UNIX, '20200524200807' ),
2005 'Relative timestamp that fails to be parsed by strtotime should be returned without modification'
2011 * @dataProvider provideFormatNum
2013 public function testFormatNum(
2014 $translateNumerals, $langCode, $number, $noSeparators, $expected
2016 $this->hideDeprecated( 'Language::formatNum with a non-numeric string' );
2017 $this->overrideConfigValue( MainConfigNames::TranslateNumerals, $translateNumerals );
2018 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( $langCode );
2019 if ( $noSeparators ) {
2020 $formattedNum = $lang->formatNumNoSeparators( $number );
2021 } else {
2022 $formattedNum = $lang->formatNum( $number );
2024 $this->assertIsString( $formattedNum );
2025 $this->assertEquals( $expected, $formattedNum );
2028 public static function provideFormatNum() {
2029 return [
2030 [ true, 'en', 100, false, '100' ],
2031 [ true, 'en', 101, true, '101' ],
2032 [ false, 'en', 103, false, '103' ],
2033 [ false, 'en', 104, true, '104' ],
2034 [ true, 'en', '105', false, '105' ],
2035 [ true, 'en', '106', true, '106' ],
2036 [ false, 'en', '107', false, '107' ],
2037 [ false, 'en', '108', true, '108' ],
2038 [ true, 'en', -1, false, '−1' ],
2039 [ true, 'en', 10, false, '10' ],
2040 [ true, 'en', 100, false, '100' ],
2041 [ true, 'en', 1000, false, '1,000' ],
2042 [ true, 'en', 10000, false, '10,000' ],
2043 [ true, 'en', 100000, false, '100,000' ],
2044 [ true, 'en', 1000000, false, '1,000,000' ],
2045 [ true, 'en', -1.001, false, '−1.001' ],
2046 [ true, 'en', 1.001, false, '1.001' ],
2047 [ true, 'en', 10.0001, false, '10.0001' ],
2048 [ true, 'en', 100.001, false, '100.001' ],
2049 [ true, 'en', 1000.001, false, '1,000.001' ],
2050 [ true, 'en', 10000.001, false, '10,000.001' ],
2051 [ true, 'en', 100000.001, false, '100,000.001' ],
2052 [ true, 'en', 1000000.0001, false, '1,000,000.0001' ],
2053 [ true, 'en', -1.0001, false, '−1.0001' ],
2054 [ true, 'en', '200000000000000000000', false, '200,000,000,000,000,000,000' ],
2055 [ true, 'en', '-200000000000000000000', false, '−200,000,000,000,000,000,000' ],
2056 [ true, 'en', '1.23e10', false, '12,300,000,000' ],
2057 [ true, 'en', 1.23e10, false, '12,300,000,000' ],
2058 [ true, 'en', '1.23E-01', false, '0.123' ],
2059 [ true, 'en', 1.23e-1, false, '0.123' ],
2060 [ true, 'en', 0.0, false, '0' ],
2061 [ true, 'en', -0.0, false, '−0' ],
2062 [ true, 'en', INF, false, '∞' ],
2063 [ true, 'en', -INF, false, '−∞' ],
2064 [ true, 'en', NAN, false, 'Not a Number' ],
2065 [ true, 'kn', '1050', false, '೧,೦೫೦' ],
2066 [ true, 'kn', '1060', true, '೧೦೬೦' ],
2067 [ false, 'kn', '1070', false, '1,070' ],
2068 [ false, 'kn', '1080', true, '1080' ],
2069 [ true, 'kn', '.1090', false, '.೧೦೯೦' ],
2071 // Make sure non-numeric strings are not destroyed
2072 [ false, 'en', 'The number is 1234', false, 'The number is 1,234' ],
2073 [ false, 'en', '1234 is the number', false, '1,234 is the number' ],
2074 [ false, 'de', '.', false, '.' ],
2075 [ false, 'de', ',', false, ',' ],
2077 /** @see https://phabricator.wikimedia.org/T237467 */
2078 [ false, 'kn', "೭\u{FFFD}0", false, "೭\u{FFFD}0" ],
2079 [ false, 'kn', "-೭\u{FFFD}0", false, "-೭\u{FFFD}0" ],
2080 [ false, 'kn', "-1೭\u{FFFD}0", false, "−1೭\u{FFFD}0" ],
2082 /** @see https://phabricator.wikimedia.org/T267614 */
2083 [ false, 'ar', "1", false, "1" ],
2084 [ false, 'ar', "1234.5", false, "1٬234٫5" ],
2085 [ true, 'ar', "1", false, "١" ],
2086 [ true, 'ar', "1234.5", false, "١٬٢٣٤٫٥" ],
2088 // Test minimumGroupingDigits > 1
2089 [ false, 'pl', 1, false, '1' ],
2090 [ false, 'pl', 100, false, '100' ],
2091 [ false, 'pl', 1000, false, '1000' ],
2092 [ false, 'pl', 10000, false, "10\u{00A0}000" ],
2093 [ false, 'pl', 1000000, false, "1\u{00A0}000\u{00A0}000" ],
2094 [ false, 'pl', '1000.1', false, "1000,1" ],
2099 * @dataProvider parseFormattedNumberProvider
2101 public function testParseFormattedNumber( $langCode, $number ) {
2102 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( $langCode );
2104 $localisedNum = $lang->formatNum( $number );
2105 $normalisedNum = $lang->parseFormattedNumber( $localisedNum );
2107 $this->assertEquals( $number, $normalisedNum );
2110 public static function parseFormattedNumberProvider() {
2111 return [
2112 [ 'de', 377.01 ],
2113 [ 'fa', 334 ],
2114 [ 'fa', 382.772 ],
2115 [ 'ar', 1844 ],
2116 [ 'lzh', 3731 ],
2117 [ 'zh-classical', 7432 ],
2118 [ 'en', 1234.567 ],
2119 [ 'en', 0.0 ],
2120 [ 'en', -0.0 ],
2121 [ 'en', INF ],
2122 [ 'en', -INF ],
2123 [ 'en', NAN ],
2127 public function testListToText() {
2128 $lang = $this->getLang();
2129 $and = $lang->getMessageFromDB( 'and' );
2130 $s = $lang->getMessageFromDB( 'word-separator' );
2131 $c = $lang->getMessageFromDB( 'comma-separator' );
2133 $this->assertSame( '', $lang->listToText( [] ) );
2134 $this->assertEquals( 'a', $lang->listToText( [ 'a' ] ) );
2135 $this->assertEquals( "a{$and}{$s}b", $lang->listToText( [ 'a', 'b' ] ) );
2136 $this->assertEquals( "a{$c}b{$and}{$s}c", $lang->listToText( [ 'a', 'b', 'c' ] ) );
2137 $this->assertEquals( "a{$c}b{$c}c{$and}{$s}d", $lang->listToText( [ 'a', 'b', 'c', 'd' ] ) );
2141 * Example of the real localisation files being loaded.
2143 * This might be a bit cumbersome to maintain long-term,
2144 * but still valueable to have as integration test.
2146 public function testGetNamespaceAliasesReal() {
2147 $language = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'zh' );
2148 $aliases = $language->getNamespaceAliases();
2149 $this->assertSame( NS_FILE, $aliases['文件'] );
2150 $this->assertSame( NS_FILE, $aliases['檔案'] );
2153 public function testGetNamespaceAliasesFullLogic() {
2154 $hooks = $this->createHookContainer( [
2155 'Language::getMessagesFileName' => static function ( $code, &$file ) {
2156 $file = __DIR__ . '/../../data/messages/Messages_' . $code . '.php';
2158 ] );
2159 $langNameUtils = $this->getDummyLanguageNameUtils( [ 'hookContainer' => $hooks ] );
2161 $this->overrideConfigValue( MainConfigNames::NamespaceAliases, [
2162 'Mouse' => NS_SPECIAL,
2163 ] );
2164 $this->setService( 'LanguageNameUtils', $langNameUtils );
2166 $language = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'x-bar' );
2168 $this->assertEquals(
2170 // from x-bar
2171 'Cat' => NS_FILE,
2172 'Cat_toots' => NS_FILE_TALK,
2173 // inherited from x-foo
2174 'Dog' => NS_USER,
2175 'Dog_woofs' => NS_USER_TALK,
2176 // add from site configuration
2177 'Mouse' => NS_SPECIAL,
2179 $language->getNamespaceAliases()
2183 public function testEquals() {
2184 $languageFactory = $this->getServiceContainer()->getLanguageFactory();
2185 $en1 = $languageFactory->getLanguage( 'en' );
2186 $en2 = $languageFactory->getLanguage( 'en' );
2187 $en3 = $this->newLanguage();
2188 $this->assertTrue( $en1->equals( $en2 ), 'en1 equals en2' );
2189 $this->assertTrue( $en2->equals( $en3 ), 'en2 equals en3' );
2190 $this->assertTrue( $en3->equals( $en1 ), 'en3 equals en1' );
2192 $fr = $languageFactory->getLanguage( 'fr' );
2193 $this->assertFalse( $en1->equals( $fr ), 'en not equals fr' );
2195 $ar1 = $languageFactory->getLanguage( 'ar' );
2196 $ar2 = $this->newLanguage( LanguageAr::class, 'ar' );
2197 $this->assertTrue( $ar1->equals( $ar2 ), 'ar equals ar' );
2201 * @dataProvider provideUcfirst
2203 public function testUcfirst( $orig, $expected, $desc, $overrides = false ) {
2204 $lang = $this->newLanguage();
2205 if ( is_array( $overrides ) ) {
2206 $this->overrideConfigValue(
2207 MainConfigNames::OverrideUcfirstCharacters,
2208 $overrides
2211 $this->assertSame( $expected, $lang->ucfirst( $orig ), $desc );
2214 public static function provideUcfirst() {
2215 return [
2216 [ 'alice', 'Alice', 'simple ASCII string', false ],
2217 [ 'århus', 'Århus', 'unicode string', false ],
2218 // overrides do not affect ASCII characters
2219 [ 'foo', 'Foo', 'ASCII is not overridden', [ 'f' => 'b' ] ],
2220 // but they do affect non-ascii ones
2221 [ 'èl', 'Ll', 'Non-ASCII is overridden', [ 'è' => 'L' ] ],
2222 [ 'ვიკიპედია', 'ვიკიპედია', 'Georgian case is preserved', false ],
2226 // The following methods are for LanguageNameUtilsTestTrait
2228 private function isSupportedLanguage( $code ) {
2229 return $this->getServiceContainer()->getLanguageNameUtils()->isSupportedLanguage( $code );
2232 private function isValidCode( $code ) {
2233 return $this->getServiceContainer()->getLanguageNameUtils()->isValidCode( $code );
2236 private function isValidBuiltInCode( $code ) {
2237 return $this->getServiceContainer()->getLanguageNameUtils()->isValidBuiltInCode( $code );
2240 private function isKnownLanguageTag( $code ) {
2241 return $this->getServiceContainer()->getLanguageNameUtils()->isKnownLanguageTag( $code );
2244 protected function setLanguageTemporaryHook( string $hookName, $handler ): void {
2245 $this->setTemporaryHook( $hookName, $handler );
2248 protected function clearLanguageHook( string $hookName ): void {
2249 $this->clearHook( $hookName );
2253 * Call getLanguageName() and getLanguageNames() using the Language static methods.
2255 * @param array $options To set globals for testing Language
2256 * @param string $expected
2257 * @param string $code
2258 * @param mixed ...$otherArgs Optionally, pass $inLanguage and/or $include.
2260 private function assertGetLanguageNames( array $options, $expected, $code, ...$otherArgs ) {
2261 if ( $options ) {
2262 $this->overrideConfigValues( $options );
2265 $langNameUtils = $this->getServiceContainer()->getLanguageNameUtils();
2266 $this->assertSame( $expected,
2267 $langNameUtils->getLanguageNames( ...$otherArgs )[strtolower( $code )] ?? '' );
2268 $this->assertSame( $expected, $langNameUtils->getLanguageName( $code, ...$otherArgs ) );
2271 private function getLanguageNames( ...$args ) {
2272 return $this->getServiceContainer()->getLanguageNameUtils()->getLanguageNames( ...$args );
2275 private function getLanguageName( ...$args ) {
2276 return $this->getServiceContainer()->getLanguageNameUtils()->getLanguageName( ...$args );
2279 private function getFileName( ...$args ) {
2280 return MediaWikiServices::getInstance()->getLanguageNameUtils()->getFileName( ...$args );
2283 private function getMessagesFileName( $code ) {
2284 return MediaWikiServices::getInstance()->getLanguageNameUtils()->getMessagesFileName( $code );
2287 private function getJsonMessagesFileName( $code ) {
2288 return MediaWikiServices::getInstance()->getLanguageNameUtils()->getJsonMessagesFileName( $code );
2292 * @todo This really belongs in the cldr extension's tests.
2294 public function testCldr() {
2295 $this->markTestSkippedIfExtensionNotLoaded( 'CLDR' );
2297 $languageNameUtils = $this->getServiceContainer()->getLanguageNameUtils();
2299 // "pal" is an ancient language, which probably will not appear in Names.php, but appears in
2300 // CLDR in English
2301 $this->assertTrue( $languageNameUtils->isKnownLanguageTag( 'pal' ) );
2303 $this->assertSame( 'allemand', $languageNameUtils->getLanguageName( 'de', 'fr' ) );
2307 * @dataProvider provideGetNamespaces
2309 public function testGetNamespaces( string $langCode, array $config, array $expected ) {
2310 $services = $this->getServiceContainer();
2311 $langClass = Language::class . ucfirst( $langCode );
2312 if ( !class_exists( $langClass ) ) {
2313 $langClass = Language::class;
2315 $config += [
2316 MainConfigNames::MetaNamespace => 'Project',
2317 MainConfigNames::MetaNamespaceTalk => false,
2318 MainConfigNames::ExtraNamespaces => [],
2320 $nsInfo = new NamespaceInfo(
2321 new ServiceOptions( NamespaceInfo::CONSTRUCTOR_OPTIONS, $config, $services->getMainConfig() ),
2322 $services->getHookContainer(),
2323 ExtensionRegistry::getInstance()->getAttribute( 'ExtensionNamespaces' ),
2324 ExtensionRegistry::getInstance()->getAttribute( 'ImmovableNamespaces' )
2326 /** @var Language $lang */
2327 $lang = new $langClass(
2328 $langCode,
2329 $nsInfo,
2330 $services->getLocalisationCache(),
2331 $this->createNoOpMock( LanguageNameUtils::class ),
2332 $this->createNoOpMock( LanguageFallback::class ),
2333 $this->createNoOpMock( LanguageConverterFactory::class ),
2334 $this->createMock( HookContainer::class ),
2335 new MultiConfig( [ new HashConfig( $config ), $services->getMainConfig() ] )
2337 $namespaces = $lang->getNamespaces();
2338 $this->assertArraySubmapSame( $expected, $namespaces );
2341 public static function provideGetNamespaces() {
2342 $enNamespaces = [
2343 NS_MEDIA => 'Media',
2344 NS_SPECIAL => 'Special',
2345 NS_MAIN => '',
2346 NS_TALK => 'Talk',
2347 NS_USER => 'User',
2348 NS_USER_TALK => 'User_talk',
2349 NS_FILE => 'File',
2350 NS_FILE_TALK => 'File_talk',
2351 NS_MEDIAWIKI => 'MediaWiki',
2352 NS_MEDIAWIKI_TALK => 'MediaWiki_talk',
2353 NS_TEMPLATE => 'Template',
2354 NS_TEMPLATE_TALK => 'Template_talk',
2355 NS_HELP => 'Help',
2356 NS_HELP_TALK => 'Help_talk',
2357 NS_CATEGORY => 'Category',
2358 NS_CATEGORY_TALK => 'Category_talk',
2360 $ukNamespaces = [
2361 NS_MEDIA => 'Медіа',
2362 NS_SPECIAL => 'Спеціальна',
2363 NS_TALK => 'Обговорення',
2364 NS_USER => 'Користувач',
2365 NS_USER_TALK => 'Обговорення_користувача',
2366 NS_FILE => 'Файл',
2367 NS_FILE_TALK => 'Обговорення_файлу',
2368 NS_MEDIAWIKI => 'MediaWiki',
2369 NS_MEDIAWIKI_TALK => 'Обговорення_MediaWiki',
2370 NS_TEMPLATE => 'Шаблон',
2371 NS_TEMPLATE_TALK => 'Обговорення_шаблону',
2372 NS_HELP => 'Довідка',
2373 NS_HELP_TALK => 'Обговорення_довідки',
2374 NS_CATEGORY => 'Категорія',
2375 NS_CATEGORY_TALK => 'Обговорення_категорії',
2377 return [
2378 'Default configuration' => [
2379 'en',
2381 $enNamespaces + [
2382 NS_PROJECT => 'Project',
2383 NS_PROJECT_TALK => 'Project_talk',
2386 'Custom project NS + extra' => [
2387 'en',
2389 MainConfigNames::MetaNamespace => 'Wikipedia',
2390 MainConfigNames::ExtraNamespaces => [
2391 100 => 'Borderlands',
2392 101 => 'Borderlands_talk',
2395 $enNamespaces + [
2396 NS_PROJECT => 'Wikipedia',
2397 NS_PROJECT_TALK => 'Wikipedia_talk',
2398 100 => 'Borderlands',
2399 101 => 'Borderlands_talk',
2402 'Custom project NS and talk + extra' => [
2403 'en',
2405 MainConfigNames::MetaNamespace => 'Wikipedia',
2406 MainConfigNames::MetaNamespaceTalk => 'Wikipedia_drama',
2407 MainConfigNames::ExtraNamespaces => [
2408 100 => 'Borderlands',
2409 101 => 'Borderlands_talk',
2412 $enNamespaces + [
2413 NS_PROJECT => 'Wikipedia',
2414 NS_PROJECT_TALK => 'Wikipedia_drama',
2415 100 => 'Borderlands',
2416 101 => 'Borderlands_talk',
2419 'Ukrainian default' => [
2420 'uk',
2422 $ukNamespaces + [
2423 NS_MAIN => '',
2424 NS_PROJECT => 'Project',
2425 NS_PROJECT_TALK => 'Обговорення_Project',
2428 'Ukrainian custom NS' => [
2429 'uk',
2431 MainConfigNames::MetaNamespace => 'Вікіпедія',
2433 $ukNamespaces + [
2434 NS_MAIN => '',
2435 NS_PROJECT => 'Вікіпедія',
2436 NS_PROJECT_TALK => 'Обговорення_Вікіпедії',
2442 public function testGetGroupName() {
2443 $lang = $this->getLang();
2444 $groupName = $lang->getGroupName( 'bot' );
2445 $this->assertSame( 'Bots', $groupName );
2448 public function testGetGroupMemberName() {
2449 $lang = $this->getLang();
2450 $user = new UserIdentityValue( 1, 'user' );
2451 $groupMemberName = $lang->getGroupMemberName( 'bot', $user );
2452 $this->assertSame( 'bot', $groupMemberName );
2454 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'qqx' );
2455 $groupMemberName = $lang->getGroupMemberName( 'bot', $user );
2456 $this->assertSame( '(group-bot-member: user)', $groupMemberName );
2459 public function testMsg() {
2460 $lang = TestingAccessWrapper::newFromObject( $this->getLang() );
2461 $this->assertSame( 'Line 1:', $lang->msg( 'lineno', '1' )->text() );
2464 public function testBlockDurations() {
2465 $lang = $this->getLang();
2466 $durations = $lang->getBlockDurations();
2468 $this->assertContains( 'other', $durations );
2469 $this->assertContains( 'infinite', $durations );
2470 $this->assertContains( '1 day', $durations );