feat: add internationalization support for chatter and template file operations

- Replace hardcoded strings with i18n keys in chatter.go
- Add translation keys for errors, warnings, and metadata in locale files
- Update file.go to use i18n for operation messages and errors
- Provide translations in German, English, Spanish, Persian, French, Italian, Japanese, Portuguese, and Chinese
- Enable localized output for stream updates and file plugin operations
- Ensure consistent error handling across supported languages
- Maintain backward compatibility with existing functionality
This commit is contained in:
Kayvan Sylvan
2026-02-16 04:21:56 -08:00
parent 4626fd6c44
commit a71a006f74
12 changed files with 442 additions and 41 deletions

View File

@@ -11,6 +11,8 @@ import (
"strconv"
"strings"
"time"
"github.com/danielmiessler/fabric/internal/i18n"
)
// MaxFileSize defines the maximum file size that can be read (1MB)
@@ -24,25 +26,25 @@ type FilePlugin struct{}
// safePath validates and normalizes file paths
func (p *FilePlugin) safePath(path string) (string, error) {
debugf("File: validating path %q", path)
debugf(i18n.T("template_file_log_validating_path"), path)
// Basic security check - no path traversal
if strings.Contains(path, "..") {
return "", fmt.Errorf("file: path cannot contain '..'")
return "", fmt.Errorf("%s", i18n.T("template_file_error_path_contains_parent_ref"))
}
// Expand home directory if needed
if strings.HasPrefix(path, "~/") {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("file: could not expand home directory: %v", err)
return "", fmt.Errorf(i18n.T("template_file_error_expand_home_dir"), err)
}
path = filepath.Join(home, path[2:])
}
// Clean the path
cleaned := filepath.Clean(path)
debugf("File: cleaned path %q", cleaned)
debugf(i18n.T("template_file_log_cleaned_path"), cleaned)
return cleaned, nil
}
@@ -53,13 +55,13 @@ func (p *FilePlugin) safePath(path string) (string, error) {
// - size:PATH - Get file size in bytes
// - modified:PATH - Get last modified time
func (p *FilePlugin) Apply(operation string, value string) (string, error) {
debugf("File: operation=%q value=%q", operation, value)
debugf(i18n.T("template_file_log_operation_value"), operation, value)
switch operation {
case "tail":
parts := strings.Split(value, "|")
if len(parts) != 2 {
return "", fmt.Errorf("file: tail requires format path|lines")
return "", fmt.Errorf("%s", i18n.T("template_file_error_tail_requires_path_lines"))
}
path, err := p.safePath(parts[0])
@@ -69,11 +71,11 @@ func (p *FilePlugin) Apply(operation string, value string) (string, error) {
n, err := strconv.Atoi(parts[1])
if err != nil {
return "", fmt.Errorf("file: invalid line count %q", parts[1])
return "", fmt.Errorf(i18n.T("template_file_error_invalid_line_count"), parts[1])
}
if n < 1 {
return "", fmt.Errorf("file: line count must be positive")
return "", fmt.Errorf("%s", i18n.T("template_file_error_line_count_positive"))
}
lines, err := p.lastNLines(path, n)
@@ -82,7 +84,7 @@ func (p *FilePlugin) Apply(operation string, value string) (string, error) {
}
result := strings.Join(lines, "\n")
debugf("File: tail returning %d lines", len(lines))
debugf(i18n.T("template_file_log_tail_returning_lines"), len(lines))
return result, nil
case "read":
@@ -93,20 +95,20 @@ func (p *FilePlugin) Apply(operation string, value string) (string, error) {
info, err := os.Stat(path)
if err != nil {
return "", fmt.Errorf("file: could not stat file: %v", err)
return "", fmt.Errorf(i18n.T("template_file_error_stat_file"), err)
}
if info.Size() > MaxFileSize {
return "", fmt.Errorf("file: size %d exceeds limit of %d bytes",
return "", fmt.Errorf(i18n.T("template_file_error_size_exceeds_limit"),
info.Size(), MaxFileSize)
}
content, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("file: could not read: %v", err)
return "", fmt.Errorf(i18n.T("template_file_error_read_file"), err)
}
debugf("File: read %d bytes", len(content))
debugf(i18n.T("template_file_log_read_bytes"), len(content))
return string(content), nil
case "exists":
@@ -117,7 +119,7 @@ func (p *FilePlugin) Apply(operation string, value string) (string, error) {
_, err = os.Stat(path)
exists := err == nil
debugf("File: exists=%v for path %q", exists, path)
debugf(i18n.T("template_file_log_exists_for_path"), exists, path)
return fmt.Sprintf("%t", exists), nil
case "size":
@@ -128,11 +130,11 @@ func (p *FilePlugin) Apply(operation string, value string) (string, error) {
info, err := os.Stat(path)
if err != nil {
return "", fmt.Errorf("file: could not stat file: %v", err)
return "", fmt.Errorf(i18n.T("template_file_error_stat_file"), err)
}
size := info.Size()
debugf("File: size=%d for path %q", size, path)
debugf(i18n.T("template_file_log_size_for_path"), size, path)
return fmt.Sprintf("%d", size), nil
case "modified":
@@ -143,36 +145,36 @@ func (p *FilePlugin) Apply(operation string, value string) (string, error) {
info, err := os.Stat(path)
if err != nil {
return "", fmt.Errorf("file: could not stat file: %v", err)
return "", fmt.Errorf(i18n.T("template_file_error_stat_file"), err)
}
mtime := info.ModTime().Format(time.RFC3339)
debugf("File: modified=%q for path %q", mtime, path)
debugf(i18n.T("template_file_log_modified_for_path"), mtime, path)
return mtime, nil
default:
return "", fmt.Errorf("file: unknown operation %q (supported: read, tail, exists, size, modified)",
return "", fmt.Errorf(i18n.T("template_file_error_unknown_operation"),
operation)
}
}
// lastNLines returns the last n lines from a file
func (p *FilePlugin) lastNLines(path string, n int) ([]string, error) {
debugf("File: reading last %d lines from %q", n, path)
debugf(i18n.T("template_file_log_reading_last_lines"), n, path)
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("file: could not open: %v", err)
return nil, fmt.Errorf(i18n.T("template_file_error_open_file"), err)
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return nil, fmt.Errorf("file: could not stat: %v", err)
return nil, fmt.Errorf(i18n.T("template_file_error_stat_open_file"), err)
}
if info.Size() > MaxFileSize {
return nil, fmt.Errorf("file: size %d exceeds limit of %d bytes",
return nil, fmt.Errorf(i18n.T("template_file_error_size_exceeds_limit"),
info.Size(), MaxFileSize)
}
@@ -189,9 +191,9 @@ func (p *FilePlugin) lastNLines(path string, n int) ([]string, error) {
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("file: error reading: %v", err)
return nil, fmt.Errorf(i18n.T("template_file_error_scanner_read"), err)
}
debugf("File: read %d lines total, returning last %d", lineCount, len(lines))
debugf(i18n.T("template_file_log_read_total_return_last"), lineCount, len(lines))
return lines, nil
}