Files
UmbraAudio/tests/test_language_scripts.py
Umbra Tools 3db6ac3b8b Refuse a delivery whose text is not written in the language it claims
The production directory oldbox calls "deal-hunter-es" declares
target_language "es-ES", its glossary says Spanish, and its 32-chapter run
finished - and all 3,038 translated paragraphs are in Chinese. It is a second
Chinese draft filed under a Spanish name.

The cause is in the pipeline: the drafter's instructions are hard-coded to
Simplified Chinese in three places (SYSTEM_DRAFTER, draft_prompt,
SYSTEM_CRITIC), so the glossary's target language was ignored. No non-Chinese
translation has ever been produced by that pipeline.

The danger at this end is that the rows would be stored under "es", the review
lane would look populated, and a reviewer would approve Chinese as Spanish.
So the ingest now checks the script of the delivered text against the
declared language, and refuses the delivery before writing a single row.

The check is about the script, not the language: it cannot tell Spanish from
Portuguese, but it can tell Chinese from Spanish, which is the mistake that
actually happens. Japanese needs its kana and Korean its Hangul, and a
Chinese delivery carrying kana is a Japanese translation filed in the wrong
lane. An unknown language is not checked - there is no expectation to check
it against.

Proven on the real data: the es delivery is refused with "the text is mostly
Han characters, so it is not Spanish", and the 4,300-row Chinese delivery
still imports unchanged.

19 new tests. Suite: 909 passed, 8 failed (the same 8 pre-existing failures).
2026-11-06 20:06:59 -08:00

