Loading go_port/i18n.go +4 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,8 @@ commandes new, projects et cache-clear qui peuvent être lancées aussi en dehor "msg.unknown_title": "inconnu", "msg.untitled": "sans titre", "msg.vocabulary_ignored": "Ce bundle de vocabulaire sera ignoré: %s", "msg.download_progress": "Téléchargement de %s", "msg.download_complete": "Téléchargé %s (%s)", "msg.init_new": "Initialisation d'une nouvelle instance de MaX dans %s", "msg.new_ready": "La nouvelle instance de MaX est prête dans %s", "msg.init_existing": "Initialisation de l'instance de MaX existant dans %s", Loading Loading @@ -183,6 +185,8 @@ which can also be run outside a MaX directory.`, "msg.unknown_title": "unknown", "msg.untitled": "untitled", "msg.vocabulary_ignored": "This vocabulary bundle will be ignored: %s", "msg.download_progress": "Downloading %s", "msg.download_complete": "Downloaded %s (%s)", "msg.init_new": "Initializing a new MaX instance in %s", "msg.new_ready": "The new MaX instance is ready in %s", "msg.init_existing": "Initializing existing MaX instance in %s", Loading go_port/main.go +113 −1 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import ( "strconv" "strings" "sync" "sync/atomic" "time" "github.com/PuerkitoBio/goquery" Loading Loading @@ -1032,7 +1033,17 @@ func cachedDownload(source, destination string) error { if err != nil { return err } if _, err := io.Copy(f, resp.Body); err != nil { writer := io.Writer(f) var progress *downloadProgress if isInteractiveTTY(os.Stderr) { progress = newDownloadProgress(filepath.Base(source), resp.ContentLength) progress.start() defer progress.finish() writer = io.MultiWriter(f, progress) } if _, err := io.Copy(writer, resp.Body); err != nil { f.Close() return err } Loading @@ -1045,6 +1056,107 @@ func cachedDownload(source, destination string) error { return copyFile(cachePath, destination) } type downloadProgress struct { label string totalBytes int64 written atomic.Int64 done chan struct{} wg sync.WaitGroup once sync.Once } func newDownloadProgress(label string, totalBytes int64) *downloadProgress { if strings.TrimSpace(label) == "" { label = "file" } return &downloadProgress{ label: label, totalBytes: totalBytes, done: make(chan struct{}), } } func (p *downloadProgress) Write(b []byte) (int, error) { p.written.Add(int64(len(b))) return len(b), nil } func (p *downloadProgress) start() { p.render("|") p.wg.Add(1) go func() { defer p.wg.Done() frames := []string{"|", "/", "-", "\\"} ticker := time.NewTicker(120 * time.Millisecond) defer ticker.Stop() frameIndex := 1 for { select { case <-p.done: return case <-ticker.C: p.render(frames[frameIndex%len(frames)]) frameIndex++ } } }() } func (p *downloadProgress) finish() { p.once.Do(func() { close(p.done) p.wg.Wait() fmt.Fprintf(os.Stderr, "\r%s\n", T("msg.download_complete", p.label, formatBytes(p.written.Load()))) }) } func (p *downloadProgress) render(frame string) { current := p.written.Load() base := T("msg.download_progress", p.label) if p.totalBytes > 0 { percent := int((float64(current) / float64(p.totalBytes)) * 100) if percent > 100 { percent = 100 } fmt.Fprintf( os.Stderr, "\r%s %s %3d%% (%s/%s)", base, frame, percent, formatBytes(current), formatBytes(p.totalBytes), ) return } fmt.Fprintf(os.Stderr, "\r%s %s %s", base, frame, formatBytes(current)) } func formatBytes(v int64) string { if v < 1024 { return fmt.Sprintf("%d B", v) } units := []string{"KB", "MB", "GB", "TB"} size := float64(v) unit := -1 for size >= 1024 && unit < len(units)-1 { size /= 1024 unit++ } return fmt.Sprintf("%.1f %s", size, units[unit]) } func isInteractiveTTY(f *os.File) bool { if f == nil { return false } info, err := f.Stat() if err != nil { return false } return (info.Mode() & os.ModeCharDevice) != 0 } func ensureJava() (string, error) { if javaBin, err := exec.LookPath("java"); err == nil { return javaBin, nil Loading Loading
go_port/i18n.go +4 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,8 @@ commandes new, projects et cache-clear qui peuvent être lancées aussi en dehor "msg.unknown_title": "inconnu", "msg.untitled": "sans titre", "msg.vocabulary_ignored": "Ce bundle de vocabulaire sera ignoré: %s", "msg.download_progress": "Téléchargement de %s", "msg.download_complete": "Téléchargé %s (%s)", "msg.init_new": "Initialisation d'une nouvelle instance de MaX dans %s", "msg.new_ready": "La nouvelle instance de MaX est prête dans %s", "msg.init_existing": "Initialisation de l'instance de MaX existant dans %s", Loading Loading @@ -183,6 +185,8 @@ which can also be run outside a MaX directory.`, "msg.unknown_title": "unknown", "msg.untitled": "untitled", "msg.vocabulary_ignored": "This vocabulary bundle will be ignored: %s", "msg.download_progress": "Downloading %s", "msg.download_complete": "Downloaded %s (%s)", "msg.init_new": "Initializing a new MaX instance in %s", "msg.new_ready": "The new MaX instance is ready in %s", "msg.init_existing": "Initializing existing MaX instance in %s", Loading
go_port/main.go +113 −1 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import ( "strconv" "strings" "sync" "sync/atomic" "time" "github.com/PuerkitoBio/goquery" Loading Loading @@ -1032,7 +1033,17 @@ func cachedDownload(source, destination string) error { if err != nil { return err } if _, err := io.Copy(f, resp.Body); err != nil { writer := io.Writer(f) var progress *downloadProgress if isInteractiveTTY(os.Stderr) { progress = newDownloadProgress(filepath.Base(source), resp.ContentLength) progress.start() defer progress.finish() writer = io.MultiWriter(f, progress) } if _, err := io.Copy(writer, resp.Body); err != nil { f.Close() return err } Loading @@ -1045,6 +1056,107 @@ func cachedDownload(source, destination string) error { return copyFile(cachePath, destination) } type downloadProgress struct { label string totalBytes int64 written atomic.Int64 done chan struct{} wg sync.WaitGroup once sync.Once } func newDownloadProgress(label string, totalBytes int64) *downloadProgress { if strings.TrimSpace(label) == "" { label = "file" } return &downloadProgress{ label: label, totalBytes: totalBytes, done: make(chan struct{}), } } func (p *downloadProgress) Write(b []byte) (int, error) { p.written.Add(int64(len(b))) return len(b), nil } func (p *downloadProgress) start() { p.render("|") p.wg.Add(1) go func() { defer p.wg.Done() frames := []string{"|", "/", "-", "\\"} ticker := time.NewTicker(120 * time.Millisecond) defer ticker.Stop() frameIndex := 1 for { select { case <-p.done: return case <-ticker.C: p.render(frames[frameIndex%len(frames)]) frameIndex++ } } }() } func (p *downloadProgress) finish() { p.once.Do(func() { close(p.done) p.wg.Wait() fmt.Fprintf(os.Stderr, "\r%s\n", T("msg.download_complete", p.label, formatBytes(p.written.Load()))) }) } func (p *downloadProgress) render(frame string) { current := p.written.Load() base := T("msg.download_progress", p.label) if p.totalBytes > 0 { percent := int((float64(current) / float64(p.totalBytes)) * 100) if percent > 100 { percent = 100 } fmt.Fprintf( os.Stderr, "\r%s %s %3d%% (%s/%s)", base, frame, percent, formatBytes(current), formatBytes(p.totalBytes), ) return } fmt.Fprintf(os.Stderr, "\r%s %s %s", base, frame, formatBytes(current)) } func formatBytes(v int64) string { if v < 1024 { return fmt.Sprintf("%d B", v) } units := []string{"KB", "MB", "GB", "TB"} size := float64(v) unit := -1 for size >= 1024 && unit < len(units)-1 { size /= 1024 unit++ } return fmt.Sprintf("%.1f %s", size, units[unit]) } func isInteractiveTTY(f *os.File) bool { if f == nil { return false } info, err := f.Stat() if err != nil { return false } return (info.Mode() & os.ModeCharDevice) != 0 } func ensureJava() (string, error) { if javaBin, err := exec.LookPath("java"); err == nil { return javaBin, nil Loading