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.8"
version = "0.1.9"
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
23 changes: 23 additions & 0 deletions src/pydocvi/audit/language.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,33 @@ def l02_not_the_source(corpus: Corpus) -> Iterator[Finding]:
what they are for. Everything else that is identical is either a model
refusing the work or a classifier that should have called it passthrough,
and both are worth a line in the report.

An entry whose whole ``msgid`` is a term the glossary keeps in English is
exempt too, and that exemption is the reason the glossary has a ``keep_en``
field at all. Narrowing the identifier rule put 6 558 single-word entries
into :meth:`Corpus.prose` for the first time and took this check from 10
findings to 144. A third of them were entries reading ``sys``, ``builtins``,
``import``, ``exec``, ``NaN`` and ``Infinity``, all of which are index
entries naming a module or a statement, and all of which a reviewer left in
English because that is what a Vietnamese programmer calls them.

Nothing in the string can tell those from ``module``, ``object`` and
``type``, which are the other 89 and are ordinary English words used as
index categories. ``sys`` and ``Notes`` are the same shape, and that is the
discrimination the classifier was narrowed for being unable to make. So it
is made once, by hand, in the glossary, where it is a written decision that
``G03`` then checks in both directions rather than an exception buried here.

Matched on the whole ``msgid`` and not on a substring. A kept term inside a
sentence says nothing about whether the sentence was translated, and this
check is about the entry.
"""
kept = {term.en for term in corpus.glossary.kept} if corpus.glossary is not None else set()
for one, entry in corpus.translated():
if classify.classify(entry.msgid) in PASSTHROUGH:
continue
if entry.msgid.strip() in kept:
continue
if entry.msgstr.strip() == entry.msgid.strip():
yield Finding(
check="L02",
Expand Down
35 changes: 35 additions & 0 deletions tests/test_audit_language.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@
which correct translations those are.
"""

from dataclasses import replace

from conftest import catalog_of, corpus_of, entry, findings, machine_segment
from pydocvi.audit import language
from pydocvi.audit.model import Corpus
from pydocvi.glossary import Glossary, Term
from pydocvi.memory import Memory


def over(msgid: str, msgstr: str, **overrides: object) -> object:
return corpus_of(catalog_of(entry(msgid, msgstr, **overrides)))


def with_kept(corpus: object, *english: str) -> Corpus:
"""The same corpus with a glossary that keeps these terms in English."""
terms = tuple(Term(en=one, vi=one, keep_en=True) for one in english)
return replace(corpus, glossary=Glossary(version=1, terms=terms)) # type: ignore[type-var]


class TestL01:
LONG = "A sentence long enough that a reader would expect a diacritic in it."

Expand Down Expand Up @@ -51,6 +61,31 @@ def test_a_real_translation_is_clean(self) -> None:
== []
)

def test_an_entry_that_is_a_kept_term_is_the_glossary_s_business(self) -> None:
"""``sys`` is an index entry naming a module and a reviewer left it in
English because that is what a Vietnamese programmer calls it. Nothing
in the string separates it from ``Notes``, so the decision is made once
in the glossary and ``G03`` checks it in both directions."""
corpus = over("sys", "sys", flags=())
assert len(findings(language.l02_not_the_source, corpus)) == 1
assert findings(language.l02_not_the_source, with_kept(corpus, "sys")) == []

def test_a_kept_term_inside_a_sentence_exempts_nothing(self) -> None:
"""Matched on the whole ``msgid``. A kept term in a sentence says
nothing about whether the sentence was translated."""
english = "Import the sys module first."
corpus = with_kept(over(english, english), "sys")
assert len(findings(language.l02_not_the_source, corpus)) == 1

def test_a_word_the_glossary_does_not_keep_is_still_reported(self) -> None:
"""``module`` is the other side of the same 144 findings, an ordinary
English word used as an index category, and it wants translating."""
corpus = with_kept(over("module", "module", flags=()), "sys")
assert len(findings(language.l02_not_the_source, corpus)) == 1

def test_no_glossary_means_no_exemption_rather_than_a_crash(self) -> None:
assert len(findings(language.l02_not_the_source, over("sys", "sys", flags=()))) == 1


class TestL03:
def test_a_model_talking_about_the_work_is_found(self) -> None:
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