206 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Copyright 2026 Umbra Tools (umbratools.com)
# SPDX-License-Identifier: Apache-2.0
"""Tests for the script check on a translation delivery (spec 31 section 8).
A delivery that claims one language and arrives written in another is not a
translation of that language. It is the "declared value disagrees with the
measured value" defect class, and it is checked at the boundary: once the rows
are stored, the review lane looks populated and the mistake is invisible.
The check is about the *script*, not the language. It cannot tell Spanish
from Portuguese. It can tell Chinese from Spanish, which is the mistake that
actually happens: a pipeline configured for one language that silently
produces another.
"""
from __future__ import annotations
import sqlite3
import pytest
from umbraaudio import languages as lang
from umbraaudio import translations as tr
CHINESE = "欢迎收听《Deal Hunter》有声书。我是旁白,将为您介绍几位您将听到的声音。"
SPANISH = "Bienvenido al audiolibro Deal Hunter. Soy tu narrador."
RUSSIAN = "Добро пожаловать в аудиокнигу Deal Hunter. Я ваш рассказчик."
JAPANESE = "『Deal Hunter』的有声書へようこそ。私はナレーターです。"
KOREAN = "Deal Hunter 오디오북에 오신 것을 환영합니다. 저는 내레이터입니다."
# --- the registry's own answers --------------------------------------------
def test_chinese_is_not_spanish() -> None:
reason = lang.script_mismatch("es", CHINESE)
assert reason is not None
assert "Han" in reason
assert "Spanish" in reason
def test_chinese_is_chinese() -> None:
assert lang.script_mismatch("zh", CHINESE) is None
def test_spanish_is_spanish() -> None:
assert lang.script_mismatch("es", SPANISH) is None
def test_russian_is_russian() -> None:
assert lang.script_mismatch("ru", RUSSIAN) is None
def test_latin_text_is_not_russian() -> None:
reason = lang.script_mismatch("ru", SPANISH)
assert reason is not None
assert "Latin" in reason
assert "Russian" in reason
def test_japanese_needs_kana() -> None:
assert lang.script_mismatch("ja", JAPANESE) is None
def test_chinese_text_is_not_japanese() -> None:
"""Kanji are Han, so Chinese reads as Japanese without the kana."""
reason = lang.script_mismatch("ja", CHINESE)
assert reason is not None
assert "kana" in reason
def test_japanese_text_is_not_chinese() -> None:
"""Kana in a Chinese delivery means the target is the wrong language."""
reason = lang.script_mismatch("zh", JAPANESE)
assert reason is not None
assert "Japanese" in reason or "kana" in reason
def test_korean_is_hangul() -> None:
assert lang.script_mismatch("ko", KOREAN) is None
def test_chinese_text_is_not_korean() -> None:
reason = lang.script_mismatch("ko", CHINESE)
assert reason is not None
def test_names_kept_in_english_do_not_break_the_check() -> None:
"""A Chinese paragraph keeps Deal Hunter and Umbra Audio in English."""
text = CHINESE + " Deal Hunter 是船名,Umbra Audio 是出品方。"
assert lang.script_mismatch("zh", text) is None
def test_an_unknown_language_is_not_checked() -> None:
"""There is no script expectation for a language we do not know."""
assert lang.script_mismatch("xx", CHINESE) is None
def test_empty_text_is_not_checked() -> None:
assert lang.script_mismatch("es", "") is None
assert lang.script_mismatch("es", "1234 -- ...") is None
# --- the gate at the ingest boundary ----------------------------------------
def _seed(conn: sqlite3.Connection) -> None:
conn.executescript(
"""
CREATE TABLE project (id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE chapters (id INTEGER PRIMARY KEY, chapter_index INTEGER);
CREATE TABLE utterances (
id INTEGER PRIMARY KEY,
chapter_id INTEGER,
utterance_index INTEGER,
speaker_name TEXT,
text TEXT,
context_before TEXT,
context_after TEXT,
language_code TEXT,
language_origin TEXT
);
INSERT INTO project (id, name) VALUES (1, 'Test');
INSERT INTO utterances (id, chapter_id, utterance_index, speaker_name, text) VALUES
(1, 1, 0, 'narrator', 'The ship landed.'),
(2, 1, 1, 'Kainda', 'Welcome to the ship.');
"""
)
conn.commit()
@pytest.fixture
def conn() -> sqlite3.Connection:
c = sqlite3.connect(":memory:")
c.row_factory = sqlite3.Row
_seed(c)
tr.ensure_tables(c)
yield c
c.close()
def test_a_mismatched_delivery_is_refused_and_writes_nothing(conn: sqlite3.Connection) -> None:
"""The Chinese text filed under Spanish must not reach the database."""
report = tr.import_translations(
conn,
1,
{
"target_language": "es",
"utterances": [
{"utterance_id": 1, "text": CHINESE},
{"utterance_id": 2, "text": "我是旁白。"},
],
},
)
assert report.accepted == 0
assert report.problems
assert "not Spanish" in report.problems[0]
assert conn.execute("SELECT COUNT(*) FROM utterance_translations").fetchone()[0] == 0
def test_a_matching_delivery_is_imported(conn: sqlite3.Connection) -> None:
report = tr.import_translations(
conn,
1,
{
"target_language": "es",
"utterances": [
{"utterance_id": 1, "text": SPANISH},
{"utterance_id": 2, "text": "Bienvenido a la nave."},
],
},
)
assert report.accepted == 2
assert report.problems == []
assert conn.execute("SELECT COUNT(*) FROM utterance_translations").fetchone()[0] == 2
def test_a_locale_code_is_checked_against_its_base_language(conn: sqlite3.Connection) -> None:
"""``es-ES`` is still Spanish; ``zh-Hans`` is still Chinese."""
report = tr.import_translations(
conn,
1,
{"target_language": "es-ES", "utterances": [{"utterance_id": 1, "text": CHINESE}]},
)
assert report.accepted == 0
assert report.problems
def test_a_collapse_orphan_is_not_mistaken_for_a_mismatch(conn: sqlite3.Connection) -> None:
"""A row with no text is a gap, not evidence of the wrong language."""
report = tr.import_translations(
conn,
1,
{
"target_language": "es",
"utterances": [
{"utterance_id": 1, "text": SPANISH},
{"utterance_id": 2, "voiced": False, "reason": "absorbed into 1"},
],
},
)
assert report.accepted == 2
assert report.not_voiced == 1
assert report.problems == []