Skip to content

Concept: AI translations with a learned per-project glossary and leading language #8

Description

@robflowk

Goal

AI-assisted translation that respects how this project translates its business terms — not generic machine translation. A project designates a leading language; the app learns how specific terms are consistently translated into each target language and applies that knowledge when suggesting translations.

This ticket is design-first: agree on the concept below (or amend it), then split implementation into follow-up issues.

Proposed concept

1. Leading language

  • New project-config field: translations.leadingLanguage (must be one of the configured languages)
  • The leading language is the source of truth: AI suggestions always translate from it; "missing in X" workflows (Missing-translation finder with per-language selection #7) pair naturally with "suggest from leading"

2. Glossary as a first-class, committable artifact

  • babelshift.glossary.json next to the project config — plain JSON so it's reviewable in PRs, mergeable, and shared with the team
  • Entry shape: source term (in leading language) → per-language approved translation(s), plus optional note ("brand name — never translate") and a locked flag
  • Three ways entries get in:
    1. Manual: glossary editor UI in the app
    2. Mined: an analysis pass over the existing catalog finds recurring terms in leading-language values and checks whether their translations are consistent across keys; consistent pairs become suggested entries the user confirms (never auto-accepted). Inconsistent ones surface as a consistency report — valuable on its own.
    3. Imported: from an existing file the team already maintains (e.g. a GLOSSARY.md) — import is a mapping step where the user marks which terms are translation-relevant, since such files usually define terms rather than translate them

3. Suggestion pipeline

For "translate key X into language Y":

  • Context assembled by the Rust side: the source value, matched glossary entries for Y, and a handful of similar existing translations (same key namespace / similar source strings) as few-shot examples — this is what makes it learn the project's voice, tone, and formality (Du/Sie matters in German)
  • Provider-agnostic LLM layer via the Vercel AI SDK on the TypeScript side — no hardcoded provider. Two tiers:
    • Handpicked first-class providers: Anthropic, OpenAI, Google (@ai-sdk/anthropic / @ai-sdk/openai / @ai-sdk/google)
    • Generic OpenAI-compatible endpoint (@ai-sdk/openai-compatible) with configurable base URL — covers Ollama & LM Studio (local models, nothing leaves the machine — relevant for teams with sensitive strings), OpenRouter, Mistral, Groq, DeepSeek, …
  • Division of labor: Rust assembles the context (glossary matching, few-shot retrieval, placeholder validation — all provider-agnostic) and exposes it via a command; the frontend makes the model call through the AI SDK. Webview CORS is bypassed by injecting @tauri-apps/plugin-http's fetch into the provider config
  • Bring-your-own-key stored in the OS keychain (never in project files), fetched into memory only at call time; model id is a free-form string per provider; batch mode for "fill everything missing in FR"
  • Config scope: provider + model live in the project config as a committed, team-shared default, overridable per user in local app settings (teammates may use different providers or local models). Keys are per-user (keychain) regardless
  • The OpenAI-compatible tier is also the hook for a one-click local LLM setup — split out into One-click local LLM setup for AI translations (managed Ollama sidecar) #9
  • Suggestions are drafts: shown in the details panel with a distinct state (e.g. amber), never written to disk until the user accepts. Accepting is a normal value commit; explicit accept/reject also feeds term-usage stats

4. Learning loop (the honest version)

No model training — "learning" is retrieval: glossary + similar-translation few-shots improve as the catalog and glossary grow. When a user edits an AI suggestion before accepting, diff the changed terms and offer to add them to the glossary. A consistency checker (pure Rust, no AI) continuously flags values that contradict glossary entries.

Open questions

  • Glossary matching: exact + case-insensitive first; stemming/lemmatization per language later?
  • Placeholder safety: suggestions must preserve {{interpolations}}/ICU syntax — validate before showing
  • Where do suggestion drafts live — frontend-only state, or persisted so they survive restarts?
  • Batch cost control: token estimate + confirmation before large runs?

Suggested split after concept sign-off

  1. Leading language config + glossary file format + manual editor
  2. Consistency checker + glossary mining report
  3. AI SDK provider layer + single-key suggestions with glossary/few-shot context
  4. Batch fill for a target language (builds on Missing-translation finder with per-language selection #7's missing filter)
  5. One-click local LLM setup → One-click local LLM setup for AI translations (managed Ollama sidecar) #9

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions