Skip to content

feat(translation): render-time translation core — settings, single-endpoint client, settled-only, cache - #700

Open
yunsheng111 wants to merge 9 commits into
xintaofei:mainfrom
yunsheng111:feat/translation-settings-client
Open

feat(translation): render-time translation core — settings, single-endpoint client, settled-only, cache#700
yunsheng111 wants to merge 9 commits into
xintaofei:mainfrom
yunsheng111:feat/translation-settings-client

Conversation

@yunsheng111

Copy link
Copy Markdown

Summary

Re-grounded, settled-only core of the render-time translation middleware (#695), rebuilt from main as the first of three PRs splitting up #696 along the seams suggested in its review:

  1. this PR — settings + single-endpoint client + settled-only translation + cache, with one authoritative acceptance owner
  2. multi-endpoint rotation pool (round-robin + AIMD + cooldowns) — stacked PR to follow
  3. streaming segmentation — stacked PR to follow

The single acceptance owner

translate_one runs marker-echo normalization and the quality gates before any success report or cache insert. A parseable-but-refused reply is recorded as a gate rejection — never an Ok (review I7), never cached (review I6). Identity segments return skipped: true and the frontend renders them verbatim past its own gates, so the two sides can no longer disagree about "already in the target language" (review I5). Retry escalation carries a variant that folds into the cache key, so an escalated retry cannot hit the cached reply it just rejected.

Other review items addressed here

  • B1: build.rs gates the resource.lib link args by CARGO_CFG_TARGET_OS/CARGO_CFG_TARGET_ENV (a build script's #[cfg] describes the host), so cargo test links again on macOS/Linux.
  • B3: the settings subscription binds its flag only after subscribe succeeds, catches rejection (no unhandled rejection failing untouched vitest files), and retries on the next mount.
  • I15: with streaming deferred to PR3, thinking translation is a real settled path; the dual-hook structure that could never issue a request is gone by construction.
  • I16: test_connection never falls back to another provider; an incomplete row is reported as such.
  • I17: error bodies and model lists are read through read_capped.

Structure

  • Backend src-tauri/src/translation/: settings.rs, endpoint.rs (single-endpoint selection + consecutive-failure cooldown), client.rs (protocol adapters, acceptance owner), mod.rs (orchestration, gates), cache.rs (variant-aware key), metrics.rs (single-row counters), prompt.rs; wired into desktop commands and the Axum router (12 routes) with integration coverage in tests/translation_api.rs.
  • Frontend: lib/translation.ts (mask, numbered protocol, marker-echo restoration), use-translated-text.ts (settled), renderer integration, settings page with provider CRUD + outbound-flow disclosure (code spans never leave the machine), i18n keys in all 10 locales.
  • Deliberately excluded from this PR: the rotation pool, health scoring, trend chart, streaming segmentation, the log-budget rewrite, and the CLAUDE.md regeneration.

Testing

  • tsc --noEmit: clean; eslint on touched files: clean
  • pnpm test: 437 files / 6429 tests passing
  • cargo check + cargo clippy --all-targets -D warnings, desktop and server modes: clean
  • cargo test --features test-utils (full, including integration binaries): 3616 lib tests + all integration binaries passing
  • cargo test --no-default-features --bin codeg-server --lib: 3575 passing

macOS/Linux linking can't be exercised locally; the B1 fix reads the target triple from Cargo-provided env rather than #[cfg] for exactly that reason.

Follow-ups (tracked on #696): app-level settings provider, disk-cache compaction (I11), provider-dimension cache keys (I12), DeepL/Azure-style purpose-built API as a default path, log size-based rotation.

The tauri-build resource.lib artifact only exists on Windows/MSVC, so the
rustc-link-arg-tests directive handed every other platform's linker a
path that does not exist and broke cargo test compilation of all
integration-test binaries. A build script's #[cfg] describes the host,
not the target, so the gate reads CARGO_CFG_TARGET_OS and
CARGO_CFG_TARGET_ENV instead (the lib.rs #[link] side was already
target-gated).
Render-time translation into the UI language without touching the agent,
the prompt, or the session files. PR1 carries the settled-only core:
provider settings with validation and change broadcast, one
OpenAI/Claude/Gemini/Ollama-compatible client, sanity gates,
content-addressed memory+disk cache, per-process counters, and
consecutive-failure cooldown for the single configured endpoint.

The acceptance decision has a single owner: translate_one runs
normalize/marker-echo and the quality gates BEFORE any success report or
cache insert — a parseable-but-refused reply is recorded as a gate
rejection, never as an Ok, and never lands in the cache. Identity
segments return skipped:true so the frontend bypasses its own gates
(dissolving the cross-boundary predicate disagreement), and retry
variants fold into the cache key so an escalated retry cannot hit the
cached reply it just rejected.
Wire the translation module into both runtimes: twelve translation_*
Tauri commands, the same twelve POST /api/translation_* routes, and the
translation-settings-changed event bridge. Pool runtime events wait for
the rotation pool (PR2).
Auth matrix, settings save/get roundtrip with key masking, identity
translation returning skipped:true end to end, validation rejections,
and the PR1 shape of the metrics/status endpoints — the axum wiring had
no integration coverage before.
…rotocol

Envelope builders, code-span masking ([[CBLK]] placeholders never leave
the machine), numbered-request protocol with a lenient inline second
pass, protocol-marker echo restoration, structure-flattening refusal,
and per-chunk quality judgement helpers. Settings/stats API shapes match
the single-endpoint backend (variant rides with every translate call).
… subscription

The settings event subscription no longer opens unconditionally during
render: binding succeeds before the bound flag is set, failures are
caught (no unhandled rejection breaking vitest runs of untouched test
files) and retried on the next mount. skipped results render verbatim
past every frontend gate, and retry escalation carries the variant so
the backend cache cannot serve the reply the gate just refused.
Text and reasoning parts translate through the settled hook — thinking
translation is a real path now, not the dual-hook structure that could
never issue a request. The original/translation toggle keeps its
keyPrefix scoping.
Multi-row endpoint configuration with per-row test connection (no
fallback: an incomplete row is reported as such, never silently tested
through another row), enable toggle, target language, failure-threshold
and cooldown settings, one-line call statistics, and an outbound-flow
disclosure naming what leaves the machine — code spans never do.
95 keys per locale with genuine translations; locale key sets verified
identical by script and by the repo's parity test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant