fix: the ASCII mode's own strings are ASCII

`Language::label_ascii()` returned `Русский` for Russian — byte for byte the
same text as `label()` — and Russian's `unavailable_ascii` was `недоступен`.
Both are drawn under `if ascii_mode()` (`screens/main.rs:475` and `:501`), and
`ascii_mode` is on either because `--ascii` was passed or because the console
has no VT support at all (`legacy_console = !has_vt || args.ascii`,
`main.rs:303`) — so the one console class the mode exists for, Windows 7/8 and
any terminal without ANSI, was the one that got the Cyrillic. English, Chinese
and Farsi were already Latin.

`label_ascii()` says `Russian` now, `unavailable_ascii` says `unavailable`, and
a test walks `Language::ALL` asserting both are `is_ascii()`: the invariant was
never stated anywhere, which is how one of the four languages drifted.
This commit is contained in:
Kirill Minovsky 2026-10-10 02:02:57 +03:00
parent 24663f6a07
commit 69d3c8d085
2 changed files with 30 additions and 2 deletions

View file

@ -42,7 +42,10 @@ impl Language {
pub(crate) fn label_ascii(&self) -> &'static str {
match self {
Self::En => "English",
Self::Ru => "Русский",
// Latin, like the other three: this is the string the `--ascii`
// path shows on a console that cannot render anything else, and
// `Русский` is exactly what that console cannot render.
Self::Ru => "Russian",
Self::Zh => "Chinese",
Self::Fa => "Farsi",
}
@ -243,6 +246,31 @@ mod tests {
assert_eq!(Language::from_code("unknown"), None);
}
/// Every string the ASCII path prints must itself be ASCII.
///
/// The failure this pins: `label_ascii()` returned `Русский` for Russian —
/// the same text as `label()` — and Russian's `unavailable_ascii` was
/// `недоступен`. Both are drawn under `if ascii_mode()`, which is on either
/// because `--ascii` was passed or because the console has no VT support at
/// all (`!has_vt`), so the one console class the mode exists for was the one
/// that got the Cyrillic.
#[test]
fn the_ascii_mode_strings_are_ascii() {
for lang in Language::ALL {
assert!(
lang.label_ascii().is_ascii(),
"{lang:?} label_ascii is {:?}",
lang.label_ascii()
);
let msg = get_messages(lang);
assert!(
msg.unavailable_ascii.is_ascii(),
"{lang:?} unavailable_ascii is {:?}",
msg.unavailable_ascii
);
}
}
/// The live progress line must name what it is counting. Test 1 is labelled
/// with the block tokens UDP/DoH/DoT/EGRESS, so it needs no wording of its
/// own; the TCP window test names itself instead of a generic "Checking...".

View file

@ -199,7 +199,7 @@ pub(crate) fn messages() -> Messages {
invalid_concurrency_flag: "Параметр --concurrency должен быть целым числом >= 1.",
tui_unavailable: "\r\nИнтерактивное меню (TUI) недоступно в этом терминале [{}].\r\nЗапустите диагностику с параметрами:\r\n\x1b[36m dpi-detector -t 1\x1b[0m — проверка DNS-серверов\r\n\x1b[36m dpi-detector -t 1,2,3\x1b[0m — базовые тесты (DNS + сайты + TCP16)\r\n\x1b[36m dpi-detector -t 12345\x1b[0m — все тесты\r\n\x1b[36m dpi-detector --help\x1b[0m — список всех параметров\r\n\r\n",
unavailable_ascii: "недоступен",
unavailable_ascii: "unavailable",
proxy_in_use: "Используется прокси",
tui_reason_stdin: "stdin не является терминалом (pipe или перенаправление)",
tui_reason_raw_mode: "терминал не поддерживает raw mode",