16 KiB
SPEC-REVIEW-371-r2
Issue: #371 — Add French localization files
Трек: small (лёгкий). Заход r2, блокирующих циклов израсходовано 1 из 2 (израсходован
на жёлтый вердикт r1; зелёный вердикт цикла не тратит — см. §4 PROCESS.md и правило #227).
Ревьюер: Claude (роль «ревьюер ТЗ»), автор ревизии: Codex (комментарий-ревизия от
2026-08-29T10:08:21Z).
Материал: тело issue #371 на момент разбора (текущая версия, с примечанием «Ревизия 2
(по SPEC-REVIEW-371-r1)» в конце тела) — без файла в docs/specs/, соответствует метке small.
Скоуп раунда
Это ревью округа r2 — по правилам §2.9 объём разбора идёт по дельте, а не заново.
Дельта r1→r2 — это правка тела issue, объявленная автором в комментарии-ревизии и в примечании внизу тела:
- К2 дополнен абзацем «Плюс (r1-M1)»: французская ветка в
entryFallbackPlugin(#353) и расширение smokeentry_staleтретьим локальным прогоном. - AC1 дополнен абзацем «Плюс (r1-M2)»: французские аналоги двух German-персональных
тестов из
test/i18n.test.mjs— глоссарий продуктовых терминов и скан на артефакты не-перевода (кириллица + allow-list явных совпадений с английским).
Больше нигде текст issue не менялся: К1, К3, К4, AC2–AC5, откат, трейлер User-Visible
и продуктовая классификация трека — та же строка символов, что была на r1. Дельта
локальна (правка двух абзацев ради двух точечных технических пробелов), новой
подсистемы не задевает, контракт поведения не меняет объёма — полный разбор с нуля не
требуется по критерию §2.9.
Как проверялось
- Найден вердикт r1 (жёлтый, 0 High / 2 Medium в скоупе — M1, M2) и текст документа
docs/reviews/SPEC-REVIEW-371-r1.md, зафиксировавший SHA сверки кода —914bb4ed(dev на момент r1). - Получено текущее тело issue #371 (
gh issue view 371 --json body,comments) и сверено построчно с текстом, процитированным вSPEC-REVIEW-371-r1.md, чтобы выделить именно дельту, а не пересказ автора о ней. - Для M1 — прочитан
scripts/bundle-manifest.mjs:192-224(entryFallbackPlugin) на текущемdev(dd591a6c, код в этом файле не менялся со времени r1 — issue #371 ещё не имеет кода в репозитории). Подтверждено: ternary строк 208-212 действительно обрывается наru/deи молча уходит в английский текст для всех остальных языков — ровно та дыра, которую r1 назвал. Новый абзац К2 закрывает её точным описанием ветки (l.startsWith('fr')перед финальнымelse) с готовым французским текстом в том же стиле, что немецкий и русский варианты (тот же формат: «что случилось — что делать (Ctrl+F5)»). - Для M1 (smoke) — прочитан
demo/smoke_entry_stale.mjsцеликом. Подтверждено: текущий смок гоняет толькоen-US/ru-RU(де-локаль в этом конкретном смоке не проверяется вовсе, несмотря на то что у неё есть своя ветка в коде) — формулировка ревизии «третья локаль в существующем цикле en/ru» точна, а не приблизительна. - Для M2 — прочитан
test/i18n.test.mjs:327-370, оба German-персональных теста (German catalog keeps the product glossary...,German values equal to English are explicitly reviewed). Подтверждено: это точечные assert’ы конкретных строк плюс регэксп-скан на кириллицу/сентинелы и allow-list точных совпадений сen.json, ни один не читаетLANGUAGE_REGISTRY. Новый абзац AC1 обещает French-аналоги той же конструкции (глоссарий ключевых терминов + запрет кириллицы + allow-list совпадений, список которого «собирается по факту словаря» — так же, как собирался немецкий allow-list по факту немецкого словаря, а не заранее). - Проверено, что остальные разделы (К1, К3, К4, AC2-AC5, откат,
User-Visible) не изменились с r1 — значит, находки r1 вне M1/M2 (L1, L2) и весь блок «что проверено и корректно» из r1 переносятся без повторной проверки (см. «Унаследовано из r1» ниже). - Гейты (
tsc/test/build/bundle:budget/check-docs) не запускались — этапspec, кода French в репозитории по-прежнему нет (ls src/i18n/не изменился с r1: толькоde.json/de.ts/en.json/ru.json/registry.ts/language-runtime.ts), гнать нечего — то же основание, что было в r1.
Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
M1 — entryFallbackPlugin хардкодит ru/de в тексте плашки устаревшего entry (#353), French останется без ветки и молча получит английский текст; ни один AC это не покрывает |
К2 дополнен точной веткой l.startsWith('fr') с готовым французским текстом в формате существующих веток + smoke entry_stale явно расширяется fr-прогоном как третьей локалью |
Тело issue, абзац К2, предложение «Плюс (r1-M1): entryFallbackPlugin (#353) несёт локализованную плашку… добавляется французская ветка… и smoke entry_stale расширяется fr-прогоном (третья локаль в существующем цикле en/ru)» |
| M2 — AC1 заявляет «существующий тестовый механизм расширяется на fr» как единый факт, но два German-персональных теста (глоссарий + скан на артефакты) не зациклены по реестру и не расширяются автоматически | AC1 дополнен явным обязательством завести French-аналоги обоих тестов: глоссарий продуктовых терминов + скан на кириллицу/артефакты не-перевода с allow-list | Тело issue, абзац AC1, предложение «Плюс (r1-M2) — аналоги двух German-персональных тестов: (а) французский глоссарий… (б) скан на артефакты не-перевода…» |
| L1 (Low, снята ревьюером в r1 без правки) | Не требовала правки текста — снята с записью в r1 | SPEC-REVIEW-371-r1.md, раздел L1 |
| L2 (Low, снята ревьюером в r1 без правки) | Не требовала правки текста — снята с записью в r1 | SPEC-REVIEW-371-r1.md, раздел L2 |
Обе Medium-находки закрыты содержательно, не декларативно: формулировки ревизии
указывают конкретную ветку кода (с точным местом вставки и текстом), конкретный смок и
конкретную тестовую конструкцию, а не общие слова «учтём» / «поправим». Технической
проверкой (сверка с реальным кодом bundle-manifest.mjs и i18n.test.mjs, п. 3-5 выше)
подтверждено, что оба обещания реализуемы буквально так, как написаны, и действительно
закрывают тот пробел, который был назван в r1.
Унаследовано из r1
Без повторной проверки в этом раунде принято всё, что не входит в дельту r1→r2 —
документ docs/reviews/SPEC-REVIEW-371-r1.md, сверка на SHA 914bb4ed (dev на момент
r1; код с тех пор не менялся, issue ещё не реализована):
- Классификация трека
smallпо пяти критериям §5 (единственная точка расширения — реестр локалей #62, спроектированный под этот случай; одна поверхность; нет миграций конфига; нет нового UX-контракта; нет влияния на perf/touch) — раздел «Что проверено и корректно» r1. - К1 (файлы, арифметика вклада 1026+121−21=1126, backend-паритет 6/6).
- К2 в заявленной на r1 части (
registry.ts:loadFrench/токен/запись — точная копияloadGerman;bundle-manifest.mjs:_role,localeRoots,DE_RETRY_ASSET_TOKEN→ парный FR-токен и regex(de|fr)-). - К3 (авто-выбор по BCP-47:
resolveLanguageCodeуже обобщён, юнитde-DE/de-AT/de-CHдоказывает механизм без доп. кода дляfr-FR/fr-CA/fr-BE/fr-CH). - К4 (доки: абзац auto-языка в USER-GUIDE строится по существующему шаблону; changelog).
- AC2-AC5 в исходной формулировке r1 (юнит реестра, смок AC3 как лёгкий клон
smoke_german_locale, бюджет AC4, мутационный тест AC5). - Продуктовая рамка: локализация не входит буквальной строкой в J1-J7
docs/SCOPE.md, но прецедент #62/#348 уже прошёл этот вопрос — инфраструктура спроектирована под добавление языков, German принят на ней с P1, открывать вопрос заново на четвёртом языке того же пайплайна избыточно (перепроверено:docs/SCOPE.mdпо-прежнему не содержит строки про локализацию как отдельный job, см. п. «Как проверялось» этого документа — J1-J7 сверены заново, т.к. это дешёвая проверка, но вывод не изменился). - Вывод «догадок, выданных за решение, не найдено» — дельта r1→r2 тоже проверена на этот критерий заново (см. ниже) и вывод тот же.
Проверка дельты на новые находки
Прочитаны оба добавленных абзаца ещё раз с вопросом «не появилась ли новая находка вместе с закрытием старой»:
- Порядок веток в ternary (
ru → de → fr → en) ревизия не оговаривает явно, но это не требует решения: проверка идёт по несовпадающим префиксам (ru/de/fr), порядок веток не влияет на результат — не находка, а деталь реализации без развилки. - Ревизия не переносит в French-тест конкретные «сентинел»-маркеры German-теста
(
ZXQPH/QXZ/⟦HP,test/i18n.test.mjs:335) — это не пробел: маркеры проверены, нигде в кодовой базе не используются как общий контракт (grepпо.mjs/.ts/.jsвнеnode_modulesдаёт единственное вхождение — в самом German-тесте), это специфичный для истории German-словаря артефакт, а не воспроизводимая для French проверка. Формулировка ревизии «скан на артефакты не-перевода: кириллица… запрещена» покрывает содержательно значимую часть (реальный риск для контрибьютора, часть рабочего процесса которого проходит на русском) без копирования частности одного раунда. - Новый текст не вводит противоречий с K1/K3/K4/AC2-AC5 и не расширяет скоуп задачи
(не добавляет пятый язык, не трогает
LanguageRuntime, не меняет видимое поведение сверх «French — четвёртый пункт в существующем пикере»).
Новых находок в дельте не обнаружено.
Вердикт
Зелёный: 0 High, 0 Medium. Обе Medium-находки r1 закрыты содержательно и проверяемо; новых находок дельта не породила. Классификация трека, продуктовая рамка и остальной контракт наследуются из r1 без изменений. ТЗ готово к переходу на код-ревью.
Чего не проверял
- Не открывал приложенный контрибьютором zip-архив и не проверял содержимое French словарей — файлов ещё нет в репозитории (то же основание, что в r1).
- Не запускал
npm run typecheck/test/build/bundle:budget/check-docs— класс A не тронут, кода этой задачи в репозитории ещё нет; фиксировать зелёный прогон не на чем на этапеspec. - Не оценивал лингвистическое качество французских строк ни в присланном словаре, ни в
новых французских фразах ревизии (текст плашки
entryFallbackPlugin) — вне компетенции ревью ТЗ; owner явно оставил финальную вычитку носителю языка постфактум (комментарий от 2026-08-29T09:58:31Z).