diff --git a/src/convertor/view.go b/src/convertor/view.go index db26786..3fb68ee 100644 --- a/src/convertor/view.go +++ b/src/convertor/view.go @@ -2,11 +2,14 @@ package convertor import ( "errors" + "ffmpegGui/helper" + "ffmpegGui/localizer" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" + "github.com/nicksnyder/go-i18n/v2/i18n" "image/color" ) @@ -17,7 +20,8 @@ type ViewContract interface { } type View struct { - w fyne.Window + w fyne.Window + localizerService localizer.ServiceContract } type HandleConvertSetting struct { @@ -32,8 +36,11 @@ type enableFormConversionStruct struct { form *widget.Form } -func NewView(w fyne.Window) *View { - return &View{w} +func NewView(w fyne.Window, localizerService localizer.ServiceContract) *View { + return &View{ + w: w, + localizerService: localizerService, + } } func (v View) Main( @@ -51,18 +58,35 @@ func (v View) Main( buttonForSelectedDir, buttonForSelectedDirMessage, pathToSaveDirectory := v.getButtonForSelectingDirectoryForSaving() isOverwriteOutputFiles := false - checkboxOverwriteOutputFiles := widget.NewCheck("Разрешить перезаписать файл", func(b bool) { + checkboxOverwriteOutputFilesTitle := v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "checkboxOverwriteOutputFilesTitle", + }) + checkboxOverwriteOutputFiles := widget.NewCheck(checkboxOverwriteOutputFilesTitle, func(b bool) { isOverwriteOutputFiles = b }) form.Items = []*widget.FormItem{ - {Text: "Файл для ковертации:", Widget: fileVideoForConversion}, - {Widget: fileVideoForConversionMessage}, - {Text: "Папка куда будет сохраняться:", Widget: buttonForSelectedDir}, - {Widget: buttonForSelectedDirMessage}, - {Widget: checkboxOverwriteOutputFiles}, + { + Text: v.localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: "fileVideoForConversionTitle"}), + Widget: fileVideoForConversion, + }, + { + Widget: fileVideoForConversionMessage, + }, + { + Text: v.localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: "buttonForSelectedDirTitle"}), + Widget: buttonForSelectedDir, + }, + { + Widget: buttonForSelectedDirMessage, + }, + { + Widget: checkboxOverwriteOutputFiles, + }, } - form.SubmitText = "Конвертировать" + form.SubmitText = v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "converterVideoFilesSubmitTitle", + }) enableFormConversionStruct := enableFormConversionStruct{ fileVideoForConversion: fileVideoForConversion, @@ -72,7 +96,9 @@ func (v View) Main( form.OnSubmit = func() { if len(*pathToSaveDirectory) == 0 { - showConversionMessage(conversionMessage, errors.New("Не выбрали папку для сохранения!")) + showConversionMessage(conversionMessage, errors.New(v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "errorSelectedFolderSave", + }))) enableFormConversion(enableFormConversionStruct) return } @@ -96,7 +122,10 @@ func (v View) Main( enableFormConversion(enableFormConversionStruct) } - v.w.SetContent(widget.NewCard("Конвертор видео файлов в mp4", "", container.NewVBox(form, conversionMessage, progress))) + converterVideoFilesTitle := v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "converterVideoFilesTitle", + }) + v.w.SetContent(widget.NewCard(converterVideoFilesTitle, "", container.NewVBox(form, conversionMessage, progress))) form.Disable() } @@ -107,7 +136,11 @@ func (v View) getButtonFileVideoForConversion(form *widget.Form, progress *widge fileVideoForConversionMessage.TextSize = 16 fileVideoForConversionMessage.TextStyle = fyne.TextStyle{Bold: true} - button := widget.NewButton("выбрать", func() { + buttonTitle := v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "choose", + }) + + button := widget.NewButton(buttonTitle, func() { fileDialog := dialog.NewFileOpen( func(r fyne.URIReadCloser, err error) { if err != nil { @@ -131,6 +164,7 @@ func (v View) getButtonFileVideoForConversion(form *widget.Form, progress *widge progress.Refresh() conversionMessage.Text = "" }, v.w) + helper.FileDialogResize(fileDialog, v.w) fileDialog.Show() }) @@ -145,7 +179,11 @@ func (v View) getButtonForSelectingDirectoryForSaving() (button *widget.Button, path := "" dirPath = &path - button = widget.NewButton("выбрать", func() { + buttonTitle := v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "choose", + }) + + button = widget.NewButton(buttonTitle, func() { fileDialog := dialog.NewFolderOpen( func(r fyne.ListableURI, err error) { if err != nil { @@ -162,6 +200,7 @@ func (v View) getButtonForSelectingDirectoryForSaving() (button *widget.Button, buttonMessage.Text = r.Path() setStringSuccessStyle(buttonMessage) }, v.w) + helper.FileDialogResize(fileDialog, v.w) fileDialog.Show() }) diff --git a/src/error/view.go b/src/error/view.go index df3fc7d..101f0af 100644 --- a/src/error/view.go +++ b/src/error/view.go @@ -1,9 +1,11 @@ package error import ( + "ffmpegGui/localizer" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/widget" + "github.com/nicksnyder/go-i18n/v2/i18n" ) type ViewContract interface { @@ -11,16 +13,54 @@ type ViewContract interface { } type View struct { - w fyne.Window + w fyne.Window + localizerService localizer.ServiceContract } -func NewView(w fyne.Window) *View { - return &View{w} +func NewView(w fyne.Window, localizerService localizer.ServiceContract) *View { + return &View{ + w: w, + localizerService: localizerService, + } } func (v View) PanicError(err error) { - v.w.SetContent(container.NewVBox( - widget.NewLabel("Произошла ошибка!"), - widget.NewLabel("Ошибка: "+err.Error()), + messageHead := v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "error", + }) + + v.w.SetContent(container.NewBorder( + container.NewVBox( + widget.NewLabel(messageHead), + widget.NewLabel(err.Error()), + ), + nil, + nil, + nil, + localizer.LanguageSelectionForm(v.localizerService, func(lang localizer.Lang) { + v.PanicError(err) + }), + )) +} + +func (v View) PanicErrorWriteDirectoryData() { + message := v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "errorDatabase", + }) + messageHead := v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "error", + }) + + v.w.SetContent(container.NewBorder( + container.NewVBox( + widget.NewLabel(messageHead), + widget.NewLabel(message), + ), + nil, + nil, + nil, + localizer.LanguageSelectionForm(v.localizerService, func(lang localizer.Lang) { + v.PanicErrorWriteDirectoryData() + }), )) } diff --git a/src/go.mod b/src/go.mod index 2831b7f..0d1f420 100644 --- a/src/go.mod +++ b/src/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( fyne.io/fyne/v2 v2.4.3 // indirect fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e // indirect + github.com/BurntSushi/toml v1.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fredbi/uri v1.0.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect @@ -21,6 +22,7 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect github.com/mattn/go-sqlite3 v1.14.19 // indirect + github.com/nicksnyder/go-i18n/v2 v2.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect @@ -31,7 +33,7 @@ require ( golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/text v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/sqlite v1.5.4 // indirect gorm.io/gorm v1.25.5 // indirect diff --git a/src/go.sum b/src/go.sum index 0fdad10..695098f 100644 --- a/src/go.sum +++ b/src/go.sum @@ -42,6 +42,8 @@ fyne.io/fyne/v2 v2.4.3/go.mod h1:1h3BKxmQYRJlr2g+RGVxedzr6vLVQ/AJmFWcF9CJnoQ= fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e h1:Hvs+kW2VwCzNToF3FmnIAzmivNgrclwPgoUdVSrjkP8= fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -226,6 +228,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nicksnyder/go-i18n/v2 v2.3.0 h1:2NPsCsNFCVd7i+Su0xYsBrIhS3bE2XMv5gNTft2O+PQ= +github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -477,6 +481,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/src/handler/convertor.go b/src/handler/convertor.go index 8d3d5f5..053ece0 100644 --- a/src/handler/convertor.go +++ b/src/handler/convertor.go @@ -5,11 +5,12 @@ import ( "errors" "ffmpegGui/convertor" "ffmpegGui/helper" + "ffmpegGui/localizer" "ffmpegGui/setting" "fyne.io/fyne/v2/widget" + "github.com/nicksnyder/go-i18n/v2/i18n" "io" "regexp" - "runtime" "strconv" "strings" ) @@ -18,23 +19,35 @@ type ConvertorHandler struct { convertorService convertor.ServiceContract convertorView convertor.ViewContract settingView setting.ViewContract + localizerView localizer.ViewContract settingRepository setting.RepositoryContract + localizerService localizer.ServiceContract } func NewConvertorHandler( convertorService convertor.ServiceContract, convertorView convertor.ViewContract, settingView setting.ViewContract, + localizerView localizer.ViewContract, settingRepository setting.RepositoryContract, + localizerService localizer.ServiceContract, ) *ConvertorHandler { return &ConvertorHandler{ - convertorService, - convertorView, - settingView, - settingRepository, + convertorService: convertorService, + convertorView: convertorView, + settingView: settingView, + localizerView: localizerView, + settingRepository: settingRepository, + localizerService: localizerService, } } +func (h ConvertorHandler) LanguageSelection() { + h.localizerView.LanguageSelection(func(lang localizer.Lang) { + h.GetConvertor() + }) +} + func (h ConvertorHandler) GetConvertor() { if h.checkingFFPathUtilities() == true { h.convertorView.Main(h.runConvert) @@ -48,7 +61,7 @@ func (h ConvertorHandler) runConvert(setting convertor.HandleConvertSetting, pro if err != nil { return err } - progress := NewProgress(totalDuration, progressbar) + progress := NewProgress(totalDuration, progressbar, h.localizerService) return h.convertorService.RunConvert( convertor.ConvertSetting{ @@ -69,12 +82,7 @@ func (h ConvertorHandler) checkingFFPathUtilities() bool { return true } - var pathsToFF []convertor.FFPathUtilities - if runtime.GOOS == "windows" { - pathsToFF = []convertor.FFPathUtilities{{"ffmpeg\\bin\\ffmpeg.exe", "ffmpeg\\bin\\ffprobe.exe"}} - } else { - pathsToFF = []convertor.FFPathUtilities{{"ffmpeg/bin/ffmpeg", "ffmpeg/bin/ffprobe"}, {"ffmpeg", "ffprobe"}} - } + pathsToFF := getPathsToFF() for _, item := range pathsToFF { ffmpegChecking, _ := h.convertorService.ChangeFFmpegPath(item.FFmpeg) if ffmpegChecking == false { @@ -97,12 +105,18 @@ func (h ConvertorHandler) checkingFFPathUtilities() bool { func (h ConvertorHandler) saveSettingFFPath(ffmpegPath string, ffprobePath string) error { ffmpegChecking, _ := h.convertorService.ChangeFFmpegPath(ffmpegPath) if ffmpegChecking == false { - return errors.New("это не FFmpeg") + errorText := h.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "errorFFmpeg", + }) + return errors.New(errorText) } ffprobeChecking, _ := h.convertorService.ChangeFFprobePath(ffprobePath) if ffprobeChecking == false { - return errors.New("это не FFprobe") + errorText := h.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "errorFFprobe", + }) + return errors.New(errorText) } ffmpegEntity := setting.Setting{Code: "ffmpeg", Value: ffmpegPath} @@ -130,16 +144,18 @@ func (h ConvertorHandler) checkingFFPath() bool { } type progress struct { - totalDuration float64 - progressbar *widget.ProgressBar - protocol string + totalDuration float64 + progressbar *widget.ProgressBar + protocol string + localizerService localizer.ServiceContract } -func NewProgress(totalDuration float64, progressbar *widget.ProgressBar) progress { +func NewProgress(totalDuration float64, progressbar *widget.ProgressBar, localizerService localizer.ServiceContract) progress { return progress{ - totalDuration: totalDuration, - progressbar: progressbar, - protocol: "pipe:", + totalDuration: totalDuration, + progressbar: progressbar, + protocol: "pipe:", + localizerService: localizerService, } } @@ -169,10 +185,15 @@ func (p progress) Run(stdOut io.ReadCloser, stdErr io.ReadCloser) error { scannerOut := bufio.NewScanner(stdOut) for scannerOut.Scan() { - if isProcessCompleted != true { - isProcessCompleted = true - } data := scannerOut.Text() + + if strings.Contains(data, "progress=end") { + p.progressbar.Value = p.totalDuration + p.progressbar.Refresh() + isProcessCompleted = true + break + } + re := regexp.MustCompile(`frame=(\d+)`) a := re.FindAllStringSubmatch(data, -1) @@ -183,11 +204,6 @@ func (p progress) Run(stdOut io.ReadCloser, stdErr io.ReadCloser) error { } progress = float64(c) } - if strings.Contains(data, "progress=end") { - p.progressbar.Value = p.totalDuration - p.progressbar.Refresh() - isProcessCompleted = true - } if p.progressbar.Value != progress { p.progressbar.Value = progress p.progressbar.Refresh() @@ -196,7 +212,9 @@ func (p progress) Run(stdOut io.ReadCloser, stdErr io.ReadCloser) error { if isProcessCompleted == false { if len(errorText) == 0 { - errorText = "не смогли отконвертировать видео" + errorText = p.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "errorConverter", + }) } return errors.New(errorText) } diff --git a/src/handler/convertor_anyos.go b/src/handler/convertor_anyos.go new file mode 100644 index 0000000..66d5fb6 --- /dev/null +++ b/src/handler/convertor_anyos.go @@ -0,0 +1,10 @@ +//go:build !windows +// +build !windows + +package handler + +import "ffmpegGui/convertor" + +func getPathsToFF() []convertor.FFPathUtilities { + return []convertor.FFPathUtilities{{"ffmpeg/bin/ffmpeg", "ffmpeg/bin/ffprobe"}, {"ffmpeg", "ffprobe"}} +} diff --git a/src/handler/convertor_windows.go b/src/handler/convertor_windows.go new file mode 100644 index 0000000..aee0790 --- /dev/null +++ b/src/handler/convertor_windows.go @@ -0,0 +1,10 @@ +//go:build windows +// +build windows + +package handler + +import "ffmpegGui/convertor" + +func getPathsToFF() []convertor.FFPathUtilities { + return []convertor.FFPathUtilities{{"ffmpeg\\bin\\ffmpeg.exe", "ffmpeg\\bin\\ffprobe.exe"}} +} diff --git a/src/helper/helper.go b/src/helper/helper.go index b98d666..682704c 100644 --- a/src/helper/helper.go +++ b/src/helper/helper.go @@ -1,10 +1,11 @@ package helper -import "runtime" +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/dialog" +) -func PathSeparator() string { - if runtime.GOOS == "windows" { - return "\\" - } - return "/" +func FileDialogResize(fileDialog *dialog.FileDialog, w fyne.Window) { + contentSize := w.Content().Size() + fileDialog.Resize(fyne.Size{Width: contentSize.Width - 50, Height: contentSize.Height - 50}) } diff --git a/src/helper/path_separator.go b/src/helper/path_separator.go new file mode 100644 index 0000000..905b935 --- /dev/null +++ b/src/helper/path_separator.go @@ -0,0 +1,8 @@ +//go:build !windows +// +build !windows + +package helper + +func PathSeparator() string { + return "/" +} diff --git a/src/helper/path_separator_window.go b/src/helper/path_separator_window.go new file mode 100644 index 0000000..433e550 --- /dev/null +++ b/src/helper/path_separator_window.go @@ -0,0 +1,8 @@ +//go:build windows +// +build windows + +package helper + +func PathSeparator() string { + return "\\" +} diff --git a/src/icon.png b/src/icon.png new file mode 100644 index 0000000..067ee50 Binary files /dev/null and b/src/icon.png differ diff --git a/src/languages/active.en.toml b/src/languages/active.en.toml new file mode 100644 index 0000000..5fa57a1 --- /dev/null +++ b/src/languages/active.en.toml @@ -0,0 +1,75 @@ +[buttonForSelectedDirTitle] +hash = "sha1-52b13f1b13e82d22e8c4102332db5d4ec551247b" +other = "Folder where it will be saved:" + +[checkboxOverwriteOutputFilesTitle] +hash = "sha1-5860124bb781e7ef680f573fa93977e96328d4e7" +other = "Allow file to be overwritten" + +[choose] +hash = "sha1-f60bb5f761024d973834b5e9d25ceebce2c85f94" +other = "choose" + +[converterVideoFilesSubmitTitle] +hash = "sha1-7ac460f3c24c9952082f2db6e4d62f752598709c" +other = "Convert" + +[converterVideoFilesTitle] +hash = "sha1-4d972809e4c7f9c9ff2c110a126bbc183c9429ce" +other = "Converter video files to mp4" + +[error] +hash = "sha1-a7df8f8b5d754f226ac4cb320577fe692b33e483" +other = "An error has occurred!" + +[errorConverter] +hash = "sha1-55ebddceddb8b044e33cc3893ec2eba7bbd9fcf9" +other = "Couldn't convert video" + +[errorDatabase] +hash = "sha1-531abc3f0d12727e542df6e5a22de91098380fc1" +other = "could not create file 'database' in folder 'data'" + +[errorFFmpeg] +hash = "sha1-ccf0b95c0d1b392dc215258d917eb4e5d0b88ed0" +other = "this is not FFmpeg" + +[errorFFprobe] +hash = "sha1-86d1b0b4c4ccd6a4f71e758fc67ce11aff4ba9b8" +other = "this is not FFprobe" + +[errorSelectedFolderSave] +hash = "sha1-83da899677cdc90e4344e3b94ee03c46b51bee4c" +other = "You haven't selected a folder to save!" + +[fileVideoForConversionTitle] +hash = "sha1-5e727d4a2ff3f21080e51e81641595b2e668f3be" +other = "File for conversion:" + +[languageSelectionFormHead] +hash = "sha1-0ff5fa82cf684112660128cba1711297acf11003" +other = "Switch language" + +[languageSelectionHead] +hash = "sha1-daf1108fc10d3b1a908288d611f749b3cc651e4b" +other = "Choose language" + +[pathToFfmpeg] +hash = "sha1-2eba439f365640ff77e0ed6a7486fcb662573850" +other = "Path to ffmpeg:" + +[pathToFfprobe] +hash = "sha1-02ad53337801906f8ebfee4616100dd9f43eabd7" +other = "Path to ffprobe:" + +[save] +hash = "sha1-4864057d626a868fa60f999bed3191d61d045ddc" +other = "Save" + +[selectFFPathTitle] +hash = "sha1-95581446a28d968ff1a027c623159a7eb08654cf" +other = "Specify the path to FFmpeg and FFprobe" + +[titleDownloadLink] +hash = "sha1-92df86371f6c3a06ca1e4754f113142776a32d49" +other = "You can download it from here" diff --git a/src/languages/active.kk.toml b/src/languages/active.kk.toml new file mode 100644 index 0000000..f1f713e --- /dev/null +++ b/src/languages/active.kk.toml @@ -0,0 +1,75 @@ +[buttonForSelectedDirTitle] +hash = "sha1-52b13f1b13e82d22e8c4102332db5d4ec551247b" +other = "Файлды сақтауға арналған каталог:" + +[checkboxOverwriteOutputFilesTitle] +hash = "sha1-5860124bb781e7ef680f573fa93977e96328d4e7" +other = "Файлды қайта жазуға рұқсат беріңіз" + +[choose] +hash = "sha1-f60bb5f761024d973834b5e9d25ceebce2c85f94" +other = "таңдау" + +[converterVideoFilesSubmitTitle] +hash = "sha1-7ac460f3c24c9952082f2db6e4d62f752598709c" +other = "Файлды түрлендіру" + +[converterVideoFilesTitle] +hash = "sha1-4d972809e4c7f9c9ff2c110a126bbc183c9429ce" +other = "Бейне файлдарын mp4 форматына түрлендіру" + +[error] +hash = "sha1-a7df8f8b5d754f226ac4cb320577fe692b33e483" +other = "Қате орын алды!" + +[errorConverter] +hash = "sha1-55ebddceddb8b044e33cc3893ec2eba7bbd9fcf9" +other = "Бейнені түрлендіру мүмкін болмады" + +[errorDatabase] +hash = "sha1-531abc3f0d12727e542df6e5a22de91098380fc1" +other = "'data' қалтасында 'database' файлын жасау мүмкін болмады" + +[errorFFmpeg] +hash = "sha1-ccf0b95c0d1b392dc215258d917eb4e5d0b88ed0" +other = "бұл FFmpeg емес" + +[errorFFprobe] +hash = "sha1-86d1b0b4c4ccd6a4f71e758fc67ce11aff4ba9b8" +other = "бұл FFprobe емес" + +[errorSelectedFolderSave] +hash = "sha1-83da899677cdc90e4344e3b94ee03c46b51bee4c" +other = "Сіз сақталатын қалтаны таңдамадыңыз!" + +[fileVideoForConversionTitle] +hash = "sha1-5e727d4a2ff3f21080e51e81641595b2e668f3be" +other = "Түрлендіруге арналған файл:" + +[languageSelectionFormHead] +hash = "sha1-0ff5fa82cf684112660128cba1711297acf11003" +other = "Тілді ауыстыру" + +[languageSelectionHead] +hash = "sha1-daf1108fc10d3b1a908288d611f749b3cc651e4b" +other = "Тілді таңдаңыз" + +[pathToFfmpeg] +hash = "sha1-2eba439f365640ff77e0ed6a7486fcb662573850" +other = "ffmpeg жол:" + +[pathToFfprobe] +hash = "sha1-02ad53337801906f8ebfee4616100dd9f43eabd7" +other = "ffprobe жол:" + +[save] +hash = "sha1-4864057d626a868fa60f999bed3191d61d045ddc" +other = "Сақтау" + +[selectFFPathTitle] +hash = "sha1-95581446a28d968ff1a027c623159a7eb08654cf" +other = "FFmpeg және FFprobe жолын көрсетіңіз" + +[titleDownloadLink] +hash = "sha1-92df86371f6c3a06ca1e4754f113142776a32d49" +other = "Сіз оны осы жерден жүктей аласыз" diff --git a/src/languages/active.ru.toml b/src/languages/active.ru.toml new file mode 100644 index 0000000..b4185d9 --- /dev/null +++ b/src/languages/active.ru.toml @@ -0,0 +1,19 @@ +buttonForSelectedDirTitle = "Папка куда будет сохраняться:" +checkboxOverwriteOutputFilesTitle = "Разрешить перезаписать файл" +choose = "выбрать" +converterVideoFilesSubmitTitle = "Конвертировать" +converterVideoFilesTitle = "Конвертор видео файлов в mp4" +error = "Произошла ошибка!" +errorConverter = "не смогли отконвертировать видео" +errorDatabase = "не смогли создать файл 'database' в папке 'data'" +errorFFmpeg = "это не FFmpeg" +errorFFprobe = "это не FFprobe" +errorSelectedFolderSave = "Не выбрали папку для сохранения!" +fileVideoForConversionTitle = "Файл для ковертации:" +languageSelectionFormHead = "Переключить язык" +languageSelectionHead = "Выберите язык" +pathToFfmpeg = "Путь к ffmpeg:" +pathToFfprobe = "Путь к ffprobe:" +save = "Сохранить" +selectFFPathTitle = "Укажите путь к FFmpeg и к FFprobe" +titleDownloadLink = "Скачать можно от сюда" diff --git a/src/languages/translate.en.toml b/src/languages/translate.en.toml new file mode 100644 index 0000000..e69de29 diff --git a/src/languages/translate.kk.toml b/src/languages/translate.kk.toml new file mode 100644 index 0000000..e69de29 diff --git a/src/localizer/service.go b/src/localizer/service.go new file mode 100644 index 0000000..129c31b --- /dev/null +++ b/src/localizer/service.go @@ -0,0 +1,128 @@ +package localizer + +import ( + "github.com/BurntSushi/toml" + "github.com/nicksnyder/go-i18n/v2/i18n" + "golang.org/x/text/cases" + "golang.org/x/text/language" + "golang.org/x/text/language/display" + "path/filepath" + "sort" +) + +type ServiceContract interface { + GetLanguages() []Lang + GetMessage(localizeConfig *i18n.LocalizeConfig) string + SetCurrentLanguage(lang Lang) error + GetCurrentLanguage() *CurrentLanguage +} + +type Lang struct { + Code string + Title string +} + +type CurrentLanguage struct { + Lang Lang + localizer *i18n.Localizer + localizerDefault *i18n.Localizer +} + +type Service struct { + bundle *i18n.Bundle + languages []Lang + currentLanguage *CurrentLanguage +} + +func NewService(directory string, languageDefault language.Tag) (*Service, error) { + bundle := i18n.NewBundle(languageDefault) + bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) + + languages, err := initLanguages(directory, bundle) + if err != nil { + return nil, err + } + + localizerDefault := i18n.NewLocalizer(bundle, languageDefault.String()) + + return &Service{ + bundle: bundle, + languages: languages, + currentLanguage: &CurrentLanguage{ + Lang: Lang{ + Code: languageDefault.String(), + Title: cases.Title(languageDefault).String(display.Self.Name(languageDefault)), + }, + localizer: localizerDefault, + localizerDefault: localizerDefault, + }, + }, nil +} + +func initLanguages(directory string, bundle *i18n.Bundle) ([]Lang, error) { + var languages []Lang + + files, err := filepath.Glob(directory + "/active.*.toml") + if err != nil { + return nil, err + } + for _, file := range files { + language, err := bundle.LoadMessageFile(file) + if err != nil { + return nil, err + } + title := cases.Title(language.Tag).String(display.Self.Name(language.Tag)) + languages = append(languages, Lang{Code: language.Tag.String(), Title: title}) + } + + sort.Sort(languagesSort(languages)) + + return languages, nil +} + +func (s Service) GetLanguages() []Lang { + return s.languages +} + +func (s Service) GetMessage(localizeConfig *i18n.LocalizeConfig) string { + message, err := s.GetCurrentLanguage().localizer.Localize(localizeConfig) + if err != nil { + message, err = s.GetCurrentLanguage().localizerDefault.Localize(localizeConfig) + if err != nil { + return err.Error() + } + } + return message +} + +func (s Service) SetCurrentLanguage(lang Lang) error { + s.currentLanguage.Lang = lang + s.currentLanguage.localizer = i18n.NewLocalizer(s.bundle, lang.Code) + return nil +} + +func (s Service) GetCurrentLanguage() *CurrentLanguage { + return s.currentLanguage +} + +type languagesSort []Lang + +func (l languagesSort) Len() int { return len(l) } +func (l languagesSort) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l languagesSort) Less(i, j int) bool { + return languagePriority(l[i]) < languagePriority(l[j]) +} +func languagePriority(l Lang) int { + priority := 0 + + switch l.Code { + case "ru": + priority = -3 + case "kk": + priority = -2 + case "en": + priority = -1 + } + + return priority +} diff --git a/src/localizer/view.go b/src/localizer/view.go new file mode 100644 index 0000000..351d9c9 --- /dev/null +++ b/src/localizer/view.go @@ -0,0 +1,76 @@ +package localizer + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" + "github.com/nicksnyder/go-i18n/v2/i18n" +) + +type ViewContract interface { + LanguageSelection(funcSelected func(lang Lang)) +} + +type View struct { + w fyne.Window + localizerService ServiceContract +} + +func NewView(w fyne.Window, localizerService ServiceContract) *View { + return &View{ + w: w, + localizerService: localizerService, + } +} + +func (v View) LanguageSelection(funcSelected func(lang Lang)) { + languages := v.localizerService.GetLanguages() + listView := widget.NewList( + func() int { + return len(languages) + }, + func() fyne.CanvasObject { + return widget.NewLabel("template") + }, + func(i widget.ListItemID, o fyne.CanvasObject) { + block := o.(*widget.Label) + block.SetText(languages[i].Title) + }) + listView.OnSelected = func(id widget.ListItemID) { + _ = v.localizerService.SetCurrentLanguage(languages[id]) + funcSelected(languages[id]) + } + + messageHead := v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "languageSelectionHead", + }) + + v.w.SetContent(widget.NewCard(messageHead, "", listView)) +} + +func LanguageSelectionForm(localizerService ServiceContract, funcSelected func(lang Lang)) fyne.CanvasObject { + languages := localizerService.GetLanguages() + currentLanguage := localizerService.GetCurrentLanguage() + listView := widget.NewList( + func() int { + return len(languages) + }, + func() fyne.CanvasObject { + return widget.NewLabel("template") + }, + func(i widget.ListItemID, o fyne.CanvasObject) { + block := o.(*widget.Label) + block.SetText(languages[i].Title) + if languages[i].Code == currentLanguage.Lang.Code { + block.TextStyle = fyne.TextStyle{Bold: true} + } + }) + listView.OnSelected = func(id widget.ListItemID) { + _ = localizerService.SetCurrentLanguage(languages[id]) + funcSelected(languages[id]) + } + + messageHead := localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "languageSelectionFormHead", + }) + return widget.NewCard(messageHead, "", listView) +} diff --git a/src/main.go b/src/main.go index 46aec63..47cf9ed 100644 --- a/src/main.go +++ b/src/main.go @@ -3,30 +3,44 @@ package main import ( "errors" "ffmpegGui/convertor" - myError "ffmpegGui/error" + error2 "ffmpegGui/error" "ffmpegGui/handler" + "ffmpegGui/localizer" "ffmpegGui/migration" "ffmpegGui/setting" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" _ "github.com/mattn/go-sqlite3" + "golang.org/x/text/language" "gorm.io/driver/sqlite" "gorm.io/gorm" "os" ) -//const appVersion string = "0.1.1" +//const appVersion string = "0.2.0" func main() { a := app.New() + iconResource, err := fyne.LoadResourceFromPath("icon.png") + if err == nil { + a.SetIcon(iconResource) + } w := a.NewWindow("GUI FFMpeg!") w.Resize(fyne.Size{Width: 800, Height: 600}) w.CenterOnScreen() - errorView := myError.NewView(w) + localizerService, err := localizer.NewService("languages", language.Russian) + if err != nil { + panicErrorLang(w, err) + w.ShowAndRun() + return + } + errorView := error2.NewView(w, localizerService) if canCreateFile("data/database") != true { - errorView.PanicError(errors.New("не смогли создать файл 'database' в папке 'data'")) + errorView.PanicErrorWriteDirectoryData() w.ShowAndRun() return } @@ -63,13 +77,14 @@ func main() { ffPathUtilities := convertor.FFPathUtilities{FFmpeg: pathFFmpeg, FFprobe: pathFFprobe} - convertorView := convertor.NewView(w) - settingView := setting.NewView(w) + localizerView := localizer.NewView(w, localizerService) + convertorView := convertor.NewView(w, localizerService) + settingView := setting.NewView(w, localizerService) convertorService := convertor.NewService(ffPathUtilities) defer appCloseWithConvert(convertorService) - mainHandler := handler.NewConvertorHandler(convertorService, convertorView, settingView, settingRepository) + mainHandler := handler.NewConvertorHandler(convertorService, convertorView, settingView, localizerView, settingRepository, localizerService) - mainHandler.GetConvertor() + mainHandler.LanguageSelection() w.ShowAndRun() } @@ -95,3 +110,10 @@ func canCreateFile(path string) bool { _ = file.Close() return true } + +func panicErrorLang(w fyne.Window, err error) { + w.SetContent(container.NewVBox( + widget.NewLabel("Произошла ошибка!"), + widget.NewLabel("произошла ошибка при получении языковых переводах. \n\r"+err.Error()), + )) +} diff --git a/src/setting/view.go b/src/setting/view.go index 7a85e0d..397972f 100644 --- a/src/setting/view.go +++ b/src/setting/view.go @@ -1,11 +1,14 @@ package setting import ( + "ffmpegGui/helper" + "ffmpegGui/localizer" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" + "github.com/nicksnyder/go-i18n/v2/i18n" "image/color" "net/url" ) @@ -15,11 +18,15 @@ type ViewContract interface { } type View struct { - w fyne.Window + w fyne.Window + localizerService localizer.ServiceContract } -func NewView(w fyne.Window) *View { - return &View{w} +func NewView(w fyne.Window, localizerService localizer.ServiceContract) *View { + return &View{ + w: w, + localizerService: localizerService, + } } func (v View) SelectFFPath(save func(ffmpegPath string, ffprobePath string) error) { @@ -38,14 +45,37 @@ func (v View) SelectFFPath(save func(ffmpegPath string, ffprobePath string) erro form := &widget.Form{ Items: []*widget.FormItem{ - {Text: "Скачать можно от сюда", Widget: link}, - {Text: "Путь к ffmpeg:", Widget: buttonFFmpeg}, - {Widget: buttonFFmpegMessage}, - {Text: "Путь к ffprobe:", Widget: buttonFFprobe}, - {Widget: buttonFFprobeMessage}, - {Widget: errorMessage}, + { + Text: v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "titleDownloadLink", + }), + Widget: link, + }, + { + Text: v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "pathToFfmpeg", + }), + Widget: buttonFFmpeg, + }, + { + Widget: buttonFFmpegMessage, + }, + { + Text: v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "pathToFfprobe", + }), + Widget: buttonFFprobe, + }, + { + Widget: buttonFFprobeMessage, + }, + { + Widget: errorMessage, + }, }, - SubmitText: "Сохранить", + SubmitText: v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "save", + }), OnSubmit: func() { err := save(string(*ffmpegPath), string(*ffprobePath)) if err != nil { @@ -53,7 +83,10 @@ func (v View) SelectFFPath(save func(ffmpegPath string, ffprobePath string) erro } }, } - v.w.SetContent(widget.NewCard("Укажите путь к FFmpeg и к FFprobe", "", container.NewVBox(form))) + selectFFPathTitle := v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "selectFFPathTitle", + }) + v.w.SetContent(widget.NewCard(selectFFPathTitle, "", container.NewVBox(form))) } func (v View) getButtonSelectFile() (filePath *string, button *widget.Button, buttonMessage *canvas.Text) { @@ -64,7 +97,11 @@ func (v View) getButtonSelectFile() (filePath *string, button *widget.Button, bu buttonMessage.TextSize = 16 buttonMessage.TextStyle = fyne.TextStyle{Bold: true} - button = widget.NewButton("выбрать", func() { + buttonTitle := v.localizerService.GetMessage(&i18n.LocalizeConfig{ + MessageID: "choose", + }) + + button = widget.NewButton(buttonTitle, func() { fileDialog := dialog.NewFileOpen( func(r fyne.URIReadCloser, err error) { if err != nil { @@ -81,6 +118,7 @@ func (v View) getButtonSelectFile() (filePath *string, button *widget.Button, bu buttonMessage.Text = r.URI().Path() setStringSuccessStyle(buttonMessage) }, v.w) + helper.FileDialogResize(fileDialog, v.w) fileDialog.Show() })