summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-01 13:15:13 +0300
committerPaul Buetow <paul@buetow.org>2026-04-01 13:15:13 +0300
commitd3fe3586f50545575978f8d73a649afabc915008 (patch)
treedf11fc9e4c684ccabe9cf3fb006fc5fb616d3eb0
parent5b88a045b29970a3cc057326b85eae58ab41fe02 (diff)
zr: preserve Gemini init errors and docs
-rw-r--r--assets/config.yaml.example11
-rw-r--r--internal/processor/processor_test.go12
-rw-r--r--internal/translation/translator.go24
-rw-r--r--internal/translation/translator_test.go29
4 files changed, 62 insertions, 14 deletions
diff --git a/assets/config.yaml.example b/assets/config.yaml.example
index 3988035..18d4513 100644
--- a/assets/config.yaml.example
+++ b/assets/config.yaml.example
@@ -23,6 +23,17 @@ audio:
# openai_instruction: "You are a Bulgarian language teacher. Pronounce the Bulgarian words slowly with authentic Bulgarian accent and phonetics."
# openai_instruction: "Speak Bulgarian text with proper Bulgarian pronunciation. Avoid Russian accent. Speak clearly at a pace suitable for beginners."
+# Translation configuration
+translation:
+ # Translation backend used by internal/translation/translator.go
+ # Supported values: openai (default) or gemini
+ provider: openai
+
+# Google API settings
+google:
+ # Used by Gemini translation when translation.provider is set to gemini
+ api_key: ${GOOGLE_API_KEY}
+
# Image configuration
image:
# Provider: currently only openai is supported
diff --git a/internal/processor/processor_test.go b/internal/processor/processor_test.go
index 9e6b5fc..d0a0070 100644
--- a/internal/processor/processor_test.go
+++ b/internal/processor/processor_test.go
@@ -110,9 +110,9 @@ func TestProcessSingleWord_InvalidWord(t *testing.T) {
}
func TestProcessSingleWord_ValidWord(t *testing.T) {
- // Skip if no API key
- if os.Getenv("OPENAI_API_KEY") == "" || os.Getenv("GOOGLE_API_KEY") == "" {
- t.Skip("Skipping test: OPENAI_API_KEY and GOOGLE_API_KEY must be set")
+ // Skip if no OpenAI API key
+ if os.Getenv("OPENAI_API_KEY") == "" {
+ t.Skip("Skipping test: OPENAI_API_KEY must be set")
}
flags := cli.NewFlags()
@@ -162,9 +162,9 @@ func TestProcessBatch_ValidFile(t *testing.T) {
flags.SkipImages = true
p := NewProcessor(flags)
- // Skip if no API keys
- if os.Getenv("OPENAI_API_KEY") == "" || os.Getenv("GOOGLE_API_KEY") == "" {
- t.Skip("Skipping test: OPENAI_API_KEY and GOOGLE_API_KEY must be set")
+ // Skip if no OpenAI API key
+ if os.Getenv("OPENAI_API_KEY") == "" {
+ t.Skip("Skipping test: OPENAI_API_KEY must be set")
}
err = p.ProcessBatch()
diff --git a/internal/translation/translator.go b/internal/translation/translator.go
index 1a3d3b2..09a1e67 100644
--- a/internal/translation/translator.go
+++ b/internal/translation/translator.go
@@ -47,15 +47,18 @@ func DefaultConfig() *Config {
// Translator handles Bulgarian and English translation using the configured backend.
type Translator struct {
- provider Provider
- openAIKey string
- googleAPIKey string
- openAIClient *openai.Client
- geminiClient *genai.Client
- openAIModel string
- geminiModel string
+ provider Provider
+ openAIKey string
+ googleAPIKey string
+ openAIClient *openai.Client
+ geminiClient *genai.Client
+ geminiInitErr error
+ openAIModel string
+ geminiModel string
}
+var newGeminiClient = genai.NewClient
+
// NewTranslator creates a new translator instance from the provided config.
func NewTranslator(config *Config) *Translator {
if config == nil {
@@ -76,11 +79,13 @@ func NewTranslator(config *Config) *Translator {
}
if normalized.GoogleAPIKey != "" {
- client, err := genai.NewClient(context.Background(), &genai.ClientConfig{
+ client, err := newGeminiClient(context.Background(), &genai.ClientConfig{
APIKey: normalized.GoogleAPIKey,
})
if err == nil {
translator.geminiClient = client
+ } else {
+ translator.geminiInitErr = err
}
}
@@ -169,6 +174,9 @@ func (t *Translator) translateWithGemini(prompt string) (string, error) {
if t.googleAPIKey == "" {
return "", fmt.Errorf("Google API key not found")
}
+ if t.geminiInitErr != nil {
+ return "", fmt.Errorf("Gemini client initialization failed: %w", t.geminiInitErr)
+ }
if t.geminiClient == nil {
return "", fmt.Errorf("Gemini client not initialized")
}
diff --git a/internal/translation/translator_test.go b/internal/translation/translator_test.go
index d506a1e..49f1f15 100644
--- a/internal/translation/translator_test.go
+++ b/internal/translation/translator_test.go
@@ -1,10 +1,14 @@
package translation
import (
+ "context"
+ "errors"
"os"
"path/filepath"
"reflect"
"testing"
+
+ "google.golang.org/genai"
)
func TestNewTranslator_DefaultsToOpenAI(t *testing.T) {
@@ -90,6 +94,31 @@ func TestTranslateWord_ExplicitGeminiRequiresGoogleAPIKey(t *testing.T) {
}
}
+func TestTranslateWord_GeminiClientInitError(t *testing.T) {
+ originalNewGeminiClient := newGeminiClient
+ defer func() {
+ newGeminiClient = originalNewGeminiClient
+ }()
+
+ initErr := errors.New("boom")
+ newGeminiClient = func(context.Context, *genai.ClientConfig) (*genai.Client, error) {
+ return nil, initErr
+ }
+
+ translator := NewTranslator(&Config{
+ Provider: ProviderGemini,
+ GoogleAPIKey: "test-google-key",
+ })
+
+ _, err := translator.TranslateWord("ябълка")
+ if err == nil {
+ t.Fatal("Expected Gemini init error")
+ }
+ if !errors.Is(err, initErr) {
+ t.Fatalf("Expected wrapped init error, got: %v", err)
+ }
+}
+
func TestTranslateWord_IntegrationGemini(t *testing.T) {
apiKey := os.Getenv("GOOGLE_API_KEY")
if apiKey == "" {