// Package translator wraps the per-language rendering for the // diplomail subsystem. The package exposes a narrow `Translator` // interface so the actual translation backend (LibreTranslate, an // in-process model, a SaaS engine, …) can be swapped without // touching the rest of the codebase. // // Stage D ships a `NoopTranslator` that returns the input unchanged. // The diplomail Service treats a `Name == NoopEngine` result as // "translation unavailable" and refrains from writing a cache row; // the inbox handler then returns the original body with a // `translated == false` payload. The contract lets the rest of the // system ship without a translation backend; future stages can wire // a real `Translator` without code changes elsewhere. package translator import "context" // NoopEngine is the engine identifier returned by `NoopTranslator`. // The diplomail Service checks for this value to decide whether to // persist a `diplomail_translations` row. const NoopEngine = "noop" // Result carries one translated rendering plus the engine identifier // that produced it. The engine name is persisted as // `diplomail_translations.translator` so an operator can see which // backend produced each row. type Result struct { Subject string Body string Engine string } // Translator is the read-only surface diplomail consumes when it // needs to render a message for a recipient whose // `preferred_language` differs from `body_lang`. Implementations // must be safe for concurrent use; `Translate` may be invoked from // the async worker on many messages at once. type Translator interface { // Translate renders `subject` and `body` from `srcLang` into // `dstLang`. A nil error with `Result.Engine == NoopEngine` // signals that no real rendering happened. Translate(ctx context.Context, srcLang, dstLang, subject, body string) (Result, error) } // NewNoop returns a Translator that always returns the input // unchanged with engine name `NoopEngine`. func NewNoop() Translator { return noop{} } type noop struct{} func (noop) Translate(_ context.Context, _, _, subject, body string) (Result, error) { return Result{ Subject: subject, Body: body, Engine: NoopEngine, }, nil }