Skip to content

v0.10.0 — Cultural Precision & Privacy (VN Register + Privacy badge + Glossary v2)

Choose a tag to compare

@hoangperry hoangperry released this 25 May 16:47

Cultural Precision & Privacy

The first feature-loaded minor since v0.9.0. Three new sub-features anchored on "Apple Intelligence can't do per-locale Vietnamese precision" — pin your social register, see the privacy class of every translation, and replace the free-text glossary blob with typed entries the LLM follows exactly. Risk-free upgrade — 375/79 tests stay GREEN, every v0.9.x setting + binding + hotkey + glossary blob round-trips byte-identical, Sparkle OTA from v0.6.1+ users auto-updates with no manual action.

Added — VN Social Register Card (anchor)

  • RegisterCard with 3 axes — Dialect (Bắc/Nam), Kinship (anh/chị/em/cháu/bạn), Formality (formal/neutral/casual) — plus optional roleHint ("e.g. TikTok Shop seller addressing customer").
  • Settings → Contextual Rewrite → Vietnamese register card — 3 Picker dropdowns, roleHint TextField, Reset button, live composed-prompt preview so you see exactly what the LLM receives.
  • Composition is prepend, not replace — your per-binding tone instructions flow through unchanged below the [Tone] tag.
  • nil = disabled (default) → v0.9.x behaviour byte-identical for users who never open the panel.

Added — Local-LLM Privacy mode

  • ProviderPrivacyClass enum (.local / .cloud / .hosted) — every provider classifies explicitly. Ollama = .local, BackendProvider = .hosted, everything else (Gemini direct + CLI, Codex CLI, DeepL, Google Translate, LibreTranslate, OpenAI-compatible) = .cloud.
  • PreviewHUD Privacy badge — every translation surfaces the provider's class (🛡 Local / ☁ Cloud / 🏢 Hosted) with .help() tooltip showing the full provider name. Stamped eagerly into TranslationStyle at workflow construction so SwiftUI render never reaches back into providerFactory().
  • Settings → Privacy section — active-provider ribbon with Vietnamese headline ("🛡 Local only — không gửi dữ liệu khách ra nước ngoài" when Ollama is active), collapsed Ollama onboarding card with download link + two curated ollama pull commands (each with a Copy button) + Browse-more-models link + one-click "Test Ollama connection" button (2s timeout, exact-name model match via /api/tags JSON parse, inline green/red status).

Added — Glossary v2-Lite (typed entries)

  • GlossaryEntry Codable with 3 kinds — .dontTranslate(term:) for brand names / code identifiers, .alias(from:to:) for casing & spelling normalisations, .alwaysTranslate(term:to:) for jargon.
  • Forward-compatible partial recovery — unknown KindTag from a future v0.10.x is dropped at the element level (not the whole list) via GlossaryEntry.decodeArray(from:). A user who briefly opens v0.10.1, adds one .scoped entry, then downgrades, keeps every other v0.10.0 entry intact.
  • GlossaryComposer pure function composes typed entries + legacy free-text blob. dontTranslate grouped under one bullet; alias + alwaysTranslate render one-per-line directional pairs. 50-entry render cap.
  • Settings → Glossary editor — Add-menu for the 3 kinds, type pill tinted per kind, 1-or-2 TextFields per row, delete button, drag-to-reorder via .onMove. Legacy free-text TextEditor moved to a collapsed DisclosureGroup below; rules apply structured-first.

What's-New on first launch

A small window pops once for v0.10.0 upgrades with 3 highlight cards (Register Card / Privacy badge / Glossary v2). Doesn't steal focus — click "Got it" to dismiss.

Compatibility

  • Every v0.9.x persisted state round-trips clean: RewriteBinding, HotkeyConfig, TranslationStyle, SaaSConfig, legacy glossary string blob — all unchanged in storage.
  • TranslationStyle gains 3 default-safe fields (registerCard: nil, privacyClass: nil, providerDisplayName: "").
  • macOS 14 minimum preserved.

Install

  • Sparkle OTA: v0.6.1+ users auto-update from inside the app — no action needed.
  • Fresh install: download the DMG below → drag to /Applications.

Verify

  • App: 375 Swift / 79 suites GREEN (+56 from v0.9.2's 319).
  • Build: signed (Developer ID), notarized + stapled (DMG + Sparkle zip), EdDSA-signed appcast.

Deliver-phase code review

Independent code-review agent before tag: 0 CRITICAL / 3 HIGH / 4 MED / 2 LOW. All 3 HIGH + 3 MED fixed inline (commit 285f519):

  • H1SettingsPrivacySection was allocating TranslationProviderFactory on every SwiftUI render. Cached in @State, refreshed only on provider-change.
  • H2 — Force-unwrapped URL literals in body position. Lifted to static let constants.
  • H3 — Glossary array decode was all-or-nothing; one unknown KindTag from a future v0.10.x would nuke the user's whole list. Switched to per-element partial-recovery decode.
  • M1 — Concurrent taps of "Test Ollama connection" could race on testResult. Added isTesting guard.
  • M2RegisterCard.prompted was appending raw prefix instead of trimmed prefix, opening a double-blank gap when callers passed trailing whitespace.
  • M3 — Ollama /api/tags model match was substring (bodyText.contains(...)) which gave false positives. Switched to JSON-parsed exact-name match.

Deferred to v0.10.1: M4 (stamp() helper duplication between TranslationWorkflow + CaptureOrchestrator), L1 (roleHint character-vs-byte cap clarification), L2 (LibreTranslate self-hosted shows ☁ Cloud despite localhost endpoint).

Roadmap (next)

  • v0.10.1 patch backlog: M4 + L1 + L2 above, per-binding register-card override toggle (carried from v0.10.0 deferred list).
  • v0.11.0 candidates: voice input via WhisperKit (Moonshine-Medium is production-ready for VN by 2026), Glossary v2 scoped entries (per-language + per-app).
    EOF