@@ -31,6 +31,21 @@ def write_po(root, name, translation):
3131 return path
3232
3333
34+ DATED_PO = (
35+ '# Catalog\n '
36+ 'msgid ""\n '
37+ 'msgstr ""\n '
38+ '"Content-Type: text/plain; charset=UTF-8\\ n"\n '
39+ '"POT-Creation-Date: 2026-09-29 18:58+0000\\ n"\n '
40+ '"PO-Revision-Date: 2025-09-16 00:01+0000\\ n"\n '
41+ '"Last-Translator: Translator\\ n"\n '
42+ '\n '
43+ '#: example.rst:10\n '
44+ 'msgid "example"\n '
45+ 'msgstr "翻訳"\n '
46+ ).encode ('utf-8' )
47+
48+
3449class TranslationTests (unittest .TestCase ):
3550 def setUp (self ):
3651 temporary = tempfile .TemporaryDirectory ()
@@ -49,6 +64,89 @@ def track(self):
4964 ["git" , "-C" , str (self .destination ), "add" , "." ], check = True
5065 )
5166
67+ def restore_catalog (self , before , incoming ):
68+ target = self .destination / "example.po"
69+ target .write_bytes (before )
70+ self .track ()
71+ target .write_bytes (incoming )
72+ translations .restore_timestamp_only_changes (self .destination )
73+ return target
74+
75+ def test_restore_timestamp_only_changes (self ):
76+ for newline in (b'\n ' , b'\r \n ' ):
77+ before = DATED_PO .replace (b'\n ' , newline )
78+ changes = [
79+ before ,
80+ before .replace (b'2026-09-29' , b'2026-10-19' ),
81+ before .replace (b'2025-09-16' , b'2026-10-20' ),
82+ before .replace (b'2026-09-29' , b'2026-10-19' ).replace (
83+ b'2025-09-16' , b'2026-10-20'
84+ ),
85+ ]
86+ for incoming in changes :
87+ with self .subTest (newline = newline , incoming = incoming ):
88+ target = self .restore_catalog (before , incoming )
89+ self .assertEqual (target .read_bytes (), before )
90+
91+ def test_restore_keeps_other_changes_and_their_new_timestamps (self ):
92+ updated = DATED_PO .replace (b'2026-09-29' , b'2026-10-19' )
93+ replacements = [
94+ ('msgstr "翻訳"' , 'msgstr "新しい訳"' ),
95+ ('msgid "example"' , 'msgid "changed"' ),
96+ ('#: example.rst:10' , '#: example.rst:20' ),
97+ ('# Catalog' , '# Updated comment' ),
98+ ('msgid "example"' , '#, fuzzy\n msgid "example"' ),
99+ ('Last-Translator: Translator' , 'Last-Translator: New Translator' ),
100+ ('msgstr "翻訳"' , 'msgstr ""\n "翻訳"' ),
101+ ]
102+ for old , new in replacements :
103+ with self .subTest (change = new ):
104+ incoming = updated .replace (old .encode (), new .encode ())
105+ target = self .restore_catalog (DATED_PO , incoming )
106+ self .assertEqual (target .read_bytes (), incoming )
107+
108+ def test_restore_does_not_ignore_timestamp_text_in_translation (self ):
109+ before = DATED_PO .replace (
110+ 'msgstr "翻訳"\n ' .encode (),
111+ b'msgstr ""\n "POT-Creation-Date: 2026-09-29 18:58+0000\\ n"\n ' ,
112+ )
113+ incoming = before .replace (b'2026-09-29' , b'2026-10-19' )
114+ target = self .restore_catalog (before , incoming )
115+ self .assertEqual (target .read_bytes (), incoming )
116+
117+ def test_restore_preserves_new_files_and_deletions (self ):
118+ deleted = self .destination / "deleted.po"
119+ deleted .write_bytes (DATED_PO )
120+ self .track ()
121+ deleted .unlink ()
122+ new = self .destination / "new.po"
123+ new .write_bytes (DATED_PO )
124+ translations .restore_timestamp_only_changes (self .destination )
125+ self .assertFalse (deleted .exists ())
126+ self .assertEqual (new .read_bytes (), DATED_PO )
127+
128+ def test_restore_command_handles_multiple_files_and_explicit_root (self ):
129+ catalogs = ["unchanged.po" , "translated.po" , "library/with space.po" ]
130+ for path in catalogs :
131+ target = self .destination / path
132+ target .parent .mkdir (parents = True , exist_ok = True )
133+ target .write_bytes (DATED_PO )
134+ self .track ()
135+ updated = DATED_PO .replace (b'2026-09-29' , b'2026-10-19' )
136+ translated = updated .replace ('翻訳' .encode (), '新しい訳' .encode ())
137+ (self .destination / "unchanged.po" ).write_bytes (updated )
138+ (self .destination / "translated.po" ).write_bytes (translated )
139+ (self .destination / "library/with space.po" ).write_bytes (updated )
140+ subprocess .run (
141+ [sys .executable , translations .__file__ , "restore-timestamp-only" ,
142+ str (self .destination )],
143+ cwd = self .source , check = True , stdout = subprocess .PIPE ,
144+ )
145+
146+ self .assertEqual ((self .destination / "unchanged.po" ).read_bytes (), DATED_PO )
147+ self .assertEqual ((self .destination / "translated.po" ).read_bytes (), translated )
148+ self .assertEqual ((self .destination / "library/with space.po" ).read_bytes (), DATED_PO )
149+
52150 def test_sync_removes_old_path_and_preserves_untracked_files (self ):
53151 catalogs = ["builtins/functions.po" , "library/builtins.po" , "bugs.po" ]
54152 write_config (self .source , catalogs )
0 commit comments