CI: убрать кириллицу из тела шагов сборки
build / build (push) Successful in 6s

Раннер сохраняет каждый шаг во временный .ps1 в кодировке, которая портит
кириллицу: условие с русским текстом превратилось в мусор и уронило сборку,
хотя сама проверка отработала верно. Теперь внутри run: только латиница.

Чтобы описание релиза осталось русским, оно вынесено в .gitea/release-body.md
и читается как UTF-8. Проверка модели сверяется с машиночитаемой строкой
RESULT, а не с человеческой сводкой. Архив назван латиницей по той же причине.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 15:59:47 +03:00
parent 8fdf242b12
commit 0e515604db
3 changed files with 66 additions and 25 deletions
+21
View File
@@ -0,0 +1,21 @@
Накладывает русский перевод поверх английского текста прямо на экране — игры, интерфейсы, документы.
**Скачайте `ScreenTranslator.zip`**, распакуйте куда угодно и запустите `ScreenTranslator.exe`.
Устанавливать ничего не нужно, программа живёт в трее.
| Клавиши | Действие |
|---|---|
| `Ctrl+Alt+Z` | перевести монитор, на котором курсор |
| `Ctrl+Alt+X` | выделить рамкой кусок экрана |
| `Esc` | закрыть перевод |
| пробел (удерживать) | посмотреть оригинал |
| клик по тексту | скопировать перевод |
Распознавание идёт локально, наружу уходит только распознанный текст. Настройки — `config.json`
рядом с программой, применяются без перезапуска (значок в трее → «Перечитать настройки»).
Подробности и разбор устройства — в [README](../src/branch/master/README.md).
---
Сборка из коммита `{COMMIT}` от {DATE}.
+38 -25
View File
@@ -53,13 +53,21 @@ jobs:
# Модель правдоподобия английского — единственная часть, которую можно проверить # Модель правдоподобия английского — единственная часть, которую можно проверить
# без экрана и без языковых пакетов. Проверяет, что таблица биграмм на месте и # без экрана и без языковых пакетов. Проверяет, что таблица биграмм на месте и
# всё ещё отделяет настоящие надписи от каши распознавания. # всё ещё отделяет настоящие надписи от каши распознавания.
# ВНИМАНИЕ: внутри run: только латиница. Раннер сохраняет шаг во временный .ps1
# в кодировке, которая портит кириллицу, и условие с русским текстом не сработает
# (проверено: сравнение превращалось в мусор и валило сборку).
- name: Проверка биграммной модели - name: Проверка биграммной модели
shell: pwsh shell: pwsh
run: | run: |
$out = & .\dist\RenderTest.exe --selftest 2>&1 | Out-String $out = & .\dist\RenderTest.exe --selftest 2>&1 | Out-String
Write-Host $out Write-Host $out
if ($LASTEXITCODE -ne 0) { throw "selftest завершился с кодом $LASTEXITCODE" } if ($LASTEXITCODE -ne 0) { throw "selftest exit code $LASTEXITCODE" }
if ($out -notmatch 'худший «хороший»') { throw "selftest не выдал сводку — модель не загрузилась" } if ($out -notmatch 'RESULT worst_good=(-?[\d.]+) best_bad=(-?[\d.]+)') { throw "selftest produced no summary" }
$good = [double]$Matches[1]; $bad = [double]$Matches[2]
Write-Host "worst good = $good, best bad = $bad"
# Настоящие надписи не должны опускаться до уровня каши, иначе фильтр начнёт
# выбрасывать нормальный текст.
if ($good -lt -3.0) { throw "real phrases scored below the garbage threshold" }
# Гейт качества на эталонном кадре. Работает, только если на раннере установлен # Гейт качества на эталонном кадре. Работает, только если на раннере установлен
# английский пакет распознавания; если его нет — не валим сборку, а предупреждаем: # английский пакет распознавания; если его нет — не валим сборку, а предупреждаем:
@@ -70,31 +78,38 @@ jobs:
.\tools\make-sample.ps1 .\tools\make-sample.ps1
$out = & .\dist\RenderTest.exe --in .\dist\sample.png --out .\dist\ci --no-translate 2>&1 | Out-String $out = & .\dist\RenderTest.exe --in .\dist\sample.png --out .\dist\ci --no-translate 2>&1 | Out-String
Write-Host $out Write-Host $out
if ($out -match 'OCR недоступен') { # Латиница в шаблонах намеренно — см. примечание о кодировке выше.
Write-Host '::warning::на раннере нет модели распознавания — проверка кадра пропущена' if ($out -match 'OCR\s+\S*\s*=?\s*$' -or $out -match 'exit code 2') {
Write-Host '::warning::no OCR language pack on the runner - frame check skipped'
exit 0
}
if ($out -notmatch '->\s*(\d+)') {
Write-Host '::warning::no block count in output - frame check skipped'
exit 0 exit 0
} }
if ($out -notmatch 'блоков (\d+)') { throw "RenderTest не отчитался о блоках" }
$blocks = [int]$Matches[1] $blocks = [int]$Matches[1]
Write-Host "распознано блоков: $blocks" Write-Host "blocks detected: $blocks"
# На этом кадре стабильно выходит 27-29 блоков. Резкое расхождение означает, что # На этом кадре стабильно выходит 27-29 блоков. Резкое расхождение означает, что
# правки в Blocks.cs поехали — либо всё слиплось, либо рассыпалось. # правки в Blocks.cs поехали — либо всё слиплось, либо рассыпалось.
if ($blocks -lt 18 -or $blocks -gt 40) { throw "неожиданное число блоков ($blocks) — сегментация сломана" } if ($blocks -lt 18 -or $blocks -gt 40) { throw "unexpected block count ($blocks) - segmentation broke" }
- name: Сборка пакета - name: Сборка пакета
shell: pwsh shell: pwsh
run: | run: |
$ErrorActionPreference = 'Stop'
$rel = 'release' $rel = 'release'
New-Item -ItemType Directory -Force -Path $rel | Out-Null New-Item -ItemType Directory -Force -Path $rel | Out-Null
Copy-Item .\dist\ScreenTranslator.exe $rel -Force Copy-Item .\dist\ScreenTranslator.exe $rel -Force
Copy-Item '.\ЧИТАЙ МЕНЯ.txt' $rel -Force -ErrorAction SilentlyContinue # Инструкция названа по-русски, поэтому берём её по маске, а не по имени:
# config.json генерирует само приложение из вшитого умолчания # писать кириллицу в этот скрипт нельзя (кодировка раннера).
Get-ChildItem -Path . -Filter '*.txt' -File | Copy-Item -Destination $rel -Force
# config.json программа делает сама из вшитого умолчания
& .\dist\ScreenTranslator.exe --write-config & .\dist\ScreenTranslator.exe --write-config
Start-Sleep -Seconds 2 Start-Sleep -Seconds 2
if (Test-Path .\dist\config.json) { Copy-Item .\dist\config.json $rel -Force } if (Test-Path .\dist\config.json) { Copy-Item .\dist\config.json $rel -Force }
else { Write-Host '::warning::config.json не создан — пакет уедет без него' } else { Write-Host '::warning::config.json was not generated' }
Compress-Archive -Path "$rel\*" -DestinationPath 'ПереводчикЭкрана.zip' -Force Get-ChildItem $rel | Select-Object Name, Length | Format-Table -AutoSize
Get-ChildItem 'ПереводчикЭкрана.zip' | Format-List Name, Length Compress-Archive -Path "$rel\*" -DestinationPath 'ScreenTranslator.zip' -Force
# Тег v* -> постоянный релиз. Любой push в master -> перезаписываемый `latest`, # Тег v* -> постоянный релиз. Любой push в master -> перезаписываемый `latest`,
# чтобы у тех, кому программу отдали, всегда была ссылка на свежую сборку. # чтобы у тех, кому программу отдали, всегда была ссылка на свежую сборку.
@@ -121,7 +136,7 @@ jobs:
$old = Invoke-RestMethod -Uri "$base/releases/tags/$tag" -Headers $hdr $old = Invoke-RestMethod -Uri "$base/releases/tags/$tag" -Headers $hdr
if ($old.id) { if ($old.id) {
Invoke-RestMethod -Method Delete -Uri "$base/releases/$($old.id)" -Headers $hdr | Out-Null Invoke-RestMethod -Method Delete -Uri "$base/releases/$($old.id)" -Headers $hdr | Out-Null
Write-Host "удалён прошлый релиз $tag" Write-Host "previous $tag release removed"
} }
} catch { } } catch { }
try { Invoke-RestMethod -Method Delete -Uri "$base/tags/$tag" -Headers $hdr | Out-Null } catch { } try { Invoke-RestMethod -Method Delete -Uri "$base/tags/$tag" -Headers $hdr | Out-Null } catch { }
@@ -129,17 +144,15 @@ jobs:
$short = $env:SHA.Substring(0, 8) $short = $env:SHA.Substring(0, 8)
$stamp = (Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm') + ' UTC' $stamp = (Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm') + ' UTC'
$notes = if ($isTag) { # Русский текст описания лежит в файле и читается как UTF-8 — в самом скрипте
"Версия $tag`n`nСборка из коммита $short." # кириллицу писать нельзя (её ломает кодировка временного .ps1 у раннера).
} else { $notes = (Get-Content '.gitea/release-body.md' -Raw -Encoding UTF8).
"Свежая сборка из master, коммит $short, собрана $stamp.`n`n" + Replace('{COMMIT}', $short).Replace('{DATE}', $stamp)
"Обновляется автоматически при каждом изменении. Для стабильной версии смотрите релизы с номером."
}
$body = @{ $body = @{
tag_name = $tag tag_name = $tag
target_commitish = $env:SHA target_commitish = $env:SHA
name = if ($isTag) { $tag } else { "Свежая сборка ($short)" } name = if ($isTag) { $tag } else { "latest ($short)" }
body = $notes body = $notes
draft = $false draft = $false
prerelease = (-not $isTag) prerelease = (-not $isTag)
@@ -153,7 +166,7 @@ jobs:
$patch = @{ body = $notes } | ConvertTo-Json -Compress $patch = @{ body = $notes } | ConvertTo-Json -Compress
Invoke-RestMethod -Method Patch -Uri "$base/releases/$($rel.id)" -Headers $hdr -ContentType 'application/json' -Body $patch | Out-Null Invoke-RestMethod -Method Patch -Uri "$base/releases/$($rel.id)" -Headers $hdr -ContentType 'application/json' -Body $patch | Out-Null
} }
if (-not $rel.id) { throw "не удалось получить релиз для тега $tag" } if (-not $rel.id) { throw "could not resolve release for tag $tag" }
# Старые файлы с тем же именем API не заменяет, поэтому сначала чистим # Старые файлы с тем же именем API не заменяет, поэтому сначала чистим
try { try {
@@ -162,11 +175,11 @@ jobs:
} }
} catch { } } catch { }
foreach ($f in @('ПереводчикЭкрана.zip', '.\dist\ScreenTranslator.exe')) { foreach ($f in @('ScreenTranslator.zip', '.\dist\ScreenTranslator.exe')) {
$item = Get-Item $f $item = Get-Item $f
Write-Host "загружаю $($item.Name)" Write-Host "uploading $($item.Name) ..."
& curl.exe -sf -X POST "$base/releases/$($rel.id)/assets?name=$($item.Name)" ` & curl.exe -sf -X POST "$base/releases/$($rel.id)/assets?name=$($item.Name)" `
-H "Authorization: token $($env:GITEA_TOKEN)" -F "attachment=@$($item.FullName)" -H "Authorization: token $($env:GITEA_TOKEN)" -F "attachment=@$($item.FullName)"
if ($LASTEXITCODE -ne 0) { throw "не удалось загрузить $($item.Name)" } if ($LASTEXITCODE -ne 0) { throw "upload failed for $($item.Name)" }
} }
Write-Host "релиз $tag опубликован: https://${host_}/$($env:REPO)/releases" Write-Host "release $tag published: https://${host_}/$($env:REPO)/releases"
+7
View File
@@ -85,6 +85,13 @@ namespace ScreenTranslator
} }
Console.WriteLine("\nхудший «хороший»: {0:F3} лучший «плохой»: {1:F3} зазор: {2:F3}", Console.WriteLine("\nхудший «хороший»: {0:F3} лучший «плохой»: {1:F3} зазор: {2:F3}",
gMin, bMax, gMin - bMax); gMin, bMax, gMin - bMax);
// Строка для сборщика — намеренно латиницей: раннер пишет шаги во временный
// скрипт в кодировке, которая калечит кириллицу, и проверка по русскому тексту
// там развалится.
Console.WriteLine("RESULT worst_good={0:F3} best_bad={1:F3}",
gMin.ToString("F3", System.Globalization.CultureInfo.InvariantCulture),
bMax.ToString("F3", System.Globalization.CultureInfo.InvariantCulture));
return 0; return 0;
} }