19declare(strict_types=1);
31use PHPUnit\Framework\Attributes\DataProvider;
32use PHPUnit\Framework\TestCase;
38 $this->
shipPo([
'greeting' =>
'Hallo',
'farewell' =>
'Tschüss']);
40 'greeting' => [
'value' =>
'Servus',
'original' =>
'Hallo',
'local_change' =>
'2025-05-05T05:05:05Z'],
41 'farewell' => [
'value' =>
'Tschüss',
'original' =>
'Tschüss'],
42 'added_locally' => [
'value' =>
'Eigene Variable',
'local_change' =>
'2025-06-06T06:06:06Z'],
44 $this->
writeLang(
'lang/customizing/ilias_de.lang.local', [
'pilot#:#greeting#:#Aus Customizing']);
46 $this->
manager()->insertLanguageForRemovingLocalChanges(
'de');
50 'pilot|greeting' => [
'value' =>
'Hallo',
'local_change' =>
null],
51 'pilot|farewell' => [
'value' =>
'Tschüss',
'local_change' =>
null],
55 $this->assertSame([
'greeting' =>
'Hallo',
'farewell' =>
'Tschüss'], $this->
lngModules()[self::MODULE]);
57 $this->assertFileDoesNotExist($this->
overlayBase() .
'.po');
60 $this->assertSame([
'greeting',
'farewell'], array_keys($effective),
'the locally added variable is gone');
61 $this->assertSame(
'Hallo', $effective[
'greeting'][
'value']);
62 $this->assertFalse($effective[
'greeting'][
'local_change']);
72 $this->
shipPo([
'greeting' =>
'Hallo',
'farewell' =>
'Tschüss']);
74 'greeting' => [
'value' =>
'Hallo',
'original' =>
'Hallo'],
75 'farewell' => [
'value' =>
'Tschüss',
'original' =>
'Tschüss'],
77 $this->db_entries = [self::MODULE => [
'greeting' =>
'Hallo',
'farewell' =>
'Tschüss'],
'common' => [
'yes' =>
'Ja']];
78 $this->
writeLang(
'lang/customizing/ilias_de.lang.local', [
'pilot#:#greeting#:#Grüß Gott']);
80 $this->
manager()->insertLanguageForApplyingLocalChanges(
'de');
84 'pilot|greeting' => [
'value' =>
'Grüß Gott',
'local_change' => self::NOW],
85 'pilot|farewell' => [
'value' =>
'Tschüss',
'local_change' =>
null],
90 [self::MODULE => [
'greeting' =>
'Grüß Gott',
'farewell' =>
'Tschüss'],
'common' => [
'yes' =>
'Ja']],
93 $greeting = $this->
overlayPo()->find(
null,
'greeting');
94 $this->assertSame(
'Grüß Gott', $greeting->getTranslation());
95 $this->assertSame(
'Hallo', LocalChangeComments::getOriginal($greeting));
96 $this->assertNotNull(LocalChangeComments::getLocalChange($greeting));
106 $this->
shipPo([
'greeting' =>
'Hallo',
'farewell' =>
'Tschüss',
'obsolete' =>
'Alt']);
107 $this->
manager()->insertLanguageForInstallation(
'de');
109 [
'value' =>
'Alt',
'local_change' =>
null],
110 $this->
lngData()[
'pilot|obsolete'],
111 'sanity: the row exists after the first install'
114 $this->
shipPo([
'greeting' =>
'Servus',
'farewell' =>
'Pfiat di']);
116 'greeting' => [
'value' =>
'Hallo',
'original' =>
'Hallo'],
117 'farewell' => [
'value' =>
'Auf Wiedersehen',
'original' =>
'Tschüss',
'local_change' =>
'2025-04-04T04:04:04Z'],
118 'added_locally' => [
'value' =>
'Eigene Var',
'local_change' =>
'2025-03-03T03:03:03Z'],
120 $this->db_local_changes = [self::MODULE => [
'farewell' =>
'Auf Wiedersehen']];
121 $this->db_entries = [
122 'common' => [
'yes' =>
'Ja',
'no' =>
'Nein'],
123 self::MODULE => [
'greeting' =>
'Hallo',
'farewell' =>
'Tschüss',
'obsolete' =>
'Alt'],
126 $this->
manager()->insertLanguageForApplyingLocalChanges(
'de');
130 $this->
lngModules()[self::MODULE][
'greeting'],
131 'stored "Hallo", shipped now "Servus" -> "Servus"'
135 $this->
lngModules()[self::MODULE][
'farewell'],
136 'a real local change (L != S != O) is kept'
140 $this->
lngModules()[self::MODULE][
'added_locally'],
141 'an overlay-only local change is taken over'
143 $this->assertArrayNotHasKey(
'obsolete', $this->
lngModules()[self::MODULE]);
145 [
'yes' =>
'Ja',
'no' =>
'Nein'],
147 'a non-migrated module in the seed stays complete'
150 $this->assertSame([
'value' =>
'Servus',
'local_change' =>
null], $this->
lngData()[
'pilot|greeting']);
152 [
'value' =>
'Eigene Var',
'local_change' =>
'2025-03-03 03:03:03'],
153 $this->
lngData()[
'pilot|added_locally']
155 $this->assertArrayNotHasKey(
158 'the DELETE ... local_change IS NULL bulk purge took effect: the stale unchanged row is gone'
160 $this->assertNotEmpty(
163 static fn(
string $q):
bool => str_starts_with($q,
'DELETE FROM lng_data')
164 && str_contains($q,
'local_change IS NULL')
165 && str_contains($q,
"identifier NOT IN ('")
167 'the bulk delete of unchanged migrated rows was actually issued'
177 $this->
shipPo([
'greeting' =>
'Hallo',
'obsolete' =>
'Alt']);
178 $this->
manager()->insertLanguageForInstallation(
'de');
179 $this->
shipPo([
'greeting' =>
'Servus']);
180 $already_recorded = count($this->queries);
182 $this->
manager()->insertLanguageForApplyingLocalChanges(
'de');
185 foreach (array_slice($this->queries, $already_recorded) as $query) {
186 if (str_starts_with($query,
'INSERT INTO lng_data')) {
187 $statements[] =
'write';
188 } elseif (str_starts_with($query,
'DELETE FROM lng_data') && str_contains($query,
'local_change IS NULL')) {
189 $statements[] =
'purge';
192 $this->assertSame([
'write',
'purge'], array_values(array_unique($statements)));
An in-memory gettext catalog (header block plus messages) and the only place of the component that re...
Reads the shipped .po of a migrated module and maintains its per-installation overlay: the ....
The module array just written to lng_modules could not be read back - almost always a wrong collation...
Write access to the language installation: installing, flushing and registering languages in the data...
testApplyingLocalChangesReconcilesEveryCaseInOneSweepAndPurgesTheStaleUnchangedRow()
The pre-INSERT "DELETE FROM lng_data WHERE ... local_change IS NULL AND module IN (....
testApplyingLocalChangesPutsTheCustomizingValueOnTopOfTheStoredEntries()
Applying local changes reconciles a migrated module with its shipped .po like an update: the not loca...
testApplyingLocalChangesWritesTheRowsBeforeItPurgesTheStaleOnes()
Written first, purged after: an aborted write must leave the former rows, not a gap.
testRemovingLocalChangesRebuildsTheOverlayFromTheShippedPo()
Fixtures of the LanguageInstallationManager tests for modules migrated to PO/MO ("pilot").
shipPo(array $entries, string $lang_key='de')
seedOverlay(array $entries)
effective()
Shipped .po plus overlay delta, as served (MigratedLanguageFileSync::loadModuleTranslations()) - the ...
manager(?\Closure $client_data_dir_resolver=null, ?\Closure $language_cache_invalidator=null, bool $with_resolver=true, bool $with_logger=true, ?LoggerInterface $logger=null)
writeLang(string $relative_file, array $lines)
static overlayMo(string $base_path)
The compiled catalog of the revision current names; a non-existing path if there is none.
A directory of language files with a path and a prefix for them.
Read-only access to the installed languages (database) and the available or valid language files (fil...