ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
LocalChangeCommentsTest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
25use PHPUnit\Framework\Attributes\DataProvider;
26use PHPUnit\Framework\TestCase;
27
33class LocalChangeCommentsTest extends TestCase
34{
36 {
37 $translation = new TranslationEntry('demo', 'greeting');
38 LocalChangeComments::setOriginal($translation, 'Hallo');
39
40 $this->assertSame('Hallo', LocalChangeComments::getOriginal($translation));
41 $this->assertNull(LocalChangeComments::getLocalChange($translation));
42 }
43
45 {
46 $translation = new TranslationEntry('demo', 'greeting');
47 LocalChangeComments::setOriginal($translation, 'Hallo');
48
49 $now = new \DateTimeImmutable('2026-09-21T10:00:00Z');
50 LocalChangeComments::refresh($translation, 'Hallo', 'Hallo, geändert', $now);
51
52 $this->assertSame('2026-09-21T10:00:00Z', LocalChangeComments::getLocalChange($translation));
53 }
54
56 {
57 $translation = new TranslationEntry('demo', 'greeting');
58 LocalChangeComments::setOriginal($translation, 'Hallo');
60 $translation,
61 'Hallo',
62 'Hallo, geändert',
63 new \DateTimeImmutable('2026-09-21T10:00:00Z')
64 );
65
67 $translation,
68 'Hallo, geändert',
69 'Hallo',
70 new \DateTimeImmutable('2026-09-21T14:00:00Z')
71 );
72
73 $this->assertNull(LocalChangeComments::getLocalChange($translation));
74 // "original" itself must never be touched by refresh()
75 $this->assertSame('Hallo', LocalChangeComments::getOriginal($translation));
76 }
77
79 {
80 $translation = new TranslationEntry('demo', 'greeting');
81 LocalChangeComments::setOriginal($translation, 'Hallo');
83 $translation,
84 'Hallo',
85 'Hallo, geändert',
86 new \DateTimeImmutable('2026-09-21T10:00:00Z')
87 );
88
89 // same value written again, e.g. an unrelated key in the same module was edited too
91 $translation,
92 'Hallo, geändert',
93 'Hallo, geändert',
94 new \DateTimeImmutable('2026-09-21T12:00:00Z')
95 );
96
97 $this->assertSame('2026-09-21T10:00:00Z', LocalChangeComments::getLocalChange($translation));
98 }
99
101 {
102 $translation = new TranslationEntry('demo', 'greeting');
103 LocalChangeComments::setOriginal($translation, 'Hallo');
105 $translation,
106 'Hallo',
107 'Hallo, geändert',
108 new \DateTimeImmutable('2026-09-21T10:00:00Z')
109 );
110
112 $translation,
113 'Hallo, geändert',
114 'Hallo, nochmal geändert',
115 new \DateTimeImmutable('2026-09-21T12:00:00Z')
116 );
117
118 $this->assertSame('2026-09-21T12:00:00Z', LocalChangeComments::getLocalChange($translation));
119 }
120
126 {
127 $translation = new TranslationEntry('demo', 'brand_new_key');
128
129 LocalChangeComments::refresh($translation, '', 'Neuer Wert', new \DateTimeImmutable('2026-09-21T15:00:00Z'));
130
131 $this->assertNull(LocalChangeComments::getOriginal($translation));
132 $this->assertSame('2026-09-21T15:00:00Z', LocalChangeComments::getLocalChange($translation));
133 }
134
140 {
141 $translation = new TranslationEntry('demo', 'greeting');
142 LocalChangeComments::setOriginal($translation, 'Hallo');
143
144 LocalChangeComments::refresh($translation, 'Hallo', 'A', new \DateTimeImmutable('2026-09-21T10:00:00Z'));
145 LocalChangeComments::refresh($translation, 'A', 'B', new \DateTimeImmutable('2026-09-21T11:00:00Z'));
146 LocalChangeComments::refresh($translation, 'B', 'C', new \DateTimeImmutable('2026-09-21T12:00:00Z'));
147
148 $localChangeComments = array_values(array_filter(
149 $translation->getTranslatorComments(),
150 static fn(string $comment): bool => str_starts_with($comment, 'local_change: ')
151 ));
152
153 $this->assertCount(1, $localChangeComments);
154 $this->assertSame('local_change: 2026-09-21T12:00:00Z', $localChangeComments[0]);
155 }
156
161 #[DataProvider('valuesNeedingEscaping')]
163 string $value,
164 string $expected_comment
165 ): void {
166 $entry = new TranslationEntry('demo', 'greeting');
167 LocalChangeComments::setOriginal($entry, $value);
168
169 $this->assertSame([$expected_comment], $entry->getTranslatorComments());
170 $this->assertSame($value, LocalChangeComments::getOriginal($entry));
171 }
172
173 public static function valuesNeedingEscaping(): array
174 {
175 return [
176 'LF' => ["Zeile eins\nZeile zwei", 'original_escaped: Zeile eins\\nZeile zwei'],
177 'CRLF' => ["Zeile eins\r\nZeile zwei", 'original_escaped: Zeile eins\\r\\nZeile zwei'],
178 'lone CR' => ["a\rb", 'original_escaped: a\\rb'],
179 'trailing LF' => ["Hallo\n", 'original_escaped: Hallo\\n'],
180 'backslash' => ['C:\\pfad', 'original_escaped: C:\\\\pfad'],
181 // A literal backslash followed by "n" must not come back as a line feed
182 'literal backslash-n' => ['a\\nb', 'original_escaped: a\\\\nb'],
183 'literal backslash before a real LF' => ["a\\\nb", 'original_escaped: a\\\\\\nb'],
184 'double backslash' => ['a\\\\b', 'original_escaped: a\\\\\\\\b'],
185 'only a line break' => ["\n", 'original_escaped: \\n'],
186 ];
187 }
188
189 public static function valuesSurvivingAPoFileRoundTrip(): array
190 {
191 return self::valuesNeedingEscaping() + [
192 'leading and trailing spaces' => [' Hallo ', 'original: Hallo '],
193 'trailing tab' => ["Hallo\t", "original: Hallo\t"],
194 'only a space' => [' ', 'original: '],
195 'empty' => ['', 'original: '],
196 'zero' => ['0', 'original: 0'],
197 'escaped with surrounding spaces' => [" a\nb ", 'original_escaped: a\\nb '],
198 ];
199 }
200
206 {
207 $entry = new TranslationEntry('demo', 'greeting');
208 LocalChangeComments::setOriginal($entry, 'Hallo "Welt" %s #:# ###');
209
210 $this->assertSame(['original: Hallo "Welt" %s #:# ###'], $entry->getTranslatorComments());
211 }
212
217 #[DataProvider('legacyOriginalComments')]
218 public function testALegacyOriginalCommentIsReadVerbatim(string $comment, string $expected): void
219 {
220 $entry = new TranslationEntry('demo', 'greeting');
221 $entry->addTranslatorComment($comment);
222
223 $this->assertSame($expected, LocalChangeComments::getOriginal($entry));
224 }
225
226 public static function legacyOriginalComments(): array
227 {
228 return [
229 'flattened line break' => ['original: a b', 'a b'],
230 'unescaped backslash' => ['original: C:\\pfad', 'C:\\pfad'],
231 'backslash-n stays two characters' => ['original: a\\nb', 'a\\nb'],
232 ];
233 }
234
240 {
241 $entry = new TranslationEntry('demo', 'greeting');
242 $entry->addTranslatorComment('original: C:\\pfad');
243
244 LocalChangeComments::refresh($entry, 'C:\\pfad', 'C:\\pfad', new \DateTimeImmutable('2026-09-21T10:00:00Z'));
245 $this->assertNull(LocalChangeComments::getLocalChange($entry));
246
247 LocalChangeComments::setOriginal($entry, 'C:\\pfad');
248 $this->assertSame(['original_escaped: C:\\\\pfad'], $entry->getTranslatorComments());
249 $this->assertSame('C:\\pfad', LocalChangeComments::getOriginal($entry));
250 }
251
257 {
258 $entry = new TranslationEntry('demo', 'greeting');
259 $entry->addTranslatorComment('translator note');
260
261 LocalChangeComments::setOriginal($entry, "Zeile\nzwei");
262 LocalChangeComments::setOriginal($entry, 'einzeilig');
263 $this->assertSame(['translator note', 'original: einzeilig'], $entry->getTranslatorComments());
264 $this->assertSame('einzeilig', LocalChangeComments::getOriginal($entry));
265
266 LocalChangeComments::setOriginal($entry, "wieder\nmehrzeilig");
267 $this->assertSame(['translator note', 'original_escaped: wieder\\nmehrzeilig'], $entry->getTranslatorComments());
268 $this->assertSame("wieder\nmehrzeilig", LocalChangeComments::getOriginal($entry));
269 }
270
276 {
277 $entry = new TranslationEntry('demo', 'greeting');
278 LocalChangeComments::setOriginal($entry, "Zeile\r\nzwei");
279 $entry->addTranslatorComment('translator note');
280
281 LocalChangeComments::setOriginal($entry, "Zeile\r\nzwei");
282
283 $this->assertSame(['original_escaped: Zeile\\r\\nzwei', 'translator note'], $entry->getTranslatorComments());
284 }
285
291 {
292 $entry = new TranslationEntry('demo', 'greeting');
293 $entry->addTranslatorComment('original: Hallo');
294 $entry->addTranslatorComment('original_escaped: Alt\\nZeile');
295
296 LocalChangeComments::setOriginal($entry, 'Hallo');
297
298 $this->assertSame(['original: Hallo'], $entry->getTranslatorComments());
299 $this->assertSame('Hallo', LocalChangeComments::getOriginal($entry));
300 }
301
307 {
308 $entry = new TranslationEntry('demo', 'greeting');
309 LocalChangeComments::setOriginal($entry, "Zeile eins\r\nZeile zwei");
310
311 LocalChangeComments::refresh($entry, "Zeile eins\r\nZeile zwei", "Zeile eins\r\nZeile zwei", new \DateTimeImmutable('2026-09-21T10:00:00Z'));
312 $this->assertNull(LocalChangeComments::getLocalChange($entry));
313
314 LocalChangeComments::refresh($entry, "Zeile eins\r\nZeile zwei", "Zeile eins\nZeile zwei", new \DateTimeImmutable('2026-09-21T11:00:00Z'));
315 $this->assertSame('2026-09-21T11:00:00Z', LocalChangeComments::getLocalChange($entry));
316 }
317
325 #[DataProvider('valuesSurvivingAPoFileRoundTrip')]
326 public function testTheOriginalSurvivesAPoFileRoundTrip(string $value, string $expected_comment): void
327 {
328 $entry = new TranslationEntry('demo', 'greeting');
329 $entry->translate($value);
330 LocalChangeComments::setOriginal($entry, $value);
331 $entry->addTranslatorComment('local_change: 2026-09-21T10:00:00Z');
332 $catalog = new TranslationCatalog();
333 $catalog->add($entry);
334
335 $parsed = TranslationCatalog::fromPoString($catalog->toPoString())->find('demo', 'greeting');
336
337 $this->assertNotNull($parsed);
338 $this->assertSame([$expected_comment, 'local_change: 2026-09-21T10:00:00Z'], $parsed->getTranslatorComments());
339 $this->assertSame($value, LocalChangeComments::getOriginal($parsed));
340 $this->assertSame($value, $parsed->getTranslation());
341 }
342
348 {
349 $entry = new TranslationEntry('demo', 'greeting');
350 LocalChangeComments::setOriginal($entry, 'Hallo');
351 $entry->addTranslatorComment('translator note');
352
353 LocalChangeComments::setOriginal($entry, 'Hallo');
354
355 $this->assertSame(['original: Hallo', 'translator note'], $entry->getTranslatorComments());
356 }
357
359 {
360 $entry = new TranslationEntry('demo', 'greeting');
361 LocalChangeComments::setOriginal($entry, 'Hallo');
362 $entry->addTranslatorComment('translator note');
363
364 LocalChangeComments::setOriginal($entry, 'Servus');
365
366 $this->assertSame(['translator note', 'original: Servus'], $entry->getTranslatorComments());
367 $this->assertSame('Servus', LocalChangeComments::getOriginal($entry));
368 }
369
375 {
376 $entry = new TranslationEntry('demo', 'greeting');
377 LocalChangeComments::setOriginal($entry, ' Hallo ');
378
379 $this->assertSame(' Hallo ', LocalChangeComments::getOriginal($entry));
380
381 LocalChangeComments::refresh($entry, ' Hallo ', ' Hallo ', new \DateTimeImmutable('2026-09-21T10:00:00Z'));
382 $this->assertNull(LocalChangeComments::getLocalChange($entry));
383 }
384
390 {
391 $entry = new TranslationEntry('demo', 'untranslated');
393
394 $this->assertSame('', LocalChangeComments::getOriginal($entry));
395 LocalChangeComments::refresh($entry, '', '', new \DateTimeImmutable('2026-09-21T10:00:00Z'));
396 $this->assertNull(LocalChangeComments::getLocalChange($entry));
397 }
398
404 {
405 $entry = new TranslationEntry('demo', 'greeting');
406 $entry->addTranslatorComment('original: Hallo');
407 $entry->addTranslatorComment('translator note');
408 $entry->addTranslatorComment('original_escaped: Zeile\\nzwei');
409
411
412 $this->assertNull(LocalChangeComments::getOriginal($entry));
413 $this->assertSame(['translator note'], $entry->getTranslatorComments());
414 }
415
417 {
418 $entry = new TranslationEntry('demo', 'greeting');
419 $entry->addTranslatorComment('translator note');
420 LocalChangeComments::setOriginal($entry, 'Hallo');
421 LocalChangeComments::refresh($entry, 'Hallo', 'Hi', new \DateTimeImmutable('2026-09-21T10:00:00Z'));
422
424
425 $this->assertNull(LocalChangeComments::getOriginal($entry));
426 $this->assertSame(
427 ['translator note', 'local_change: 2026-09-21T10:00:00Z'],
428 $entry->getTranslatorComments()
429 );
430 }
431
433 {
434 $entry = new TranslationEntry('demo', 'greeting');
435 LocalChangeComments::refresh($entry, '', 'Hi', new \DateTimeImmutable('2026-12-31T23:59:59Z'));
436
437 $this->assertSame('2026-12-31 23:59:59', LocalChangeComments::getLocalChangeAsDatabaseTimestamp($entry));
438 }
439
441 {
442 $entry = new TranslationEntry('demo', 'greeting');
443 LocalChangeComments::setOriginal($entry, 'Hallo');
444
446 }
447
453 {
454 $entry = new TranslationEntry('demo', 'greeting');
455 $entry->addTranslatorComment('local_change: yesterday');
456
457 $this->assertSame('yesterday', LocalChangeComments::getLocalChange($entry));
459 }
460
461 public function testGetRemarkIsNullWithoutOneSet(): void
462 {
463 $entry = new TranslationEntry('demo', 'greeting');
464
465 $this->assertNull(LocalChangeComments::getRemark($entry));
466 }
467
469 {
470 $entry = new TranslationEntry('demo', 'greeting');
471 LocalChangeComments::setRemark($entry, 'Bitte prüfen');
472
473 $this->assertSame(['remark: Bitte prüfen'], $entry->getTranslatorComments());
474 $this->assertSame('Bitte prüfen', LocalChangeComments::getRemark($entry));
475 }
476
481 #[DataProvider('valuesNeedingEscaping')]
482 public function testSetRemarkEscapesALineBreakOrBackslashLikeSetOriginalDoes(string $value, string $unused_original_comment): void
483 {
484 $entry = new TranslationEntry('demo', 'greeting');
485 LocalChangeComments::setRemark($entry, $value);
486
487 $comments = $entry->getTranslatorComments();
488 $this->assertCount(1, $comments);
489 $this->assertStringStartsWith('remark_escaped: ', $comments[0]);
490 $this->assertSame($value, LocalChangeComments::getRemark($entry));
491 }
492
494 {
495 $entry = new TranslationEntry('demo', 'greeting');
496 LocalChangeComments::setRemark($entry, ' Bemerkung ');
497
498 $this->assertSame(' Bemerkung ', LocalChangeComments::getRemark($entry));
499 }
500
508 {
509 $entry = new TranslationEntry('demo', 'greeting');
510 LocalChangeComments::setRemark($entry, 'Bitte prüfen');
511
513
514 $this->assertNull(LocalChangeComments::getRemark($entry));
515 $this->assertSame([], $entry->getTranslatorComments());
516 }
517
519 {
520 $entry = new TranslationEntry('demo', 'greeting');
521 LocalChangeComments::setRemark($entry, 'Bitte prüfen');
522
523 LocalChangeComments::setRemark($entry, null);
524
525 $this->assertNull(LocalChangeComments::getRemark($entry));
526 }
527
532 public function testSetRemarkThrowsForInvalidUtf8(): void
533 {
534 $entry = new TranslationEntry('demo', 'greeting');
535
536 $this->expectException(\InvalidArgumentException::class);
537 LocalChangeComments::setRemark($entry, "Ung\xFCltig");
538 }
539
546 {
547 $entry = new TranslationEntry('demo', 'greeting');
548 // 250 "ä" (2 bytes each in UTF-8) plus one more character right at the boundary
550
551 LocalChangeComments::setRemark($entry, $remark);
552
553 $stored = LocalChangeComments::getRemark($entry);
554 $this->assertSame(LocalChangeComments::MAX_REMARK_LENGTH, mb_strlen((string) $stored));
555 $this->assertSame(str_repeat('ä', LocalChangeComments::MAX_REMARK_LENGTH), $stored);
556 $this->assertTrue(mb_check_encoding((string) $stored, 'UTF-8'), 'no character was split in half');
557 }
558
560 {
561 $entry = new TranslationEntry('demo', 'greeting');
563
564 LocalChangeComments::setRemark($entry, $remark);
565
566 $this->assertSame($remark, LocalChangeComments::getRemark($entry));
567 }
568
574 {
575 $entry = new TranslationEntry('demo', 'greeting');
576 LocalChangeComments::setRemark($entry, "mit\nZeilenumbruch");
577
578 LocalChangeComments::setRemark($entry, 'ohne Umbruch');
579
580 $this->assertSame(['remark: ohne Umbruch'], $entry->getTranslatorComments());
581 $this->assertSame('ohne Umbruch', LocalChangeComments::getRemark($entry));
582 }
583
590 {
591 $entry = new TranslationEntry('demo', 'greeting');
592 LocalChangeComments::setOriginal($entry, 'Hallo');
593 LocalChangeComments::refresh($entry, 'Hallo', 'Servus', new \DateTimeImmutable('2026-09-21T10:00:00Z'));
594
595 LocalChangeComments::setRemark($entry, 'Bitte prüfen');
596
597 $this->assertSame('Hallo', LocalChangeComments::getOriginal($entry));
598 $this->assertSame('2026-09-21T10:00:00Z', LocalChangeComments::getLocalChange($entry));
599 $this->assertSame('Bitte prüfen', LocalChangeComments::getRemark($entry));
600
601 LocalChangeComments::setRemark($entry, null);
602
603 $this->assertNull(LocalChangeComments::getRemark($entry));
604 $this->assertSame('Hallo', LocalChangeComments::getOriginal($entry), 'removing the remark must not touch "original"');
605 $this->assertSame('2026-09-21T10:00:00Z', LocalChangeComments::getLocalChange($entry), 'nor "local_change"');
606 }
607
613 {
614 $entry = new TranslationEntry('demo', 'greeting');
615 LocalChangeComments::setOriginal($entry, 'Eintrag', 0);
616 LocalChangeComments::setOriginal($entry, 'Einträge', 1);
617
618 LocalChangeComments::setRemark($entry, 'Bitte prüfen');
619
620 $this->assertSame('Eintrag', LocalChangeComments::getOriginal($entry, 0));
621 $this->assertSame('Einträge', LocalChangeComments::getOriginal($entry, 1));
622 $this->assertSame('Bitte prüfen', LocalChangeComments::getRemark($entry));
623
625
626 $this->assertSame('Bitte prüfen', LocalChangeComments::getRemark($entry), 'removing the form originals must not touch the remark');
627 }
628}
$comment
Definition: buildRTE.php:72
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).
The PO/MO pilot's "locally changed" tracking: lng_data.local_change/remarks reimplemented as gettext ...
testRemarkCoexistsWithPerFormOriginalsOfAPluralMessage()
A plural message's per-form "original[<form>]" comments must not be confused with,...
testRefreshTreatsAMissingOriginalAsAlwaysLocallyChanged()
A key without a baseline (never converted, e.g.
testSetRemarkThrowsForInvalidUtf8()
Invalid UTF-8 would make the overlay .po itself unreadable (TranslationCatalog::fromPoString() reject...
testSetOriginalRemovesAStaleCommentOfTheOtherPrefix()
Both "original" comments present (a hand-edited or mixed file): setOriginal() with the value of one o...
testSetRemarkSwitchingBetweenEscapedAndPlainKeepsExactlyOneComment()
Switching from an escaped to a plain value (or back) leaves exactly one remark comment behind - never...
testAnEmptyOriginalIsDistinctFromNoOriginal()
An empty string is a valid original (an untranslated shipped entry) and must not be confused with "no...
testAValueWithoutLineBreakOrBackslashIsStoredVerbatimUnderTheLegacyPrefix()
Every other value is stored verbatim under "original: ", byte-identical to earlier versions,...
testSetOriginalWithTheSameValueKeepsTheCommentOrderStable()
setOriginal() with the value already stored must not move the comment: a re-run of an unchanged sync ...
testSwitchingBetweenEscapedAndVerbatimValuesKeepsExactlyOneOriginalComment()
Switching between a value that needs escaping and one that does not never leaves both prefixes behind...
testSetRemarkEscapesALineBreakOrBackslashLikeSetOriginalDoes(string $value, string $unused_original_comment)
Same escaping mechanism as "original" (setEscapable()), just under its own prefix - a remark with a l...
testOriginalKeepsLeadingAndTrailingWhitespaceExactly()
"original" is compared byte for byte against the value - leading/trailing whitespace is part of it an...
testSetRemarkIsCutToMaxRemarkLengthCharactersNotBytes()
lng_data.remarks is limited to 250 characters (see ilObjLanguage::replaceLangEntry()) - the overlay's...
testSetRemarkWithAnEmptyStringRemovesIt()
An empty remark is treated the same as no remark at all - unlike "original", which deliberately disti...
testALegacyCommentWithABackslashIsMigratedToTheEscapedPrefixWithoutALocalChange()
A legacy comment holding a value that now needs escaping reads as unchanged (no local change),...
testRemoveOriginalRemovesBothTheLegacyAndTheEscapedPrefix()
removeOriginal() removes both prefixes - an escaped "original" left behind would still be returned by...
testAValueWithALineBreakOrBackslashRoundTripsExactlyViaASingleEscapedComment(string $value, string $expected_comment)
A translator comment is a single PO line: a value with a line break or backslash is stored reversibly...
testRemarkCoexistsWithOriginalAndLocalChangeOnTheSameEntry()
"remark" coexists independently with "original"/"local_change" (a plural message's "original[<form>]"...
testRefreshNeverLeavesMoreThanOneLocalChangeCommentBehind()
A naive "add a comment" on every refresh() would leave every past timestamp sitting in the file forev...
testALegacyOriginalCommentIsReadVerbatim(string $comment, string $expected)
An "original: " comment written by an earlier version (line breaks already flattened into spaces,...
testTheOriginalSurvivesAPoFileRoundTrip(string $value, string $expected_comment)
The "original" comment survives a toPoString()/fromPoString() round trip byte for byte - and so does ...
testGetLocalChangeAsDatabaseTimestampIsNullForAnUnparsableTimestamp()
A hand-edited or otherwise mangled timestamp must not turn into a bogus date that then wins a max() c...
testRefreshComparesAMultiLineValueAgainstTheDecodedOriginal()
refresh() compares against the DECODED original: an unchanged multi-line value is no local change,...
testSetOriginalWithTheSameEscapedValueKeepsTheCommentOrderStable()
Idempotence for escaped values, too: setting the same multi-line value again keeps the comment where ...
static setRemark(TranslationEntry $entry, ?string $remark)
Sets the remark of $entry - null or '' removes it; cut to MAX_REMARK_LENGTH characters.
static removeFormOriginals(TranslationEntry $entry)
Removes the "original" of every plural form (original[<form>]:) - e.g.
static getRemark(TranslationEntry $entry)
The decoded remark of $entry, exactly as it was passed to setRemark(), null without one.
static refresh(TranslationEntry $entry, string $previous_value, string $value, DateTimeImmutable $now)
Called by MigratedLanguageFileSync::sync() for every entry it writes, after the new value was set.
const int MAX_REMARK_LENGTH
How many characters of a remark are kept - like lng_data.remarks.
static setOriginal(TranslationEntry $entry, string $value, ?int $form=null)
Does not touch "local_change": refresh() recomputes it right after.
static getOriginal(TranslationEntry $entry, ?int $form=null)
The decoded "original" value as passed to setOriginal().
static removeOriginal(TranslationEntry $entry, ?int $form=null)
With $form null the "original" of a singular message, otherwise the one of that plural form.
static getLocalChangeAsDatabaseTimestamp(TranslationEntry $entry)
getLocalChange() in the database's "Y-m-d H:i:s" (UTC) format, as used by lng_data....
$comments