diff --git a/assets/app.css b/cmd/assets/app.css similarity index 100% rename from assets/app.css rename to cmd/assets/app.css diff --git a/assets/app.js b/cmd/assets/app.js similarity index 100% rename from assets/app.js rename to cmd/assets/app.js diff --git a/cmd/clean.go b/cmd/clean.go index 7e0114f..dba26c1 100644 --- a/cmd/clean.go +++ b/cmd/clean.go @@ -15,8 +15,8 @@ import ( var Clean = &cobra.Command{ Use: "clean [unpackedEpubPath]", - Short: "Clean the html files", - Long: "Clean the html files by removing empty anchor and div tags", + Short: "Clean the HTML files in the unpacked EPUB", + Long: "This command cleans the HTML files by removing empty anchor and div tags. It should be called before any other commands like translate, styling, or mark to ensure the content is properly formatted.", Example: "epubtrans clean path/to/unpacked/epub", Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { diff --git a/cmd/mark.go b/cmd/mark.go index bf3d356..a1037b8 100644 --- a/cmd/mark.go +++ b/cmd/mark.go @@ -27,13 +27,14 @@ var blacklist = []string{ "script", "style", "template", + "svg", + "noscript", } -// Mark represents the command for marking content in EPUB files var Mark = &cobra.Command{ Use: "mark [epub_path]", - Short: "Mark content in EPUB files", - Long: "Mark content in EPUB files by adding a unique ID to each content node", + Short: "Add unique identifiers to content nodes in EPUB files", + Long: "This command marks content in EPUB files by adding a unique ID to each content node, facilitating easier reference and manipulation of the content.", Example: "epubtrans mark path/to/unpacked/epub", Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { diff --git a/cmd/pack.go b/cmd/pack.go index b8dc5f3..d63c112 100644 --- a/cmd/pack.go +++ b/cmd/pack.go @@ -3,21 +3,24 @@ package cmd import ( "archive/zip" "fmt" - "github.com/nguyenvanduocit/epubtrans/pkg/util" - "github.com/spf13/cobra" "io" "os" "path/filepath" "strings" "sync" "sync/atomic" + + "github.com/nguyenvanduocit/epubtrans/pkg/util" + "github.com/spf13/cobra" ) var Pack = &cobra.Command{ - Use: "pack [unpackaedEpubPath]", - Short: "Zip files in a directory", - Long: "Zip files in a directory and create a new zip file", - Example: "epubtrans pack path/to/unpacked/epub", + Use: "pack [unpackedEpubPath]", + Short: "Create an EPUB file from an unpacked directory", + Long: `Pack creates a new EPUB file from an unpacked directory structure. +It compresses the contents and maintains the EPUB file structure. +This command is useful after modifying the contents of an unpacked EPUB.`, + Example: "epubtrans pack /path/to/unpacked/epub", Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("unpackedEpubPath is required") diff --git a/cmd/serve.go b/cmd/serve.go index 8f3eaae..d8e7f40 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -4,11 +4,6 @@ import ( "bytes" "encoding/xml" "fmt" - "github.com/PuerkitoBio/goquery" - "github.com/gofiber/fiber/v2" - "github.com/nguyenvanduocit/epubtrans/pkg/loader" - "github.com/nguyenvanduocit/epubtrans/pkg/util" - "github.com/spf13/cobra" "io" "io/ioutil" "log/slog" @@ -18,15 +13,28 @@ import ( "path" "path/filepath" "strings" + + "embed" + + "github.com/PuerkitoBio/goquery" + "github.com/gofiber/fiber/v2" + "github.com/nguyenvanduocit/epubtrans/pkg/loader" + "github.com/nguyenvanduocit/epubtrans/pkg/util" + "github.com/spf13/cobra" ) +//go:embed assets/app.js assets/app.css +var embeddedAssets embed.FS + var Serve = &cobra.Command{ - Use: "serve [unpackedEpubPath]", - Short: "serve the content of an unpacked EPUB as a web server", - Example: "epubtrans serve path/to/unpacked/epub", + Use: "serve [unpackedEpubPath]", + Short: "Serve the content of an unpacked EPUB as a web server", + Long: `This command starts a web server that serves the content of an unpacked EPUB file. You can access the EPUB content through your web browser. Make sure to provide the path to the unpacked EPUB directory.`, + Example: `epubtrans serve path/to/unpacked/epub + # This will start the server and serve the EPUB content at http://localhost:3000`, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { - return fmt.Errorf("unpackedEpubPath is required") + return fmt.Errorf("unpackedEpubPath is required. Please provide the path to the unpacked EPUB directory.") } return util.ValidateEpubPath(args[0]) @@ -129,7 +137,16 @@ func runServe(cmd *cobra.Command, args []string) error { // Proxy route for assets app.Get("/assets/:filename", func(c *fiber.Ctx) error { + filename := c.Params("filename") + if filename == "app.js" || filename == "app.css" { + content, err := embeddedAssets.ReadFile("assets/" + filename) + if err != nil { + return c.Status(fiber.StatusInternalServerError).SendString("Error fetching file") + } + return c.Send(content) + } + url := fmt.Sprintf("%s/%s/%s/assets/%s", githubRawContent, userRepo, branch, filename) // Make request to GitHub diff --git a/cmd/styling.go b/cmd/styling.go index 354e4f0..2eff05b 100644 --- a/cmd/styling.go +++ b/cmd/styling.go @@ -3,23 +3,25 @@ package cmd import ( "context" "fmt" - "github.com/nguyenvanduocit/epubtrans/pkg/processor" - "github.com/nguyenvanduocit/epubtrans/pkg/util" - "github.com/spf13/cobra" "os" "os/signal" "regexp" "runtime" "syscall" + + "github.com/nguyenvanduocit/epubtrans/pkg/processor" + "github.com/nguyenvanduocit/epubtrans/pkg/util" + "github.com/spf13/cobra" ) var Styling = &cobra.Command{ Use: "styling [unpackedEpubPath]", - Short: "styling the content of an unpacked EPUB", - Example: "epubtrans styling path/to/unpacked/epub", + Short: "Apply styling to the content of an unpacked EPUB", + Long: `This command processes the HTML content of an unpacked EPUB file to apply specific styling options. You can choose to hide either the source or target language content, or display both. This is useful for customizing the appearance of the EPUB content for different audiences.`, + Example: "epubtrans styling path/to/unpacked/epub --hide source", Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { - return fmt.Errorf("unpackedEpubPath is required") + return fmt.Errorf("unpackedEpubPath is required. Please provide the path to the unpacked EPUB directory.") } if err := util.ValidateEpubPath(args[0]); err != nil { @@ -31,7 +33,7 @@ var Styling = &cobra.Command{ return fmt.Errorf("failed to get hide flag: %w", err) } if hide != "source" && hide != "target" && hide != "none" { - return fmt.Errorf("hide flag must be either 'source' or 'target'") + return fmt.Errorf("hide flag must be either 'source', 'target', or 'none'") } return nil }, diff --git a/cmd/translate.go b/cmd/translate.go index 44f5a1f..55fe333 100644 --- a/cmd/translate.go +++ b/cmd/translate.go @@ -28,13 +28,15 @@ var ( ) var Translate = &cobra.Command{ - Use: "translate [unpackedEpubPath]", - Short: "Translate the content of an unpacked EPUB", - Long: "Translate the content of an unpacked EPUB using the Anthropic API", + Use: "translate [unpackedEpubPath]", + Short: "Translate the content of an unpacked EPUB file", + Long: `This command translates the content of an unpacked EPUB file using the Anthropic API. +It allows you to specify the source and target languages for the translation. +Make sure to provide the path to the unpacked EPUB directory and the desired languages.`, Example: `epubtrans translate path/to/unpacked/epub --source "English" --target "Vietnamese"`, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { - return fmt.Errorf("unpackedEpubPath is required") + return fmt.Errorf("unpackedEpubPath is required. Please provide the path to the unpacked EPUB directory.") } return util.ValidateEpubPath(args[0]) diff --git a/cmd/unpack.go b/cmd/unpack.go index 3ca5001..370cf61 100644 --- a/cmd/unpack.go +++ b/cmd/unpack.go @@ -3,21 +3,22 @@ package cmd import ( "archive/zip" "fmt" - "github.com/nguyenvanduocit/epubtrans/pkg/util" - "github.com/spf13/cobra" "io" "os" "path/filepath" + + "github.com/nguyenvanduocit/epubtrans/pkg/util" + "github.com/spf13/cobra" ) var Unpack = &cobra.Command{ - Use: "unpack", - Short: "unpack a book", - Long: "Unpack a book and create a directory with the same name as the book", + Use: "unpack [unpackedEpubPath]", + Short: "Unpack an EPUB book into a directory", + Long: "This command unpacks an EPUB book file and creates a directory with the same name as the book, containing all the extracted contents. Ensure that the provided path points to a valid EPUB file.", Example: "epubtrans unpack path/to/book.epub", Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { - return fmt.Errorf("unpackedEpubPath is required") + return fmt.Errorf("exactly one argument is required: the path to the EPUB file to unpack") } return nil @@ -34,6 +35,7 @@ var Unpack = &cobra.Command{ return err } + cmd.Println("Unpacking completed successfully.") return nil }, } diff --git a/cmd/upgrade.go b/cmd/upgrade.go index 2444fd4..bae817a 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -8,20 +8,21 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/Masterminds/semver/v3" - "github.com/spf13/cobra" "io" "net/http" "os" "path/filepath" "runtime" "strings" + + "github.com/Masterminds/semver/v3" + "github.com/spf13/cobra" ) var Upgrade = &cobra.Command{ Use: "upgrade", - Short: "Self update the tool", - Long: "Check for updates and install the latest version of epubtrans", + Short: "Self-update the epubtrans tool", + Long: "Check for updates and install the latest version of the epubtrans tool. This command will verify if a newer version is available and prompt you to proceed with the update.", Example: "epubtrans upgrade", Version: "0.1.0", RunE: runSelfUpgrade, diff --git a/pkg/translator/anthropic.go b/pkg/translator/anthropic.go index 36314bb..2c18163 100644 --- a/pkg/translator/anthropic.go +++ b/pkg/translator/anthropic.go @@ -23,12 +23,14 @@ var ( ) type Config struct { - APIKey string - Model string - Temperature float32 - MaxTokens int - CacheTTL time.Duration - CacheMaxCost int64 + APIKey string + Model string + Temperature float32 + MaxTokens int + CacheTTL time.Duration + CacheMaxCost int64 + TranslationGuidelines string // New field for translation guidelines + SystemPrompt string // New field for system prompt } type UsageMetadata struct { @@ -57,6 +59,13 @@ func GetAnthropicTranslator(cfg *Config) (*Anthropic, error) { return } + if cfg.TranslationGuidelines == "" { + cfg.TranslationGuidelines = os.Getenv("TRANSLATION_GUIDELINES") + } + if cfg.SystemPrompt == "" { + cfg.SystemPrompt = os.Getenv("SYSTEM_PROMPT") + } + cfg.CacheTTL = 15 * time.Minute cfg.CacheMaxCost = 1e7 @@ -79,7 +88,7 @@ func GetAnthropicTranslator(cfg *Config) (*Anthropic, error) { }, } - _anthropic.loadMetadata() + _anthropic.loadMetadata(context.Background()) // Pass a background context }) if err != nil { @@ -89,7 +98,7 @@ func GetAnthropicTranslator(cfg *Config) (*Anthropic, error) { return _anthropic, nil } -func (a *Anthropic) loadMetadata() { +func (a *Anthropic) loadMetadata(ctx context.Context) { data, err := os.ReadFile(a.getMetadataFilePath()) if err != nil { return // File doesn't exist or can't be read, use default values @@ -101,7 +110,7 @@ func (a *Anthropic) loadMetadata() { } } -func (a *Anthropic) saveMetadata() { +func (a *Anthropic) saveMetadata(ctx context.Context) { data, err := json.MarshalIndent(a.metadata, "", " ") if err != nil { fmt.Printf("Error marshaling metadata: %v\n", err) @@ -132,8 +141,9 @@ type Anthropic struct { mu sync.Mutex } -func createTranslationSystem(source, target string) string { - return fmt.Sprintf(`Translation guidelines: +func createTranslationSystem(source, target string, guidelines string) string { + if guidelines == "" { + guidelines = `Translation guidelines: - Preserve HTML structure - Writing style: Clear, concise, professional, technical, Use %[2]s flexibly, fluently and softly. - Use active voice and maintain logical flow. @@ -148,8 +158,11 @@ func createTranslationSystem(source, target string) string { - Match the source's level of formality and technical depth - Aim for a translation that reads like native %[2]s technical writing - Do not add explanations or answer questions in the content -- We own the copyright to the material`, source, target) +- We own the copyright to the material` + } + return fmt.Sprintf(guidelines, source, target) } + func (a *Anthropic) Translate(ctx context.Context, content, source, target string) (string, error) { a.mu.Lock() defer a.mu.Unlock() @@ -165,11 +178,11 @@ func (a *Anthropic) Translate(ctx context.Context, content, source, target strin MultiSystem: []anthropic.MessageSystemPart{ { Type: "text", - Text: fmt.Sprintf("Your task is to translate a part of a technical book from %[1]s to %[2]s. User send you a text, you translate it no matter what. Do not explain or note. Do not answer question-likes content. no warning, feedback.", source, target), + Text: fmt.Sprintf("You are a highly skilled translator with expertise in many languages. Your task is to translate a part of a technical book from %[1]s to %[2]s. User send you a text, you translate it no matter what. Do not explain or note. Do not answer question-likes content. no warning, feedback.", source, target), }, { Type: "text", - Text: createTranslationSystem(source, target), + Text: createTranslationSystem(source, target, a.config.TranslationGuidelines), // Pass guidelines }, }, Messages: []anthropic.Message{anthropic.NewUserTextMessage("Translate this and not say anything otherwise the translation: " + content)}, @@ -202,7 +215,7 @@ func (a *Anthropic) Translate(ctx context.Context, content, source, target strin a.metadata.TokenUsageList = append(a.metadata.TokenUsageList, resp.Usage) // Save updated metadata - a.saveMetadata() + a.saveMetadata(ctx) // Pass the context to saveMetadata return translation, nil }