mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-08 13:04:13 +02:00
Finish unfinished French entries that equal their source
After a lupdate refresh, the same-text heuristic fills a new string with the translation of an existing identical one and marks it unfinished. In the source-language file that guess is the source text itself, which is exactly what a mirror run writes, but the script left it alone as "work in progress": 39 such entries stayed unfinished in qet_fr.ts after refreshing it on 2026-10-07. The script now finishes an unfinished translation (or plural with all forms) that is identical to its source. Unfinished translations with other text stay untouched. Two fixture messages and a test cover it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,19 @@
|
||||
<source>Brouillon</source>
|
||||
<translation type="unfinished">Brouillon (à revoir)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/alpha.cpp" line="15"/>
|
||||
<source>Copier</source>
|
||||
<translation>Copier</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../sources/alpha.cpp" line="16"/>
|
||||
<source>%n page(s)</source>
|
||||
<translation>
|
||||
<numerusform>%n page(s)</numerusform>
|
||||
<numerusform>%n page(s)</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../sources/alpha.cpp" line="20"/>
|
||||
<source>%n élément(s)</source>
|
||||
|
||||
@@ -28,6 +28,19 @@
|
||||
<source>Brouillon</source>
|
||||
<translation type="unfinished">Brouillon (à revoir)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../sources/alpha.cpp" line="15"/>
|
||||
<source>Copier</source>
|
||||
<translation type="unfinished">Copier</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../sources/alpha.cpp" line="16"/>
|
||||
<source>%n page(s)</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform>%n page(s)</numerusform>
|
||||
<numerusform>%n page(s)</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../sources/alpha.cpp" line="20"/>
|
||||
<source>%n élément(s)</source>
|
||||
|
||||
@@ -42,7 +42,11 @@ into the translation and the entry is finished. Nothing else changes:
|
||||
|
||||
- a translation with text is never touched, whether it is a French
|
||||
rendering of an English source, identical to the source, or marked
|
||||
unfinished (a translator's work in progress);
|
||||
unfinished (a translator's work in progress), with one exception:
|
||||
an unfinished translation identical to its source is finished.
|
||||
That is what lupdate's same-text heuristic leaves behind when a new
|
||||
string repeats an existing one, and it is what a mirror run would
|
||||
have written anyway;
|
||||
- vanished and obsolete entries are left to lupdate;
|
||||
- in a plural message only the empty <numerusform>s are filled;
|
||||
- the file stays byte-identical outside the rewritten <translation>
|
||||
@@ -87,6 +91,7 @@ class SourceError(ValueError):
|
||||
class Stats:
|
||||
mirrored_messages: int = 0
|
||||
mirrored_forms: int = 0
|
||||
finished_identical: int = 0
|
||||
unfinished_cleared: int = 0
|
||||
kept_translated: int = 0
|
||||
kept_identical: int = 0
|
||||
@@ -94,12 +99,13 @@ class Stats:
|
||||
kept_unfinished_with_text: int = 0
|
||||
|
||||
def changed(self) -> bool:
|
||||
return self.mirrored_messages > 0
|
||||
return self.mirrored_messages > 0 or self.finished_identical > 0
|
||||
|
||||
def summary(self) -> str:
|
||||
return (
|
||||
f"mirrored {self.mirrored_messages} message(s), "
|
||||
f"{self.mirrored_forms} plural form(s), "
|
||||
f"finished {self.finished_identical} identical unfinished, "
|
||||
f"cleared {self.unfinished_cleared} unfinished flag(s); kept "
|
||||
f"{self.kept_translated} translated, {self.kept_identical} identical, "
|
||||
f"{self.kept_vanished} vanished, "
|
||||
@@ -111,6 +117,18 @@ def _translation_type(attrs: str) -> str:
|
||||
return m.group("type") if m else ""
|
||||
|
||||
|
||||
def _finish(whole: str, body: str, tm: re.Match, tattrs: str,
|
||||
stats: Stats) -> str:
|
||||
"""Drop the unfinished flag of a translation equal to its source."""
|
||||
stats.finished_identical += 1
|
||||
stats.unfinished_cleared += 1
|
||||
start = whole.index(body) + tm.start()
|
||||
old = tm.group(0)
|
||||
new = old.replace(f"<translation{tattrs}>",
|
||||
f"<translation{TYPE_RE.sub('', tattrs)}>", 1)
|
||||
return whole[:start] + new + whole[start + len(old):]
|
||||
|
||||
|
||||
def _mirror_message(match: re.Match, forms: int, stats: Stats) -> str:
|
||||
whole = match.group(0)
|
||||
attrs, body = match.group("attrs"), match.group("body")
|
||||
@@ -134,6 +152,8 @@ def _mirror_message(match: re.Match, forms: int, stats: Stats) -> str:
|
||||
found = FORM_RE.findall(tbody)
|
||||
empty = [f for f in found if f == ""]
|
||||
if found and not empty:
|
||||
if ttype == "unfinished" and all(f == source for f in found):
|
||||
return _finish(whole, body, tm, tattrs, stats)
|
||||
if ttype == "unfinished":
|
||||
stats.kept_unfinished_with_text += 1
|
||||
elif all(f == source for f in found):
|
||||
@@ -153,6 +173,8 @@ def _mirror_message(match: re.Match, forms: int, stats: Stats) -> str:
|
||||
stats.mirrored_forms += forms
|
||||
else:
|
||||
if tbody != "":
|
||||
if ttype == "unfinished" and tbody == source:
|
||||
return _finish(whole, body, tm, tattrs, stats)
|
||||
if ttype == "unfinished":
|
||||
stats.kept_unfinished_with_text += 1
|
||||
elif tbody == source:
|
||||
|
||||
@@ -66,6 +66,7 @@ class MirrorText(unittest.TestCase):
|
||||
self.assertEqual(out, after)
|
||||
self.assertEqual(stats.mirrored_messages, 0)
|
||||
self.assertEqual(stats.mirrored_forms, 0)
|
||||
self.assertEqual(stats.finished_identical, 0)
|
||||
self.assertEqual(stats.unfinished_cleared, 0)
|
||||
|
||||
def test_before_becomes_after(self):
|
||||
@@ -73,7 +74,8 @@ class MirrorText(unittest.TestCase):
|
||||
self.assertEqual(out, read(AFTER))
|
||||
self.assertEqual(stats.mirrored_messages, 8)
|
||||
self.assertEqual(stats.mirrored_forms, 3)
|
||||
self.assertEqual(stats.unfinished_cleared, 2)
|
||||
self.assertEqual(stats.finished_identical, 2)
|
||||
self.assertEqual(stats.unfinished_cleared, 4)
|
||||
self.assertEqual(stats.kept_translated, 1)
|
||||
self.assertEqual(stats.kept_identical, 1)
|
||||
self.assertEqual(stats.kept_vanished, 2)
|
||||
@@ -113,6 +115,16 @@ class MirrorText(unittest.TestCase):
|
||||
if key[0] == "Beta":
|
||||
self.assertEqual(tr.text, msg.findtext("source"), key)
|
||||
|
||||
def test_unfinished_identical_to_source_is_finished(self):
|
||||
"""lupdate's same-text heuristic leaves these behind after a refresh;
|
||||
39 of them appeared in lang/qet_fr.ts on 2026-10-07."""
|
||||
out, _ = m.mirror_text(read(BEFORE))
|
||||
msgs = messages(out)
|
||||
for key in [("Alpha", "Copier"), ("Alpha", "%n page(s)")]:
|
||||
self.assertIsNone(msgs[key].find("translation").get("type"), key)
|
||||
self.assertEqual(msgs[("Alpha", "Brouillon")].find("translation").get("type"),
|
||||
"unfinished", "a different unfinished text stays unfinished")
|
||||
|
||||
def test_multiline_and_spaces_kept(self):
|
||||
out, _ = m.mirror_text(read(BEFORE))
|
||||
self.assertIn("<translation> Ligne 1\nLigne 2\n</translation>", out)
|
||||
|
||||
Reference in New Issue
Block a user