feat: internationalize file manager, Vertex AI, and Copilot error messages via i18n

- Replace hardcoded error strings in `file_manager.go` with i18n translation keys
- Add file manager, Vertex AI, and Copilot i18n keys to all 10 locale files
- Internationalize Copilot plugin error messages and debug logs
- Internationalize Vertex AI model fetching error messages
- Fix JSON trailing comma syntax errors across all locale files
- Normalize German locale JSON indentation from tabs to spaces
- Use `AddSetupQuestionWithEnvName` for Bedrock AWS region setup
This commit is contained in:
Kayvan Sylvan
2026-02-16 13:10:00 -08:00
parent d426a930ee
commit f26e328d40
14 changed files with 724 additions and 470 deletions

View File

@@ -55,7 +55,7 @@ func NewClient() (ret *BedrockClient) {
ret.PluginBase = plugins.NewVendorPluginBase(vendorName, func() error {
return fmt.Errorf(i18n.T("bedrock_unable_load_aws_config"), err)
})
ret.bedrockRegion = ret.PluginBase.AddSetupQuestion(i18n.T("bedrock_aws_region_label"), true)
ret.bedrockRegion = ret.PluginBase.AddSetupQuestionWithEnvName("AWS Region", true, i18n.T("bedrock_aws_region_label"))
return
}
@@ -69,7 +69,7 @@ func NewClient() (ret *BedrockClient) {
ret.runtimeClient = runtimeClient
ret.controlPlaneClient = controlPlaneClient
ret.bedrockRegion = ret.PluginBase.AddSetupQuestion(i18n.T("bedrock_aws_region_label"), true)
ret.bedrockRegion = ret.PluginBase.AddSetupQuestionWithEnvName("AWS Region", true, i18n.T("bedrock_aws_region_label"))
if cfg.Region != "" {
ret.bedrockRegion.Value = cfg.Region

View File

@@ -24,6 +24,7 @@ import (
"github.com/danielmiessler/fabric/internal/chat"
"github.com/danielmiessler/fabric/internal/domain"
"github.com/danielmiessler/fabric/internal/i18n"
debuglog "github.com/danielmiessler/fabric/internal/log"
"github.com/danielmiessler/fabric/internal/plugins"
"golang.org/x/oauth2"
@@ -106,7 +107,7 @@ type Client struct {
// configure initializes the client with OAuth2 configuration.
func (c *Client) configure() error {
if c.TenantID.Value == "" || c.ClientID.Value == "" {
return fmt.Errorf("tenant ID and client ID are required")
return fmt.Errorf("%s", i18n.T("copilot_tenant_client_id_required"))
}
// Build OAuth2 configuration
@@ -168,7 +169,7 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
// Create a conversation
conversationID, err := c.createConversation(ctx)
if err != nil {
return "", fmt.Errorf("failed to create conversation: %w", err)
return "", fmt.Errorf(i18n.T("copilot_failed_create_conversation"), err)
}
// Build the message content from chat messages
@@ -177,7 +178,7 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
// Send the chat message
response, err := c.sendChatMessage(ctx, conversationID, messageText)
if err != nil {
return "", fmt.Errorf("failed to send message: %w", err)
return "", fmt.Errorf(i18n.T("copilot_failed_send_message"), err)
}
return response, nil
@@ -192,7 +193,7 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
// Create a conversation
conversationID, err := c.createConversation(ctx)
if err != nil {
return fmt.Errorf("failed to create conversation: %w", err)
return fmt.Errorf(i18n.T("copilot_failed_create_conversation"), err)
}
// Build the message content from chat messages
@@ -200,7 +201,7 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
// Send the streaming chat message
if err := c.sendChatMessageStream(ctx, conversationID, messageText, channel); err != nil {
return fmt.Errorf("failed to stream message: %w", err)
return fmt.Errorf(i18n.T("copilot_failed_stream_message"), err)
}
return nil
@@ -253,7 +254,7 @@ func (c *Client) createConversation(ctx context.Context) (string, error) {
if resp.StatusCode != http.StatusCreated {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("failed to create conversation: %s - %s", resp.Status, string(body))
return "", fmt.Errorf(i18n.T("copilot_error_create_conversation"), resp.Status, string(body))
}
var result conversationResponse
@@ -261,7 +262,7 @@ func (c *Client) createConversation(ctx context.Context) (string, error) {
return "", err
}
debuglog.Debug(debuglog.Detailed, "Created Copilot conversation: %s\n", result.ID)
debuglog.Debug(debuglog.Detailed, i18n.T("copilot_debug_created_conversation")+"\n", result.ID)
return result.ID, nil
}
@@ -299,7 +300,7 @@ func (c *Client) sendChatMessage(ctx context.Context, conversationID, messageTex
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("chat request failed: %s - %s", resp.Status, string(body))
return "", fmt.Errorf(i18n.T("copilot_error_chat_request"), resp.Status, string(body))
}
var result conversationResponse
@@ -346,7 +347,7 @@ func (c *Client) sendChatMessageStream(ctx context.Context, conversationID, mess
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("stream request failed: %s - %s", resp.Status, string(body))
return fmt.Errorf(i18n.T("copilot_error_stream_request"), resp.Status, string(body))
}
// Parse SSE stream
@@ -373,7 +374,7 @@ func (c *Client) parseSSEStream(reader io.Reader, channel chan domain.StreamUpda
var event conversationResponse
if err := json.Unmarshal([]byte(jsonData), &event); err != nil {
debuglog.Debug(debuglog.Detailed, "Failed to parse SSE event: %v\n", err)
debuglog.Debug(debuglog.Detailed, i18n.T("copilot_debug_failed_parse_sse_event")+"\n", err)
continue
}
@@ -394,7 +395,7 @@ func (c *Client) parseSSEStream(reader io.Reader, channel chan domain.StreamUpda
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("error reading stream: %w", err)
return fmt.Errorf(i18n.T("copilot_error_reading_stream"), err)
}
channel <- domain.StreamUpdate{Type: domain.StreamTypeContent, Content: "\n"}

View File

@@ -9,6 +9,7 @@ import (
"sort"
"strings"
"github.com/danielmiessler/fabric/internal/i18n"
debuglog "github.com/danielmiessler/fabric/internal/log"
)
@@ -40,7 +41,7 @@ type publisherModel struct {
func fetchModelsPage(ctx context.Context, httpClient *http.Client, url, projectID, publisher string) (*publisherModelsResponse, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
return nil, fmt.Errorf(i18n.T("vertexai_error_create_request"), err)
}
req.Header.Set("Accept", "application/json")
@@ -49,28 +50,28 @@ func fetchModelsPage(ctx context.Context, httpClient *http.Client, url, projectI
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
return nil, fmt.Errorf(i18n.T("vertexai_error_request_failed"), err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(io.LimitReader(resp.Body, errorResponseLimit))
debuglog.Debug(debuglog.Basic, "API error for %s: status %d, url: %s, body: %s\n", publisher, resp.StatusCode, url, string(bodyBytes))
return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(bodyBytes))
return nil, fmt.Errorf(i18n.T("vertexai_error_api_status"), resp.StatusCode, string(bodyBytes))
}
bodyBytes, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1))
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
return nil, fmt.Errorf(i18n.T("vertexai_error_read_response"), err)
}
if len(bodyBytes) > maxResponseSize {
return nil, fmt.Errorf("response too large (>%d bytes)", maxResponseSize)
return nil, fmt.Errorf(i18n.T("vertexai_error_response_too_large"), maxResponseSize)
}
var response publisherModelsResponse
if err := json.Unmarshal(bodyBytes, &response); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
return nil, fmt.Errorf(i18n.T("vertexai_error_parse_response"), err)
}
return &response, nil