Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "python-docs-vi-translator"
version = "0.1.7"
version = "0.1.8"
description = "Translates the CPython documentation into Vietnamese gettext catalogs, with a deterministic audit over every string it writes"
readme = "README.md"
license = "MIT"
Expand Down
40 changes: 28 additions & 12 deletions src/pydocvi/apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ def apply_entry(
pipeline failing until the memory caught up with the catalog. What matters is
that no machine string ever lands on top of a human one, and none does.

A code entry is copied whatever the memory holds for it, which is a rule
about the memory being wrong rather than empty. ``P07`` says a doctest is
A code entry is copied before any of that is asked, whatever the memory
holds for it and whatever the file holds for it. ``P07`` says a doctest is
byte-identical to its ``msgid``, and until this clause existed the only thing
standing behind that was nothing having queued one: :func:`batch` filters
non-prose out, so a segment for a code entry could only come from a run made
Expand All @@ -281,14 +281,30 @@ def apply_entry(

Refusing it here rather than pruning the memory is what makes the guarantee
hold going forward. The classifier will get stricter again, that is the
direction it has moved twice, and each time it does the memory acquires
another handful of translations of things that turned out to be code. What
they are worth is decided by what the string is now, not by what some earlier
run thought when it asked.

The human guard still comes first, so this cannot quietly replace a reviewed
string with the English. Getting past that is ``--refuzzy`` and nothing else,
which is the same answer as everywhere else in this module.
direction it has moved three times now, and each time it does both the memory
and the corpus acquire another handful of translations of things that turned
out to be code. What they are worth is decided by what the string is now, not
by what some earlier run thought when it asked.

Which is why the copy is asked before the human guard and not after it. It
was after it first, on the reasoning that a reviewer's mark outranks
everything, and one classifier change was enough to show what that costs. The
block rule learned to read ``def f(pos1, pos2, /, pos_or_kwd, *, kwd1,
kwd2):``, four segments left the memory because a person's copy of a line of
code is not a translation of it, and the five entries in the corpus stayed
exactly where they were: translated, not fuzzy, and now accounted for by
nothing. ``S04`` reported all five, correctly, because that is what an
unmarked non-fuzzy string is. The guard was holding a string in place that no
longer had anything behind it.

The guard exists to stop a machine translation landing on a person's work.
Copying the ``msgid`` is not a machine translation, it is the source text,
and ``P07`` requires it. So the guard is not engaged here and putting it
first only meant that a writer could leave its own checker with findings it
had no way to clear. Measured before the swap: 5 entries in the corpus are
human-marked and classified as code, and all 5 already hold the ``msgid``
byte for byte. The reordering changes no translated text at all, and adds
five provenance comments saying where the text came from.

An entry the memory has nothing for is either prose nobody has translated
yet, which is left as upstream had it, or a no-op, which is copied through
Expand All @@ -304,10 +320,10 @@ def apply_entry(
``refuzzy`` is the one way to get past that guard, and it is off by default.
See :func:`_contradicted` for what it does and why it exists.
"""
if existing is not None and is_human(existing) and not _contradicted(segment, refuzzy):
return existing
if classify.classify(upstream.msgid).code:
return _copied(upstream)
if existing is not None and is_human(existing) and not _contradicted(segment, refuzzy):
return existing
if segment is None or not segment.msgstr:
return _copied(upstream) if not _translatable(upstream) else _untranslated(upstream)
if segment.source == "human":
Expand Down
32 changes: 21 additions & 11 deletions tests/test_apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,22 +188,32 @@ def test_a_segment_for_a_code_entry_is_refused(self) -> None:
assert entry.msgstr == code
assert entry.comments[-1] == "# pydocvi: passthrough=literal_block"

def test_refusing_it_does_not_reach_past_the_reviewer(self) -> None:
"""The human guard comes first, so this cannot quietly replace a reviewed
string with the English. ``--refuzzy`` is the only way past it, here as
everywhere else in this module."""
def test_the_copy_reaches_past_the_reviewer_too(self) -> None:
"""This assertion was the other way round first, on the reasoning that a
reviewer's mark outranks everything. One classifier change was enough to
show what that costs: four segments left the memory, five entries stayed
in the corpus translated and not fuzzy with nothing behind them, and
``S04`` reported all five and had no way to be cleared.

The guard exists to stop a machine translation landing on a person's
work. The ``msgid`` is not a machine translation, it is the source text,
and ``P07`` requires the entry to hold it byte for byte."""
code = ">>> n # try it\nNameError"
source = upstream(block(code.replace("\n", "\\n")))
existing = upstream(block(code.replace("\n", "\\n"), "đã duyệt"))
out = apply.apply_catalog(source, existing, Memory(), stamp=STAMP)[0]
assert entry_of(out, code).msgstr == "đã duyệt"
entry = entry_of(out, code)
assert entry.msgstr == code
assert entry.comments[-1] == "# pydocvi: passthrough=doctest"

def test_refuzzy_reaches_it(self) -> None:
code = ">>> n # try it\nNameError"
source = upstream(block(code.replace("\n", "\\n")))
existing = upstream(block(code.replace("\n", "\\n"), "đã duyệt"))
out = apply.apply_catalog(source, existing, Memory(), stamp=STAMP, refuzzy=True)[0]
assert entry_of(out, code).msgstr == code
def test_a_reviewed_prose_string_is_still_untouchable(self) -> None:
"""What the reordering did not change, and the reason to write it down
next to what it did. The guard is where it always was for everything that
is not code, which is 81 964 of the 87 008 entries."""
source = upstream(block("Return a sorted list."))
existing = upstream(block("Return a sorted list.", "Trả về một danh sách."))
out = apply.apply_catalog(source, existing, Memory(), stamp=STAMP)[0]
assert entry_of(out, "Return a sorted list.").msgstr == "Trả về một danh sách."

def test_a_copy_is_not_counted_as_work_done(self) -> None:
"""Spec 12 §5: a no-op is neither translated nor outstanding. Folding
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading