ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
NativeGettextTest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
26use PHPUnit\Framework\Attributes\DataProvider;
27use PHPUnit\Framework\Attributes\RunInSeparateProcess;
28use PHPUnit\Framework\TestCase;
29
37class NativeGettextTest extends TestCase
38{
40
41 private const string DOMAIN = 'ntg.de';
42 private const string REAL_LOCALE = 'de_DE.UTF-8';
43
44 private string $root;
45 private string $build_directory;
46 private string $locale_directory;
47
48 protected function setUp(): void
49 {
51 $this->root = sys_get_temp_dir() . '/ilias_native_gettext_' . bin2hex(random_bytes(6));
52 mkdir($this->root, 0775, true);
53
54 $catalog = MigratedPoFixture::catalog('ntg', ['greeting' => 'Hallo']);
55 $catalog->setHeader('Plural-Forms', 'nplurals=2; plural=(n != 1);');
56 $item = new TranslationEntry('ntg', 'item');
57 $item->setPlural('items', ['Eintrag', 'Einträge']);
58 $catalog->add($item);
59 MigratedPoFixture::writePo($this->root . '/ntg/ntg_de.po', $catalog);
61 $build = MigratedPoFixture::build($manager, $this->root)['build'];
62 // the build checked itself through native gettext in this process, see ShippedLanguageFilesCompiledObjective
64 $this->assertNotNull($build);
66 $this->locale_directory = MigratedLanguageFilePaths::buildLocaleDirectory($this->build_directory);
67 }
68
69 protected function tearDown(): void
70 {
74 }
75
76 private function activateBuild(): void
77 {
78 $this->assertTrue(NativeGettext::activate($this->locale_directory), (string) NativeGettext::getProblem());
79 $this->assertTrue(NativeGettext::bind(self::DOMAIN, $this->build_directory));
80 }
81
85 public static function previousProcessSettings(): iterable
86 {
87 yield 'nothing set before' => [false, false];
88 yield 'LANGUAGE set before' => ['de:fr', false];
89 yield 'LOCPATH set before' => [false, '/nonexistent/locpath'];
90 yield 'both set before' => ['en', '/nonexistent/locpath'];
91 }
92
93 #[DataProvider('previousProcessSettings')]
94 public function testActivateServesTheBuildAndRestoreGivesTheProcessStateBack(string|false $language, string|false $locpath): void
95 {
97 putenv($language === false ? 'LANGUAGE' : 'LANGUAGE=' . $language);
98 putenv($locpath === false ? 'LOCPATH' : 'LOCPATH=' . $locpath);
99 $messages_before = setlocale(LC_MESSAGES, '0');
100
101 $this->activateBuild();
102
103 $this->assertTrue(NativeGettext::isActive());
104 $this->assertSame(NativeGettext::LOCALE_NAME, setlocale(LC_MESSAGES, '0'));
105 $this->assertSame(NativeGettext::CATALOG_LANGUAGE, getenv('LANGUAGE'));
106 $this->assertSame($locpath, getenv('LOCPATH'), 'LOCPATH only lives for the setlocale() call (no system locale is found while it is set)');
107 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
108
110
111 $this->assertFalse(NativeGettext::isActive());
112 $this->assertSame($messages_before, setlocale(LC_MESSAGES, '0'));
113 $this->assertSame($language, getenv('LANGUAGE'));
114 $this->assertSame($locpath, getenv('LOCPATH'));
115 $this->assertSame('greeting', NativeGettext::translate(self::DOMAIN, 'greeting'), 'without the locale native gettext answers with the message id');
116 }
117
119 {
120 $this->activateBuild();
121 setlocale(LC_MESSAGES, 'C');
122 putenv('LANGUAGE=fr');
123
125
126 $this->assertSame('C', setlocale(LC_MESSAGES, '0'));
127 $this->assertSame('fr', getenv('LANGUAGE'));
128 }
129
131 {
132 putenv('LANGUAGE=de');
133 $messages = setlocale(LC_MESSAGES, '0');
134
136
137 $this->assertSame('de', getenv('LANGUAGE'));
138 $this->assertSame($messages, setlocale(LC_MESSAGES, '0'));
139 }
140
141 private function requireSystemLocale(): void
142 {
143 $before = setlocale(LC_MESSAGES, '0');
144 $found = setlocale(LC_MESSAGES, NativeGettext::SYSTEM_LOCALES);
145 setlocale(LC_MESSAGES, $before);
146 if ($found === false) {
147 $this->markTestSkipped('none of ' . implode(', ', NativeGettext::SYSTEM_LOCALES) . ' is installed');
148 }
149 }
150
152 {
153 $this->requireSystemLocale();
155 putenv('LOCPATH');
156 $this->assertDirectoryDoesNotExist($this->root . '/no-locale');
157
158 $this->assertTrue(NativeGettext::activate($this->root . '/no-locale'), (string) NativeGettext::getProblem());
159 $this->assertTrue(NativeGettext::bind(self::DOMAIN, $this->build_directory));
160
161 $this->assertTrue(NativeGettext::isActive());
162 $this->assertNotSame(NativeGettext::LOCALE_NAME, setlocale(LC_MESSAGES, '0'));
163 $this->assertSame(NativeGettext::CATALOG_LANGUAGE, getenv('LANGUAGE'));
164 $this->assertFalse(getenv('LOCPATH'), 'the system branch never sets LOCPATH');
165 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
166 }
167
171 public static function processSettingsBeforeSystemLocaleActivation(): iterable
172 {
173 yield 'C locale' => ['C', 'de'];
174 yield 'the system locale itself, LANGUAGE unset' => ['system', false];
175 yield 'a real locale, which stays' => [self::REAL_LOCALE, 'de'];
176 }
177
178 #[DataProvider('processSettingsBeforeSystemLocaleActivation')]
179 public function testSystemLocaleIsOnlyActiveAfterActivateAndRestoreGivesTheProcessStateBack(string $locale, string|false $language): void
180 {
181 $this->requireSystemLocale();
183 $kept = $locale === self::REAL_LOCALE;
184 if ($kept) {
185 $this->requireRealLocale();
186 }
187 $locale = $locale === 'system' ? (string) setlocale(LC_MESSAGES, NativeGettext::SYSTEM_LOCALES) : (string) setlocale(LC_MESSAGES, $locale);
188 putenv($language === false ? 'LANGUAGE' : 'LANGUAGE=' . $language);
189 $this->assertFalse(NativeGettext::isActive(), 'a locale set by someone else is not our activation');
190
191 $this->activateSystemLocale();
192 $this->assertTrue(NativeGettext::isActive());
193 if ($kept) {
194 $this->assertSame($locale, setlocale(LC_MESSAGES, '0'), 'a real locale is not replaced');
195 }
196
198
199 $this->assertFalse(NativeGettext::isActive());
200 $this->assertSame($locale, setlocale(LC_MESSAGES, '0'));
201 $this->assertSame($language, getenv('LANGUAGE'));
202 }
203
207 public static function foreignChangesInTheSystemLocaleBranch(): iterable
208 {
210 yield 'LC_ALL environment variable set to C' => [static function (): void {
211 putenv('LC_ALL=C');
212 }];
213 yield 'LC_ALL environment variable removed' => [static function (): void {
214 putenv('LC_ALL');
215 }];
216 }
217
218 #[DataProvider('foreignChangesInTheSystemLocaleBranch')]
220 {
221 $this->requireSystemLocale();
223 $this->activateSystemLocale();
224 $change();
225 $this->assertFalse(NativeGettext::isActive(), 'precondition');
226
227 $this->assertTrue(NativeGettext::reactivateIfLost());
228
229 $this->assertTrue(NativeGettext::isActive());
230 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
231 }
232
236 public static function lcAllBeforeSystemLocaleActivation(): iterable
237 {
238 yield 'unset' => [false];
239 yield 'C' => ['C'];
240 yield 'another locale' => ['de_DE.UTF-8'];
241 }
242
243 #[DataProvider('lcAllBeforeSystemLocaleActivation')]
245 {
246 $this->requireSystemLocale();
248 putenv($lc_all === false ? 'LC_ALL' : 'LC_ALL=' . $lc_all);
249
250 $this->activateSystemLocale();
251
252 $this->assertSame(setlocale(LC_MESSAGES, '0'), getenv('LC_ALL'));
253 $this->assertTrue(NativeGettext::isActive());
254 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
255
257
258 $this->assertSame($lc_all, getenv('LC_ALL'));
259 $this->assertFalse(NativeGettext::isActive());
260 }
261
262 #[DataProvider('lcAllBeforeSystemLocaleActivation')]
264 {
265 $this->requireSystemLocale();
267 putenv($lc_all === false ? 'LC_ALL' : 'LC_ALL=' . $lc_all);
268 $this->activateSystemLocale();
269
270 $this->assertTrue(NativeGettext::activate($this->root . '/other-no-locale'), (string) NativeGettext::getProblem());
271 putenv('LC_ALL=fr_FR.UTF-8');
272 $this->assertTrue(NativeGettext::reactivateIfLost());
273 $this->assertSame(setlocale(LC_MESSAGES, '0'), getenv('LC_ALL'));
274
276
277 $this->assertSame($lc_all, getenv('LC_ALL'));
278 }
279
281 {
282 $this->requireSystemLocale();
284 putenv('LC_ALL=C');
285 $this->activateSystemLocale();
286 $active = setlocale(LC_MESSAGES, '0');
288
290
291 $this->assertSame($active, getenv('LC_ALL'));
292 $this->assertTrue(NativeGettext::isActive());
293 }
294
296 {
297 $this->requireSystemLocale();
299 putenv('LC_ALL');
300 $this->activateSystemLocale();
301 putenv('LC_ALL=fr_FR.UTF-8');
302
304
305 $this->assertSame('fr_FR.UTF-8', getenv('LC_ALL'));
306 }
307
308 public function testGlibcBranchNeverTouchesLcAll(): void
309 {
311 putenv('LC_ALL=de_DE.UTF-8');
312
313 $this->activateBuild();
314 $this->assertTrue(NativeGettext::isActive());
315 $this->assertSame('de_DE.UTF-8', getenv('LC_ALL'));
316
318 $this->assertSame('de_DE.UTF-8', getenv('LC_ALL'));
319
320 putenv('LC_ALL');
321 $this->activateBuild();
322 $this->assertFalse(getenv('LC_ALL'));
324 $this->assertFalse(getenv('LC_ALL'));
325 }
326
327 private function activateSystemLocale(): void
328 {
329 $this->assertTrue(NativeGettext::activate($this->root . '/no-locale'), (string) NativeGettext::getProblem());
330 $this->assertTrue(NativeGettext::bind(self::DOMAIN, $this->build_directory));
331 }
332
336 public static function directoriesWithoutAUsableLocale(): iterable
337 {
338 yield 'no locale directory' => ['none'];
339 yield 'an empty locale directory' => ['empty'];
340 }
341
342 #[DataProvider('directoriesWithoutAUsableLocale')]
344 {
346 $directory = $this->root . '/locale-' . $kind;
347 mkdir($directory . ($kind === 'empty' ? '/' . NativeGettext::LOCALE_NAME : ''), 0775, true);
348 putenv('LANGUAGE=de');
349 putenv('LOCPATH=/nonexistent/locpath');
350 $messages = setlocale(LC_MESSAGES, '0');
351
352 $this->assertFalse(NativeGettext::activate($directory));
353
354 $this->assertStringContainsString(NativeGettext::LOCALE_NAME, (string) NativeGettext::getProblem());
355 $this->assertFalse(NativeGettext::isActive());
356 $this->assertSame($messages, setlocale(LC_MESSAGES, '0'));
357 $this->assertSame('de', getenv('LANGUAGE'));
358 $this->assertSame('/nonexistent/locpath', getenv('LOCPATH'));
359 }
360
364 public static function foreignChanges(): iterable
365 {
366 yield 'LC_MESSAGES set to C' => [static function (): void {
367 setlocale(LC_MESSAGES, 'C');
368 }];
369 yield 'LC_ALL set to C (what initLocale() does)' => [static function (): void {
370 setlocale(LC_ALL, 'C');
371 }];
372 yield 'LANGUAGE changed' => [static function (): void {
373 putenv('LANGUAGE=de');
374 }];
375 yield 'LANGUAGE removed' => [static function (): void {
376 putenv('LANGUAGE');
377 }];
378 }
379
380 #[DataProvider('foreignChanges')]
381 public function testReactivateIfLostRepairsWhatOtherCodeChanged(\Closure $change): void
382 {
384 $this->activateBuild();
385 $change();
386 $this->assertFalse(NativeGettext::isActive(), 'precondition');
387 $this->assertSame('greeting', NativeGettext::translate(self::DOMAIN, 'greeting'), 'precondition: nothing is translated now');
388
389 $this->assertTrue(NativeGettext::reactivateIfLost());
390
391 $this->assertTrue(NativeGettext::isActive());
392 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
393 }
394
395 private function requireRealLocale(): void
396 {
397 $before = setlocale(LC_MESSAGES, '0');
398 $found = setlocale(LC_MESSAGES, self::REAL_LOCALE);
399 setlocale(LC_MESSAGES, $before);
400 if ($found === false) {
401 $this->markTestSkipped('the locale "' . self::REAL_LOCALE . '" is not installed');
402 }
403 }
404
405 #[DataProvider('previousProcessSettings')]
406 public function testActivateKeepsARealLocaleAndRestoreLeavesItWhileTakingLanguageBack(string|false $language, string|false $locpath): void
407 {
409 $this->requireRealLocale();
410 setlocale(LC_MESSAGES, self::REAL_LOCALE);
411 putenv($language === false ? 'LANGUAGE' : 'LANGUAGE=' . $language);
412 putenv($locpath === false ? 'LOCPATH' : 'LOCPATH=' . $locpath);
413
414 $this->activateBuild();
415
416 $this->assertTrue(NativeGettext::isActive());
417 $this->assertSame(self::REAL_LOCALE, setlocale(LC_MESSAGES, '0'));
418 $this->assertSame(NativeGettext::CATALOG_LANGUAGE, getenv('LANGUAGE'));
419 $this->assertSame($locpath, getenv('LOCPATH'), 'LOCPATH is not touched when the locale stays');
420 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
421
423
424 $this->assertFalse(NativeGettext::isActive());
425 $this->assertSame(self::REAL_LOCALE, setlocale(LC_MESSAGES, '0'), 'restore() leaves a locale it did not set');
426 $this->assertSame($language, getenv('LANGUAGE'));
427 $this->assertSame($locpath, getenv('LOCPATH'));
428 }
429
430 #[DataProvider('lcAllBeforeSystemLocaleActivation')]
432 {
433 $this->requireRealLocale();
435 setlocale(LC_MESSAGES, self::REAL_LOCALE);
436 putenv($lc_all === false ? 'LC_ALL' : 'LC_ALL=' . $lc_all);
437
438 $this->activateSystemLocale();
439
440 $this->assertSame(self::REAL_LOCALE, setlocale(LC_MESSAGES, '0'));
441 $this->assertSame(self::REAL_LOCALE, getenv('LC_ALL'));
442 $this->assertTrue(NativeGettext::isActive());
443 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
444
446
447 $this->assertSame($lc_all, getenv('LC_ALL'));
448 $this->assertSame(self::REAL_LOCALE, setlocale(LC_MESSAGES, '0'));
449 $this->assertFalse(NativeGettext::isActive());
450 }
451
455 public static function branches(): iterable
456 {
457 yield 'glibc branch (own locale)' => [false];
458 yield 'system locale branch' => [true];
459 }
460
461 #[DataProvider('branches')]
462 public function testReactivateIfLostKeepsALocaleOtherCodeSetForEverything(bool $system_locale): void
463 {
464 $system_locale ? $this->requireSystemLocale() : MigratedPoFixture::requireGlibcLocale($this);
465 $this->requireRealLocale();
467 $this->activateBuildInBranch($system_locale);
468 $locpath = getenv('LOCPATH');
469 setlocale(LC_ALL, self::REAL_LOCALE);
470 $this->assertFalse(NativeGettext::isActive(), 'precondition');
471
472 $this->assertTrue(NativeGettext::reactivateIfLost());
473
474 $this->assertTrue(NativeGettext::isActive());
475 $this->assertSame(self::REAL_LOCALE, setlocale(LC_MESSAGES, '0'));
476 $this->assertSame($locpath, getenv('LOCPATH'));
477 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
478 $this->assertFalse(NativeGettext::reactivateIfLost(), 'nothing left to repair');
479
481 $this->assertSame(self::REAL_LOCALE, setlocale(LC_MESSAGES, '0'), 'the locale of the other code is not ours to take back');
482 }
483
484 private function activateBuildInBranch(bool $system_locale): void
485 {
486 $system_locale ? $this->activateSystemLocale() : $this->activateBuild();
487 }
488
490 {
492 $this->requireRealLocale();
493 setlocale(LC_MESSAGES, self::REAL_LOCALE);
494 putenv('LANGUAGE=de');
495 $this->assertDirectoryDoesNotExist($this->root . '/locale-none');
496
497 $this->assertFalse(NativeGettext::activate($this->root . '/locale-none'));
498
499 $this->assertStringContainsString(NativeGettext::LOCALE_NAME, (string) NativeGettext::getProblem());
500 $this->assertFalse(NativeGettext::isActive());
501 $this->assertSame(self::REAL_LOCALE, setlocale(LC_MESSAGES, '0'));
502 $this->assertSame('de', getenv('LANGUAGE'));
503 }
504
508 public static function localesThatAreNotReal(): iterable
509 {
510 yield 'C.UTF-8' => ['C.UTF-8'];
511 yield 'C.utf8' => ['C.utf8'];
512 yield 'POSIX' => ['POSIX'];
513 yield 'C' => ['C'];
514 }
515
516 #[DataProvider('localesThatAreNotReal')]
518 {
520 if (setlocale(LC_MESSAGES, $locale) === false) {
521 $this->markTestSkipped('the locale "' . $locale . '" is not installed');
522 }
523 $before = setlocale(LC_MESSAGES, '0');
524
525 $this->activateBuild();
526
527 $this->assertSame(NativeGettext::LOCALE_NAME, setlocale(LC_MESSAGES, '0'));
528 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
529
531
532 $this->assertSame($before, setlocale(LC_MESSAGES, '0'));
533 }
534
536 {
538 $before = setlocale(LC_MESSAGES, '0');
539 $this->activateBuild();
540 putenv('LANGUAGE');
541 $this->assertFalse(NativeGettext::isActive(), 'precondition');
542
543 $this->assertTrue(NativeGettext::reactivateIfLost());
544
545 $this->assertSame(NativeGettext::LOCALE_NAME, setlocale(LC_MESSAGES, '0'));
546 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
547
549
550 $this->assertSame($before, setlocale(LC_MESSAGES, '0'), 'the own locale is ours, so it is taken back');
551 $this->assertNotSame(NativeGettext::CATALOG_LANGUAGE, getenv('LANGUAGE'));
552 }
553
554 #[RunInSeparateProcess]
556 {
558 $this->requireRealLocale();
559 if (!extension_loaded('intl')) {
560 $this->markTestSkipped('needs ext-intl');
561 }
562 setlocale(LC_ALL, [self::REAL_LOCALE]);
563 setlocale(LC_NUMERIC, 'C');
564
565 $this->activateBuild();
566
567 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
568 $this->assertSame('de_DE', \locale_get_default());
569 }
570
572 {
573 // setUp()'s build activated native gettext - restore() keeps that locale for a reactivation
575 $this->assertFalse(NativeGettext::reactivateIfLost(), 'never activated');
576 $this->assertNotSame(NativeGettext::LOCALE_NAME, setlocale(LC_MESSAGES, '0'));
577
578 $this->activateBuild();
579 $this->assertFalse(NativeGettext::reactivateIfLost(), 'still active');
580 }
581
583 {
584 $this->activateBuild();
586 $this->assertFalse(NativeGettext::isActive(), 'precondition');
587
588 $this->assertTrue(NativeGettext::reactivateIfLost());
589 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
590 }
591
593 {
594 $this->activateBuild();
596
598
599 $this->assertTrue(NativeGettext::isThreadSafe());
600 $this->assertTrue(NativeGettext::isActive(), 'other threads may be using the settings');
601 }
602
607 #[DataProvider('foreignChanges')]
609 {
610 $this->activateBuild();
613 $this->assertTrue(NativeGettext::isActive(), 'precondition: nothing was taken back');
614 $change();
615 $this->assertFalse(NativeGettext::isActive(), 'precondition');
616
617 $this->assertTrue(NativeGettext::reactivateIfLost());
618
619 $this->assertSame('Hallo', NativeGettext::translate(self::DOMAIN, 'greeting'));
620 }
621
623 {
625 $this->activateBuild();
626 setlocale(LC_MESSAGES, 'C');
627 MigratedPoFixture::removeDirectory($this->locale_directory . '/' . NativeGettext::LOCALE_NAME);
628
629 $this->assertFalse(NativeGettext::reactivateIfLost());
630
631 $this->assertStringContainsString('is missing', (string) NativeGettext::getProblem());
632 $this->assertFalse(NativeGettext::isActive());
633 }
634
638 public static function catalogs(): iterable
639 {
640 yield 'the catalog of the build' => ['build', true];
641 yield 'garbage instead of a catalog' => ['garbage', false];
642 yield 'a catalog without header (not one of ours)' => ['no-header', false];
643 yield 'a domain without file' => ['missing', false];
644 }
645
646 #[DataProvider('catalogs')]
647 public function testIsLoadedTellsAReadableCatalogOfOurOwnByItsHeader(string $kind, bool $expected): void
648 {
649 $this->activateBuild();
650 $directory = $this->build_directory;
651 $domain = self::DOMAIN;
652 if ($kind !== 'build') {
653 $domain = 'other.' . $kind;
654 $directory = $this->root . '/catalogs-' . $kind;
655 $file = $directory . '/' . NativeGettext::CATALOG_DIRECTORY . '/' . $domain . '.mo';
656 mkdir(dirname($file), 0775, true);
657 if ($kind === 'garbage') {
658 file_put_contents($file, "this is no .mo file\n");
659 } elseif ($kind === 'no-header') {
660 $catalog = new TranslationCatalog();
661 $entry = new TranslationEntry(null, 'greeting');
662 $entry->translate('Hallo');
663 $catalog->add($entry);
664 file_put_contents($file, $catalog->toMoString());
665 }
666 }
667 $this->assertTrue(NativeGettext::bind($domain, $directory));
668
669 $this->assertSame($expected, NativeGettext::isLoaded($domain));
670 }
671
673 {
674 $this->assertFalse(NativeGettext::bind('ntg.xx', $this->root . '/does-not-exist'));
675 }
676
680 public static function messageIdsThatCannotBeLookedUp(): iterable
681 {
682 yield 'empty (would answer with the header)' => [''];
683 yield 'longer than PHP accepts' => [str_repeat('x', 4097)];
684 yield 'unknown' => ['no_such_key'];
685 }
686
687 #[DataProvider('messageIdsThatCannotBeLookedUp')]
688 public function testTranslateAnswersTheMessageIdItself(string $message_id): void
689 {
690 $this->activateBuild();
691
692 $this->assertSame($message_id, NativeGettext::translate(self::DOMAIN, $message_id));
693 $this->assertSame($message_id, NativeGettext::translatePlural(self::DOMAIN, $message_id, 2));
694 }
695
699 public static function quantities(): iterable
700 {
701 yield 'one' => [1, 'Eintrag'];
702 yield 'zero' => [0, 'Einträge'];
703 yield 'minus one counts like one' => [-1, 'Eintrag'];
704 yield 'minus five counts like five' => [-5, 'Einträge'];
705 yield 'the largest integer' => [PHP_INT_MAX, 'Einträge'];
706 yield 'the smallest integer (abs() would overflow)' => [PHP_INT_MIN, 'Einträge'];
707 }
708
709 #[DataProvider('quantities')]
710 public function testTranslatePluralSelectsTheFormOfTheAbsoluteQuantity(int $n, string $expected): void
711 {
712 $this->activateBuild();
713
714 $this->assertSame(
715 $expected,
717 );
718 }
719}
trait RestoresProcessState
Whatever a test does to the process (locale, LANGUAGE/LC_*, LOCPATH, the static state of the runtime ...
An in-memory gettext catalog (header block plus messages) and the only place of the component that re...
One message of a TranslationCatalog (a msgctxt/msgid/msgstr block of a .po file).
static buildDirectory(string $artifact_directory, string $build_id)
artifacts/language/<build>: one complete build, bound with bindtextdomain() as it is (the C library l...
static buildLocaleDirectory(string $build_directory)
The directory with the locale NativeGettext activates (NativeGettext::LOCALE_NAME): <build>/locale,...
NativeGettext changes process-wide state (LC_MESSAGES, LANGUAGE, LOCPATH, LC_ALL): what activate() le...
testReactivateIfLostRepairsWhatOtherCodeChangedInTheSystemLocaleBranch(\Closure $change)
testTranslatePluralSelectsTheFormOfTheAbsoluteQuantity(int $n, string $expected)
testSystemLocaleBranchKeepsARealLocaleSetsLcAllToItAndRestoreGivesLcAllBack(string|false $lc_all)
testActivateServesTheBuildAndRestoreGivesTheProcessStateBack(string|false $language, string|false $locpath)
testReactivateIfLostRepairsAForeignResetInAThreadSafePhpToo(\Closure $change)
In a thread-safe PHP restore() does nothing, but another thread may reset what activate() set; the lo...
testRestoreAfterARepeatedActivationGivesBackLcAllFromBeforeTheFirstOne(string|false $lc_all)
testActivateKeepsARealLocaleAndRestoreLeavesItWhileTakingLanguageBack(string|false $language, string|false $locpath)
testALocaleOfTheCFamilyIsNotRealSoTheOwnLocaleIsActivatedAndTakenBackAgain(string $locale)
testSystemLocaleBranchSetsLcAllToTheActiveLocaleAndRestoreGivesItBackExactly(string|false $lc_all)
testSystemLocaleIsOnlyActiveAfterActivateAndRestoreGivesTheProcessStateBack(string $locale, string|false $language)
testIsLoadedTellsAReadableCatalogOfOurOwnByItsHeader(string $kind, bool $expected)
static reactivateIfLost()
Activates the locale again if it was activated before and the settings changed since.
static translatePlural(string $domain, string $message_id, int $n)
The form of the plural message $message_id the "Plural-Forms" rule of the catalog selects for $n (a n...
static resetForTests()
Forgets the activation (without taking the settings back) - for tests only.
static activate(string $locale_directory)
Sets LANGUAGE and, if LC_MESSAGES holds no real locale, the locale LOCALE_NAME of $locale_directory (...
static isActive()
Whether activate() succeeded and its settings are still in place (other code, e.g.
static isThreadSafe()
Whether PHP is thread-safe (ZTS): the settings of activate() then apply to every thread and are not t...
static bind(string $domain, string $directory)
Binds $domain to $directory with the codeset UTF-8.
const string CATALOG_LANGUAGE
The value of LANGUAGE: the directory below a bound directory the catalogs are looked up in.
static useThreadSafeForTests(?bool $thread_safe)
Makes isThreadSafe() return $thread_safe (null: PHP_ZTS again) - for tests only.
static translate(string $domain, string $message_id)
The translation of $message_id in $domain, or $message_id itself if there is none (as the C library a...
static useSystemLocaleForTests(?bool $system_locale)
Makes usesSystemLocale() return $system_locale (null: by PHP_OS_FAMILY again) - for tests only.
static isLoaded(string $domain)
Whether the catalog of the bound $domain is readable: its header (the translation of the empty messag...
const array SYSTEM_LOCALES
LC_MESSAGES candidates where the system locale is used, see usesSystemLocale().
static restore()
Takes LANGUAGE, LC_MESSAGES (if activate() set it) and LC_ALL back to the state before the first acti...
Fixture helpers for PO/MO migrated modules, built with the component's own gettext adapter (Translati...
static directory(string $prefix, string $path, bool $local=false)
static requireGlibcLocale(\PHPUnit\Framework\TestCase $test)
Skips $test without the glibc locale (macOS, BSD), otherwise makes NativeGettext use it.
static catalog(string $module, array $entries)
static writePo(string $file, TranslationCatalog $catalog)
static removeDirectory(string $directory)
static build(LanguageFileDirectoryManager $manager, string $ilias_absolute_path)
Builds the shipped .po files of $manager below $ilias_absolute_path into the artifact directory of re...
static resetRuntime(?string $artifact_directory=null)
Forgets the static state of the runtime read path and points build and runtime at $artifact_directory...