diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92313b628..201abaf0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,17 +17,9 @@ start the front-end flutter project in `debug` mode to run. ## Translation -The internationalization files of Gopeed are located in the `ui/flutter/assets/locales` directory. +The internationalization files of Gopeed are located in the `ui/flutter/lib/i18n/langs` directory. You only need to add the corresponding language file in this directory. - -Generate locales after you edit locales: - - -``` -get generate locales -``` - Please refer to `en_us.dart` for translation. ## flutter development @@ -38,17 +30,4 @@ Turn on build_runner watcher if you want to edit api/models: ``` flutter pub run build_runner watch -``` - - -get-cli commands usages: - -``` - create: - controller: Generate controller - page: Use to generate pages - view: Generate view - generate: - locales: Generate translation file from json files - model: generate Class model from json -``` +``` \ No newline at end of file diff --git a/CONTRIBUTING_ja-JP.md b/CONTRIBUTING_ja-JP.md index 7786c4196..85f27591f 100644 --- a/CONTRIBUTING_ja-JP.md +++ b/CONTRIBUTING_ja-JP.md @@ -17,17 +17,9 @@ ## 翻訳 -Gopeed の国際化ファイルは `ui/flutter/assets/locales` ディレクトリにあります。 +Gopeed の国際化ファイルは `ui/flutter/lib/i18n/langs` ディレクトリにあります。 このディレクトリに対応する言語ファイルを追加するだけでよいです。 - -ロケール編集後にロケールを生成: - - -``` -get generate locales -``` - 翻訳については `en_us.dart` を参照してください。 ## flutter での開発 @@ -39,16 +31,3 @@ api/models を編集したい場合は build_runner watcher をオンにしま ``` flutter pub run build_runner watch ``` - - -get-cli コマンドの使用法: - -``` - create: - controller: Generate controller - page: Use to generate pages - view: Generate view - generate: - locales: Generate translation file from json files - model: generate Class model from json -``` diff --git a/CONTRIBUTING_zh-CN.md b/CONTRIBUTING_zh-CN.md index ecac12d65..1f345c52a 100644 --- a/CONTRIBUTING_zh-CN.md +++ b/CONTRIBUTING_zh-CN.md @@ -14,13 +14,7 @@ flutter 项目即可运行。 ## 翻译 -Gopeed 的国际化文件位于 `ui/flutter/assets/locales` 目录下,只需要在该目录下添加对应的语言文件即可。 - -编辑locales后请运行以下命令: - -``` -get generate locales -``` +Gopeed 的国际化文件位于 `ui/flutter/lib/i18n/langs` 目录下,只需要在该目录下添加对应的语言文件即可。 请注意以 `en_US.json` 为参照进行翻译。 @@ -34,15 +28,3 @@ get generate locales flutter pub run build_runner watch ``` -适用get-cli 命令: - -``` - create: - controller: Generate controller - page: Use to generate pages - view: Generate view - generate: - locales: Generate translation file from json files - model: generate Class model from json -``` - diff --git a/CONTRIBUTING_zh-TW.md b/CONTRIBUTING_zh-TW.md index d67ddae67..7d419f0bc 100644 --- a/CONTRIBUTING_zh-TW.md +++ b/CONTRIBUTING_zh-TW.md @@ -12,13 +12,8 @@ ## 翻譯 -Gopeed 的翻譯文件位於 `ui/flutter/assets/locales` 目錄中,只需要修改或新建翻譯文件即可。 +Gopeed 的翻譯文件位於 `ui/flutter/lib/i18n/langs` 目錄中,只需要修改或新建翻譯文件即可。 -修改locales後請執行一下指令: - -``` -get generate locales -``` 請以 `en_US.json` 作為參照。 @@ -31,16 +26,3 @@ get generate locales ``` flutter pub run build_runner watch ``` - -適用於 get-cli 的指令: - -``` - create: - controller: Generate controller - page: Use to generate pages - view: Generate view - generate: - locales: Generate translation file from json files - model: generate Class model from json -``` - diff --git a/README_zh-CN.md b/README_zh-CN.md index 29129b6c2..383d973bd 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -13,7 +13,7 @@ ## 介绍 -Gopeed(全称 Go Speed),是一款由`Golang`+`Flutter`开发的高速下载器,支持(HTTP、BitTorrent、Magnet)协议下载,并且支持全平台使用。除了基本的下载功能外,Gopeed 还是一款高度可定制化的下载器,支持通过对接[APIs](https://docs.gopeed.com/zh/dev-api.html)或者安装和开发[扩展](https://docs.gopeed.com/zh/dev-extension.html)来实现更多的功能。 +Gopeed(全称 Go Speed),直译过来中文名叫做`够快下载器`(不是狗屁下载器!),是一款由`Golang`+`Flutter`开发的高速下载器,支持(HTTP、BitTorrent、Magnet)协议下载,并且支持全平台使用。除了基本的下载功能外,Gopeed 还是一款高度可定制化的下载器,支持通过对接[APIs](https://docs.gopeed.com/zh/dev-api.html)或者安装和开发[扩展](https://docs.gopeed.com/zh/dev-extension.html)来实现更多的功能。 访问 ✈ [官方网站](https://gopeed.com/zh-CN) | 📖 [开发文档](https://docs.gopeed.com/zh/) diff --git a/cmd/web/flags.go b/cmd/web/flags.go index 52de292f4..326a9c4f4 100644 --- a/cmd/web/flags.go +++ b/cmd/web/flags.go @@ -23,7 +23,7 @@ func parse() *args { cliArgs.Address = flag.String("A", "0.0.0.0", "Bind Address") cliArgs.Port = flag.Int("P", 9999, "Bind Port") cliArgs.Username = flag.String("u", "gopeed", "HTTP Basic Auth Username") - cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Password") + cliArgs.Password = flag.String("p", "", "HTTP Basic Auth Pwd") cliArgs.ApiToken = flag.String("T", "", "API token, that can only be used when basic authentication is enabled.") cliArgs.configPath = flag.String("c", "./config.json", "Config file path") flag.Parse() diff --git a/go.mod b/go.mod index 435f9bde2..7d9bab2e8 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9 github.com/anacrolix/torrent v1.52.5 + github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c github.com/go-git/go-git/v5 v5.8.1 diff --git a/go.sum b/go.sum index ac27e8511..258b079df 100644 --- a/go.sum +++ b/go.sum @@ -8,7 +8,6 @@ crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c/go.mod h1:igAO5JulrQ1Dbd dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= -filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= @@ -34,7 +33,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U= github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k= github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 h1:8V0K09lrGoeT2KRJNOtspA7q+OMxGwQqK/Ug0IiaaRE= @@ -74,7 +72,6 @@ github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg= github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc= github.com/anacrolix/multiless v0.3.0 h1:5Bu0DZncjE4e06b9r1Ap2tUY4Au0NToBP5RpuEngSis= github.com/anacrolix/multiless v0.3.0/go.mod h1:TrCLEZfIDbMVfLoQt5tOoiBS/uq4y8+ojuEVVvTNPX4= -github.com/anacrolix/publicip v0.2.0/go.mod h1:67G1lVkLo8UjdEcJkwScWVTvlJ35OCDsRJoWXl/wi4g= github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg= github.com/anacrolix/stm v0.4.0 h1:tOGvuFwaBjeu1u9X1eIh9TX8OEedEiEQ1se1FjhFnXY= github.com/anacrolix/stm v0.4.0/go.mod h1:GCkwqWoAsP7RfLW+jw+Z0ovrt2OO7wRzcTtFYMYY5t8= @@ -85,7 +82,6 @@ github.com/anacrolix/sync v0.4.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DC github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= -github.com/anacrolix/tagflag v1.3.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= github.com/anacrolix/torrent v1.52.5 h1:jWowdx+EU6zFVfBwmnL0d3H4J6vTFEGOrHI35YdfIT8= github.com/anacrolix/torrent v1.52.5/go.mod h1:CcM8oPMYye5J42cSqJrmUpqwRFgSsJQ1jCEHwygqnqQ= github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 h1:QAVZ3pN/J4/UziniAhJR2OZ9Ox5kOY2053tBbbqUPYA= @@ -95,6 +91,7 @@ github.com/anacrolix/utp v0.1.0/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNa github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= @@ -144,7 +141,6 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= -github.com/elliotchance/orderedmap v1.4.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= @@ -242,7 +238,6 @@ github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -282,7 +277,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= -github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -368,7 +362,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= @@ -404,8 +397,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -419,8 +412,6 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.3.7-0.20231005155708-db45d0d6b1f6 h1:NkHVT1GBVV9hUj1iWglFxAuNlvLHELFn71U2H7aOIog= -go.etcd.io/bbolt v1.3.7-0.20231005155708-db45d0d6b1f6/go.mod h1:tDI2vlr15+D9cyN3odhFrZJi3oJj8HlHaOEFPrUiGSY= go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -537,8 +528,6 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 389543cd9..ded1ea2de 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -1,12 +1,14 @@ package controller import ( + "net/url" "os" "path/filepath" ) type Controller struct { GetConfig func(v any) bool + ProxyUrl *url.URL FileController //ContextDialer() (proxy.Dialer, error) } diff --git a/internal/protocol/bt/fetcher.go b/internal/protocol/bt/fetcher.go index 8e6272473..240e2f087 100644 --- a/internal/protocol/bt/fetcher.go +++ b/internal/protocol/bt/fetcher.go @@ -9,6 +9,7 @@ import ( "github.com/GopeedLab/gopeed/pkg/util" "github.com/anacrolix/torrent" "github.com/anacrolix/torrent/metainfo" + "net/http" "path/filepath" "sync" "sync/atomic" @@ -63,6 +64,9 @@ func (f *Fetcher) initClient() (err error) { cfg := torrent.NewDefaultClientConfig() cfg.ListenPort = f.config.ListenPort + if f.ctl.ProxyUrl != nil { + cfg.HTTPProxy = http.ProxyURL(f.ctl.ProxyUrl) + } cfg.DefaultStorage = newFileOpts(newFileClientOpts{ ClientBaseDir: cfg.DataDir, HandleFileTorrent: func(infoHash metainfo.Hash, ft *fileTorrentImpl) { diff --git a/internal/protocol/bt/fetcher_test.go b/internal/protocol/bt/fetcher_test.go index d5c21ec44..b0bff5485 100644 --- a/internal/protocol/bt/fetcher_test.go +++ b/internal/protocol/bt/fetcher_test.go @@ -8,6 +8,8 @@ import ( "github.com/GopeedLab/gopeed/internal/test" "github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/protocol/bt" + "github.com/GopeedLab/gopeed/pkg/util" + "net/url" "os" "reflect" "testing" @@ -50,7 +52,15 @@ func TestFetcher_Resolve_DataUri_Torrent(t *testing.T) { } func TestFetcher_Config(t *testing.T) { - doResolve(t, buildConfigFetcher()) + doResolve(t, buildConfigFetcher(nil)) +} + +func TestFetcher_ResolveWithProxy(t *testing.T) { + usr, pwd := "admin", "123" + proxyListener := test.StartSocks5Server(usr, pwd) + defer proxyListener.Close() + + doResolve(t, buildConfigFetcher(util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, pwd))) } func doResolve(t *testing.T, fetcher fetcher.Fetcher) { @@ -90,7 +100,7 @@ func buildFetcher() fetcher.Fetcher { return fetcher } -func buildConfigFetcher() fetcher.Fetcher { +func buildConfigFetcher(proxyUrl *url.URL) fetcher.Fetcher { fetcher := new(FetcherBuilder).Build() newController := controller.NewController() mockCfg := config{ @@ -104,6 +114,7 @@ func buildConfigFetcher() fetcher.Fetcher { } return true } + newController.ProxyUrl = proxyUrl fetcher.Setup(newController) return fetcher } diff --git a/internal/protocol/http/fetcher.go b/internal/protocol/http/fetcher.go index 8f8874284..c32b5aaf3 100644 --- a/internal/protocol/http/fetcher.go +++ b/internal/protocol/http/fetcher.go @@ -90,7 +90,7 @@ func (f *Fetcher) Resolve(req *base.Request) error { if err != nil { return err } - client := buildClient() + client := f.buildClient() // send Range request to check whether the server supports breakpoint continuation // just test one byte, Range: bytes=0-0 httpReq.Header.Set(base.HttpHeaderRange, fmt.Sprintf(base.HttpHeaderRangeFormat, 0, 0)) @@ -264,7 +264,7 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) { return err } var ( - client = buildClient() + client = f.buildClient() buf = make([]byte, 8192) maxRetries = 3 ) @@ -430,11 +430,16 @@ func (f *Fetcher) splitChunk() (chunks []*chunk) { return } -func buildClient() *http.Client { +func (f *Fetcher) buildClient() *http.Client { + transport := &http.Transport{} // Cookie handle jar, _ := cookiejar.New(nil) + if f.ctl.ProxyUrl != nil { + transport.Proxy = http.ProxyURL(f.ctl.ProxyUrl) + } return &http.Client{ - Jar: jar, + Transport: transport, + Jar: jar, } } diff --git a/internal/protocol/http/fetcher_test.go b/internal/protocol/http/fetcher_test.go index f244ba106..71543a3d5 100644 --- a/internal/protocol/http/fetcher_test.go +++ b/internal/protocol/http/fetcher_test.go @@ -8,6 +8,7 @@ import ( "github.com/GopeedLab/gopeed/internal/test" "github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/protocol/http" + "github.com/GopeedLab/gopeed/pkg/util" "net" "reflect" "testing" @@ -141,6 +142,15 @@ func TestFetcher_DownloadResume(t *testing.T) { downloadResume(listener, 16, t) } +func TestFetcher_DownloadWithProxy(t *testing.T) { + httpListener := test.StartTestFileServer() + defer httpListener.Close() + proxyListener := test.StartSocks5Server("", "") + defer proxyListener.Close() + + downloadWithProxy(httpListener, proxyListener, t) +} + func TestFetcher_Config(t *testing.T) { listener := test.StartTestFileServer() defer listener.Close() @@ -302,6 +312,26 @@ func downloadResume(listener net.Listener, connections int, t *testing.T) { } } +func downloadWithProxy(httpListener net.Listener, proxyListener net.Listener, t *testing.T) { + fetcher := downloadReady(httpListener, 4, t) + ctl := controller.NewController() + ctl.ProxyUrl = util.BuildProxyUrl("socks5", proxyListener.Addr().String(), "", "") + fetcher.Setup(ctl) + err := fetcher.Start() + if err != nil { + t.Fatal(err) + } + err = fetcher.Wait() + if err != nil { + t.Fatal(err) + } + want := test.FileMd5(test.BuildFile) + got := test.FileMd5(test.DownloadFile) + if want != got { + t.Errorf("Download() got = %v, want %v", got, want) + } +} + func buildFetcher() *Fetcher { fetcher := new(FetcherBuilder).Build() fetcher.Setup(controller.NewController()) diff --git a/internal/test/httptest.go b/internal/test/httptest.go index 8af025ce9..7f592d083 100644 --- a/internal/test/httptest.go +++ b/internal/test/httptest.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/armon/go-socks5" "io" "math/rand" "net" @@ -315,3 +316,24 @@ func ifExistAndRemove(name string) error { } return nil } + +func StartSocks5Server(usr, pwd string) net.Listener { + conf := &socks5.Config{} + if usr != "" && pwd != "" { + conf.Credentials = socks5.StaticCredentials{ + usr: pwd, + } + } + + server, err := socks5.New(conf) + if err != nil { + panic(err) + } + + listener, err := net.Listen("tcp", "127.0.0.1:0") // 你可以根据需要更改监听地址 + if err != nil { + panic(err) + } + go server.Serve(listener) + return listener +} diff --git a/pkg/base/constants.go b/pkg/base/constants.go index 88d13b8a5..cb57b02f5 100644 --- a/pkg/base/constants.go +++ b/pkg/base/constants.go @@ -19,7 +19,7 @@ const ( HttpHeaderContentLength = "Content-Length" HttpHeaderContentRange = "Content-Range" HttpHeaderContentDisposition = "Content-Disposition" - HttpHeaderUserAgent = "User-Agent" + HttpHeaderUserAgent = "Usr-Agent" HttpHeaderRangeFormat = "bytes=%d-%d" ) diff --git a/pkg/download/downloader.go b/pkg/download/downloader.go index 85674ad13..7aedbaf24 100644 --- a/pkg/download/downloader.go +++ b/pkg/download/downloader.go @@ -215,6 +215,7 @@ func (d *Downloader) setupFetcher(fetcher fetcher.Fetcher) { ctl.GetConfig = func(v any) bool { return d.getProtocolConfig(fetcher.Name(), v) } + ctl.ProxyUrl = d.cfg.ProxyUrl() fetcher.Setup(ctl) } diff --git a/pkg/download/downloader_test.go b/pkg/download/downloader_test.go index 3d6eee4b5..4836ae2d0 100644 --- a/pkg/download/downloader_test.go +++ b/pkg/download/downloader_test.go @@ -84,6 +84,82 @@ func TestDownloader_Create(t *testing.T) { } } +func TestDownloader_CreateWithProxy(t *testing.T) { + // No proxy + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { + return nil + }) + // Disable proxy + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { + proxyCfg.Enable = false + return proxyCfg + }) + // Invalid proxy scheme + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { + proxyCfg.Scheme = "" + return proxyCfg + }) + // Invalid proxy host + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { + proxyCfg.Host = "" + return proxyCfg + }) + // Use proxy without auth + doTestDownloaderCreateWithProxy(t, false, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { + return proxyCfg + }) + // Use proxy with auth + doTestDownloaderCreateWithProxy(t, true, func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig { + return proxyCfg + }) +} + +func doTestDownloaderCreateWithProxy(t *testing.T, auth bool, buildProxyConfig func(proxyCfg *DownloaderProxyConfig) *DownloaderProxyConfig) { + httpListener := test.StartTestFileServer() + defer httpListener.Close() + usr, pwd := "", "" + if auth { + usr, pwd = "admin", "123" + } + proxyListener := test.StartSocks5Server(usr, pwd) + defer proxyListener.Close() + + downloader := NewDownloader(nil) + if err := downloader.Setup(); err != nil { + t.Fatal(err) + } + defer downloader.Clear() + downloader.cfg.DownloaderStoreConfig.Proxy = buildProxyConfig(&DownloaderProxyConfig{ + Enable: true, + Scheme: "socks5", + Host: proxyListener.Addr().String(), + Usr: usr, + Pwd: pwd, + }) + + req := &base.Request{ + URL: "http://" + httpListener.Addr().String() + "/" + test.BuildName, + } + rr, err := downloader.Resolve(req) + if err != nil { + t.Fatal(err) + } + want := &base.Resource{ + Size: test.BuildSize, + Range: true, + Files: []*base.FileInfo{ + { + Name: test.BuildName, + Path: "", + Size: test.BuildSize, + }, + }, + } + if !reflect.DeepEqual(want, rr.Res) { + t.Errorf("Resolve() got = %v, want %v", rr.Res, want) + } +} + func TestDownloader_CreateRename(t *testing.T) { listener := test.StartTestFileServer() defer listener.Close() diff --git a/pkg/download/engine/engine.go b/pkg/download/engine/engine.go index f28bbd8dc..903b57f5b 100644 --- a/pkg/download/engine/engine.go +++ b/pkg/download/engine/engine.go @@ -9,7 +9,8 @@ import ( "github.com/GopeedLab/gopeed/pkg/download/engine/inject/xhr" "github.com/dop251/goja" "github.com/dop251/goja_nodejs/eventloop" - "github.com/dop251/goja_nodejs/url" + gojaurl "github.com/dop251/goja_nodejs/url" + "net/url" "time" ) @@ -107,7 +108,14 @@ func (e *Engine) Close() { e.loop.Stop() } -func NewEngine() *Engine { +type Config struct { + ProxyURL *url.URL +} + +func NewEngine(cfg *Config) *Engine { + if cfg == nil { + cfg = &Config{} + } loop := eventloop.NewEventLoop() engine := &Engine{ loop: loop, @@ -116,14 +124,14 @@ func NewEngine() *Engine { engine.Runtime = runtime runtime.SetFieldNameMapper(goja.TagFieldNameMapper("json", true)) vm.Enable(runtime) - url.Enable(runtime) + gojaurl.Enable(runtime) if err := file.Enable(runtime); err != nil { return } if err := formdata.Enable(runtime); err != nil { return } - if err := xhr.Enable(runtime); err != nil { + if err := xhr.Enable(runtime, cfg.ProxyURL); err != nil { return } if _, err := runtime.RunString(polyfillScript); err != nil { @@ -143,7 +151,7 @@ func NewEngine() *Engine { } func Run(script string) (value any, err error) { - engine := NewEngine() + engine := NewEngine(nil) return engine.RunString(script) } diff --git a/pkg/download/engine/engine_test.go b/pkg/download/engine/engine_test.go index 58496cbc0..fbab1a128 100644 --- a/pkg/download/engine/engine_test.go +++ b/pkg/download/engine/engine_test.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/GopeedLab/gopeed/internal/test" "github.com/GopeedLab/gopeed/pkg/download/engine/inject/file" + "github.com/GopeedLab/gopeed/pkg/util" "github.com/dop251/goja" "io" "net" @@ -29,7 +30,7 @@ func TestPolyfill(t *testing.T) { func TestFetch(t *testing.T) { server := startServer() defer server.Close() - engine := NewEngine() + engine := NewEngine(nil) if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", server.Addr().String())); err != nil { t.Fatal(err) } @@ -192,8 +193,40 @@ function testTimeout(){ } } +func TestFetchWithProxy(t *testing.T) { + doTestFetchWithProxy(t, "", "") + doTestFetchWithProxy(t, "admin", "123") +} + +func doTestFetchWithProxy(t *testing.T, usr, pwd string) { + httpListener := startServer() + defer httpListener.Close() + + proxyListener := test.StartSocks5Server(usr, pwd) + defer proxyListener.Close() + + engine := NewEngine(&Config{ProxyURL: util.BuildProxyUrl("socks5", proxyListener.Addr().String(), usr, pwd)}) + + if _, err := engine.RunString(fmt.Sprintf("var host = 'http://%s';", httpListener.Addr().String())); err != nil { + t.Fatal(err) + } + + respCode, err := engine.RunString(` +(async function(){ + const resp = await fetch(host+'/get'); + return resp.status; +})() +`) + if err != nil { + t.Fatal(err) + } + if respCode != int64(200) { + t.Fatalf("fetch with proxy failed, want %d, got %d", 200, respCode) + } +} + func TestVm(t *testing.T) { - engine := NewEngine() + engine := NewEngine(nil) value, err := engine.RunString(` const vm = __gopeed_create_vm() @@ -221,7 +254,7 @@ out } func TestNonStopLoop(t *testing.T) { - engine := NewEngine() + engine := NewEngine(nil) _, err := engine.RunString(` function leak(){ diff --git a/pkg/download/engine/inject/xhr/module.go b/pkg/download/engine/inject/xhr/module.go index f96f27756..8be6d4862 100644 --- a/pkg/download/engine/inject/xhr/module.go +++ b/pkg/download/engine/inject/xhr/module.go @@ -10,6 +10,7 @@ import ( "mime/multipart" "net" "net/http" + "net/url" "time" ) @@ -119,6 +120,7 @@ type XMLHttpRequest struct { requestHeaders map[string]string responseHeaders map[string]string aborted bool + proxyUrl *url.URL Upload *XMLHttpRequestUpload `json:"upload"` Timeout int `json:"timeout"` @@ -200,8 +202,13 @@ func (xhr *XMLHttpRequest) Send(data goja.Value) { for k, v := range xhr.requestHeaders { req.Header.Set(k, v) } + transport := &http.Transport{} + if xhr.proxyUrl != nil { + transport.Proxy = http.ProxyURL(xhr.proxyUrl) + } client := &http.Client{ - Timeout: time.Duration(xhr.Timeout) * time.Millisecond, + Transport: transport, + Timeout: time.Duration(xhr.Timeout) * time.Millisecond, } resp, err := client.Do(req) if err != nil { @@ -306,7 +313,7 @@ func (xhr *XMLHttpRequest) parseData(data goja.Value) any { return data.String() } -func Enable(runtime *goja.Runtime) error { +func Enable(runtime *goja.Runtime, proxyUrl *url.URL) error { progressEvent := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object { if len(call.Arguments) < 1 { inject.ThrowTypeError(runtime, "Failed to construct 'ProgressEvent': 1 argument required, but only 0 present.") @@ -320,6 +327,7 @@ func Enable(runtime *goja.Runtime) error { }) xhr := runtime.ToValue(func(call goja.ConstructorCall) *goja.Object { instance := &XMLHttpRequest{ + proxyUrl: proxyUrl, Upload: &XMLHttpRequestUpload{ EventProp: &EventProp{ eventListeners: make(map[string]func(event *ProgressEvent)), diff --git a/pkg/download/extension.go b/pkg/download/extension.go index 925187676..772da1d88 100644 --- a/pkg/download/extension.go +++ b/pkg/download/extension.go @@ -9,6 +9,7 @@ import ( "github.com/GopeedLab/gopeed/pkg/util" "github.com/dop251/goja" "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/transport" "io" "os" "path" @@ -195,11 +196,19 @@ func (d *Downloader) fetchExtensionByGit(url string, handler func(tempExtPath st if err := util.RmAndMkDirAll(tempExtDir); err != nil { return nil, err } + proxyOptions := transport.ProxyOptions{} + proxyUrl := d.cfg.ProxyUrl() + if proxyUrl != nil { + proxyOptions.URL = proxyUrl.Scheme + "://" + proxyUrl.Host + proxyOptions.Username = proxyUrl.User.Username() + proxyOptions.Password, _ = proxyUrl.User.Password() + } // clone project to extension temp dir gitUrl := parentPath + projectPath + gitSuffix if _, err := git.PlainClone(tempExtDir, false, &git.CloneOptions{ - URL: gitUrl, - Depth: 1, + URL: gitUrl, + Depth: 1, + ProxyOptions: proxyOptions, }); err != nil { return nil, err } @@ -314,7 +323,9 @@ func doTrigger[T any](d *Downloader, event ActivationEvent, req *base.Request, c if req.Labels == nil { req.Labels = make(map[string]string) } - engine := engine.NewEngine() + engine := engine.NewEngine(&engine.Config{ + ProxyURL: d.cfg.ProxyUrl(), + }) defer engine.Close() err = engine.Runtime.Set("gopeed", gopeed) if err != nil { diff --git a/pkg/download/model.go b/pkg/download/model.go index f3aee75f7..ea8c1d0f5 100644 --- a/pkg/download/model.go +++ b/pkg/download/model.go @@ -8,6 +8,7 @@ import ( "github.com/GopeedLab/gopeed/pkg/base" "github.com/GopeedLab/gopeed/pkg/util" gonanoid "github.com/matoous/go-nanoid/v2" + "net/url" "sync" "time" ) @@ -101,15 +102,37 @@ func (cfg *DownloaderConfig) Init() *DownloaderConfig { type DownloaderStoreConfig struct { FirstLoad bool `json:"-"` // FirstLoad is the flag that the config is first time init and not from store - DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files - MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count - ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol - Extra map[string]any `json:"extra"` // Extra is the extra config + DownloadDir string `json:"downloadDir"` // DownloadDir is the default directory to save the downloaded files + MaxRunning int `json:"maxRunning"` // MaxRunning is the max running download count + ProtocolConfig map[string]any `json:"protocolConfig"` // ProtocolConfig is special config for each protocol + Extra map[string]any `json:"extra"` + Proxy *DownloaderProxyConfig `json:"proxy"` } func (cfg *DownloaderStoreConfig) Init() *DownloaderStoreConfig { if cfg.MaxRunning == 0 { cfg.MaxRunning = 5 } + if cfg.Proxy == nil { + cfg.Proxy = &DownloaderProxyConfig{} + } return cfg } + +func (cfg *DownloaderStoreConfig) ProxyUrl() *url.URL { + if cfg.Proxy == nil { + return nil + } + if cfg.Proxy.Enable == false || cfg.Proxy.Scheme == "" || cfg.Proxy.Host == "" { + return nil + } + return util.BuildProxyUrl(cfg.Proxy.Scheme, cfg.Proxy.Host, cfg.Proxy.Usr, cfg.Proxy.Pwd) +} + +type DownloaderProxyConfig struct { + Enable bool `json:"enable"` + Scheme string `json:"scheme"` + Host string `json:"host"` + Usr string `json:"usr"` + Pwd string `json:"pwd"` +} diff --git a/pkg/rest/server_test.go b/pkg/rest/server_test.go index 894f10165..bc99bef2a 100644 --- a/pkg/rest/server_test.go +++ b/pkg/rest/server_test.go @@ -27,7 +27,7 @@ var ( Extra: map[string]any{ "method": "", "header": map[string]string{ - "User-Agent": "gopeed", + "Usr-Agent": "gopeed", }, "body": "", }, diff --git a/pkg/util/url.go b/pkg/util/url.go index ee1170cfa..0a6ac190b 100644 --- a/pkg/util/url.go +++ b/pkg/util/url.go @@ -2,6 +2,7 @@ package util import ( "encoding/base64" + "net/url" "regexp" "strings" ) @@ -37,3 +38,16 @@ func ParseDataUri(uri string) (string, []byte) { } return mime, data } + +// BuildProxyUrl builds a proxy url with given host, username and password. +func BuildProxyUrl(scheme, host, usr, pwd string) *url.URL { + var user *url.Userinfo + if usr != "" && pwd != "" { + user = url.UserPassword(usr, pwd) + } + return &url.URL{ + Scheme: scheme, + User: user, + Host: host, + } +} diff --git a/ui/flutter/assets/locales/en_US.json b/ui/flutter/assets/locales/en_US.json deleted file mode 100644 index 7515a079a..000000000 --- a/ui/flutter/assets/locales/en_US.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "label": "English", - "error": "Error", - "tip": "Tip", - "confirm": "Confirm", - "cancel": "Cancel", - "selectAll": "Select All", - "task": "Tasks", - "downloading": "downloading", - "downloaded": "downloaded", - "setting": "Settings", - "donate": "Donate", - "exit": "Exit", - "create": "Create Task", - "directDownload": "Direct Download", - "advancedOptions": "Advanced Options", - "downloadLink": "Download Link", - "downloadLinkValid": "Please enter the download link", - "downloadLinkHit": "Please enter the download link, HTTP/HTTPS/MAGNET supported@append", - "downloadLinkHitDesktop": ", or drag the torrent file here directly", - "download": "Download", - "noFileSelected": "Please select at least one file to continue.", - "noStoragePermission": "Storage permission required", - "selectFile": "Select File", - "rename": "Rename", - "basic": "Basic", - "advanced": "Advanced", - "general": "General", - "downloadDir": "Download Directory", - "downloadDirValid": "Please select the download directory", - "connections": "Connections", - "maxRunning": "Max Running Tasks", - "items": "@count items", - "subscribeTracker": "Subscribe Tracker", - "subscribeFail": "Subscribe failed, please check network or try again later", - "update": "Update", - "updateDaily": "Update daily", - "lastUpdate": "Last update: @time", - "addTracker": "Add Tracker", - "addTrackerHit": "Please enter the tracker server url, one per line", - "ui": "UI", - "theme": "Theme", - "themeSystem": "System", - "themeLight": "Light", - "themeDark": "Dark", - "locale": "Language", - "about": "About", - "homepage": "Homepage", - "version": "Version", - "protocol": "Protocol", - "port": "Port", - "apiToken": "API Token", - "notSet": "NS", - "set": "SET", - "effectAfterRestart": "Effect after restart", - "startAll": "Start All", - "pauseAll": "Pause All", - "deleteTask": "Delete Task", - "deleteTaskTip": "Keep downloaded files", - "delete": "Delete", - "newVersionTitle": "Discover new version @version", - "newVersionUpdate": "Update Now", - "newVersionLater": "Later", - "extensions": "Extensions", - "extensionInstallUrl": "Install URL", - "extensionInstallSuccess": "Installed successfully", - "extensionUpdateSuccess": "Updated successfully", - "extensionDelete": "Delete Extension", - "extensionAlreadyLatest": "It's already the latest version", - "extensionFind": "Find Extensions", - "extensionDevelop": "Develop Extensions", - "history": "History", - "clearHistory": "Clear History", - "noHistoryFound": "No History Found", - "serviceTitle": "Download Service", - "serviceText": "Running" -} \ No newline at end of file diff --git a/ui/flutter/assets/locales/fa_IR.json b/ui/flutter/assets/locales/fa_IR.json deleted file mode 100644 index 6256c7290..000000000 --- a/ui/flutter/assets/locales/fa_IR.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "label": "فارسی", - "error": "اررور", - "tip": "Tip", - "confirm": "تایید", - "cancel": "انصراف", - "selectAll": "انتخاب همه", - "task": "کار", - "setting": "تنظیمات", - "donate": "کمک مالی", - "exit": "خروج", - "create": "ایجاد کار", - "downloading": "دانلود کردن", - "downloaded": "دانلود شدن", - "downloadLink": "لینک دانلود", - "downloadLinkValid": "لطفا لینک دانلود را وارد کنید", - "download": "دانلود", - "noFileSelected": "لطفا حداقل یک فایل را جهت دانلود انتخاب کنید.", - "noStoragePermission": "مجوز ذخیره سازی مورد نیاز است", - "selectFile": "انتخاب فایل", - "basic": "پایه", - "advanced": "پیشرفته", - "general": "عمومی", - "downloadDir": "دایرکتوری دانلود", - "downloadDirValid": "لطفا دایرکتوری دانلود را انتخاب کنید", - "connections": "اتصالات", - "items": "@count آیتم ها", - "subscribeTracker": "Subscribe Tracker", - "subscribeFail": "Subscribe failed, please check network or try again later", - "update": "بروزرسانی", - "updateDaily": "بروزرسانی روزانه", - "lastUpdate": "Last update: @time", - "addTracker": "Add Tracker", - "addTrackerHit": "Please enter the tracker server url, one per line", - "ui": "رابط کاربری", - "theme": "پوسته", - "themeSystem": "سیستم", - "themeLight": "روشن", - "themeDark": "تاریک", - "locale": "Language", - "about": "درباره ی", - "homepage": "صفحه اصلی", - "version": "نسخه", - "protocol": "پروتکل", - "port": "پورت", - "apiToken": "API توکن", - "notSet": "NS", - "set": "SET", - "effectAfterRestart": "Effect after restart", - "startAll": "شروع همه", - "pauseAll": "توقف همه", - "deleteTask": "پاک کردن کار", - "deleteTaskTip": "فایل های دانلود شده را نگه دارد", - "delete": "پاک کردن", - "newVersionTitle": "@version عنوان: کشف نسخه جدید", - "newVersionUpdate": "بروزرسانی", - "newVersionLater": "بعداً" -} \ No newline at end of file diff --git a/ui/flutter/assets/locales/ja_JP.json b/ui/flutter/assets/locales/ja_JP.json deleted file mode 100644 index abd556efd..000000000 --- a/ui/flutter/assets/locales/ja_JP.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "label": "日本語", - "error": "エラー", - "tip": "ヒント", - "confirm": "確認", - "cancel": "キャンセル", - "selectAll": "すべてを選択", - "task": "タスク", - "downloading": "ダウンロード中", - "downloaded": "ダウンロード済", - "setting": "セッティング", - "donate": "寄付する", - "exit": "終了", - "create": "タスクを作成", - "downloadLink": "ダウンロードリンク", - "downloadLinkValid": "ダウンロードリンクを入力してください", - "downloadLinkHit": "ダウンロード リンクを入力してください、HTTP/HTTPS/MAGNET support@append", - "downloadLinkHitDesktop": "または、torrent ファイルを直接ここにドラッグします", - "download": "ダウンロード", - "noFileSelected": "続行するには少なくとも 1 つのファイルを選択してください。", - "noStoragePermission": "ストレージのパーミッションが必要", - "selectFile": "ファイルを選択", - "basic": "ベーシック", - "advanced": "アドバンスド", - "general": "ジェネラル", - "downloadDir": "ディレクトリのダウンロード", - "downloadDirValid": "ダウンロードディレクトリを選択してください", - "connections": "接続", - "maxRunning": "最大実行タスク", - "items": "@count アイテム", - "subscribeTracker": "トラッカーを購読", - "subscribeFail": "登録に失敗しました。ネットワークを確認するか、後でもう一度お試しください", - "update": "アップデート", - "updateDaily": "毎日アップデート", - "lastUpdate": "最終アップデート: @time", - "addTracker": "トラッカーを追加", - "addTrackerHit": "トラッカーサーバーの URL を 1 行に 1 つずつ入力してください", - "ui": "UI", - "theme": "テーマ", - "themeSystem": "システム", - "themeLight": "ライト", - "themeDark": "ダーク", - "locale": "言語", - "about": "概要", - "homepage": "ホームページ", - "version": "バージョン", - "protocol": "プロトコル", - "port": "ポート", - "apiToken": "API トークン", - "notSet": "未設置", - "set": "設置", - "effectAfterRestart": "再起動後の効果", - "startAll": "すべてを開始", - "pauseAll": "すべてを一時停止", - "deleteTask": "タスクを削除", - "deleteTaskTip": "ダウンロードしたファイルを保持", - "delete": "削除", - "newVersionTitle": "新しいバージョン @version を発見する", - "newVersionUpdate": "アップデート", - "newVersionLater": "後で" -} diff --git a/ui/flutter/assets/locales/ru_RU.json b/ui/flutter/assets/locales/ru_RU.json deleted file mode 100644 index c99ad7ba8..000000000 --- a/ui/flutter/assets/locales/ru_RU.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "label": "Русский(Россия)", - "error": "Ошибка", - "tip": "Подсказка", - "confirm": "Подтвердить", - "cancel": "Отмена", - "selectAll": "Выбрать все", - "task": "Задачи", - "downloading": "Загрузка", - "downloaded": "Загружено", - "setting": "Настройки", - "donate": "Пожертвовать", - "exit": "Выход", - "create": "Создать задачу", - "advancedOptions": "Расширенные опции", - "downloadLink": "Ссылка на скачивание", - "downloadLinkValid": "Пожалуйста, введите ссылку для скачивания", - "downloadLinkHit": "Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET поддерживаются@append", - "downloadLinkHitDesktop": ", или перетащите сюда торрент-файл", - "download": "Скачать", - "noFileSelected": "Файл не выбран", - "noStoragePermission": "Требуется доступ к хранилищу", - "selectFile": "Выбрать файл", - "rename": "Переименовать", - "basic": "Основные параметры", - "advanced": "Расширенные", - "general": "Общие", - "downloadDir": "Папка загрузки", - "downloadDirValid": "Пожалуйста, выберите папку для загрузки", - "connections": "Соединений", - "maxRunning": "Максимальное количество активных задач", - "items": "@подсчет элементов", - "subscribeTracker": "Подпись трекера", - "subscribeFail": "Подписка не удалась, пожалуйста, проверьте сеть или повторите попытку позже", - "update": "Обновить", - "updateDaily": "Обновлять ежедневно", - "lastUpdate": "Последнее обновление: @время", - "addTracker": "Добавить трекер", - "addTrackerHit": "Пожалуйста, введите url трекера, по одному в строке", - "ui": "UI", - "theme": "Тема", - "themeSystem": "Системная", - "themeLight": "Светлая", - "themeDark": "Темная", - "locale": "Язык", - "about": "О приложении", - "homepage": "Домашняя страница", - "version": "Версия", - "protocol": "Протокол", - "port": "Порт", - "apiToken": "API Токен", - "notSet": "Не установлено", - "set": "Установлено", - "effectAfterRestart": "Эффект после перезагрузки", - "startAll": "Запустить все", - "pauseAll": "Приостановить все", - "deleteTask": "Удалить задачу", - "deleteTaskTip": "Сохранить загруженные файлы", - "delete": "Удалить", - "newVersionTitle": "Обнаружена новая версия @version", - "newVersionUpdate": "Обновить", - "newVersionLater": "позже", - "extensions": "Расширения", - "extensionInstallSuccess": "Установка завершена", - "extensionUpdateSuccess": "Обновление завершено", - "extensionDelete": "Удалить расширение", - "extensionAlreadyLatest": "Это последняя версия", - "extensionFind": "Найти расширения", - "extensionDevelop": "Разработка расширений", - "history": "История", - "clearHistory": "Очистить историю", - "noHistoryFound": "История не найдена" - } diff --git a/ui/flutter/assets/locales/zh_CN.json b/ui/flutter/assets/locales/zh_CN.json deleted file mode 100644 index bb623ce1a..000000000 --- a/ui/flutter/assets/locales/zh_CN.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "label": "中文(简体)", - "error": "错误", - "tip": "提示", - "confirm": "确认", - "cancel": "取消", - "selectAll": "全选", - "task": "任务", - "downloading": "下载中", - "downloaded": "已完成", - "setting": "设置", - "donate": "打赏", - "exit": "退出", - "create": "创建任务", - "directDownload": "直接下载", - "advancedOptions": "高级选项", - "downloadLink": "下载链接", - "downloadLinkValid": "请输入下载链接", - "downloadLinkHit": "请输入下载链接,支持 HTTP/HTTPS/MAGNET@append", - "downloadLinkHitDesktop": ",也可以直接拖拽种子文件到此处", - "download": "下载", - "noFileSelected": "请至少选择一个文件下载", - "noStoragePermission": "需要开启存储权限", - "selectFile": "选择文件", - "rename": "重命名", - "basic": "基础", - "advanced": "高级", - "general": "通用", - "downloadDir": "下载目录", - "downloadDirValid": "请选择下载目录", - "connections": "连接数", - "maxRunning": "最大下载数", - "items": "@count 项", - "subscribeTracker": "订阅 Tracker", - "subscribeFail": "订阅失败,请检查网络或稍后重试", - "update": "更新", - "updateDaily": "每天自动更新", - "lastUpdate": "上次更新:@time", - "addTracker": "添加 Tracker", - "addTrackerHit": "请输入 tracker 服务器地址,每行一条", - "ui": "界面", - "theme": "主题", - "themeSystem": "跟随系统", - "themeLight": "明亮主题", - "themeDark": "暗黑主题", - "locale": "语言", - "about": "关于", - "homepage": "主页", - "version": "版本", - "protocol": "通讯协议", - "port": "端口", - "apiToken": "接口令牌", - "notSet": "未设置", - "set": "已设置", - "effectAfterRestart": "此配置项将在重启应用后生效", - "startAll": "全部开始", - "pauseAll": "全部暂停", - "deleteTask": "删除任务", - "deleteTaskTip": "保留已下载的文件", - "delete": "删除", - "newVersionTitle": "发现新版本 @version", - "newVersionUpdate": "立即更新", - "newVersionLater": "稍后再说", - "extensions": "扩展", - "extensionInstallUrl": "安装链接", - "extensionInstallSuccess": "安装成功", - "extensionUpdateSuccess": "更新成功", - "extensionDelete": "删除扩展", - "extensionAlreadyLatest": "已经是最新版本", - "extensionFind": "获取扩展", - "extensionDevelop": "开发扩展", - "history": "历史记录", - "clearHistory": "清空历史记录", - "noHistoryFound": "暂无历史记录", - "serviceTitle": "下载服务", - "serviceText": "运行中" -} \ No newline at end of file diff --git a/ui/flutter/assets/locales/zh_TW.json b/ui/flutter/assets/locales/zh_TW.json deleted file mode 100644 index dc14530c2..000000000 --- a/ui/flutter/assets/locales/zh_TW.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "label": "中文 (繁體) ", - "error": "錯誤", - "tip": "提示", - "confirm": "確定", - "cancel": "取消", - "selectAll": "全選", - "task": "任務", - "downloading": "下載中", - "downloaded": "已完成", - "setting": "設定", - "donate": "斗內", - "exit": "離開", - "create": "新增任務", - "directDownload": "直接下載", - "advancedOptions": "進階選項", - "downloadLink": "下載鏈接", - "downloadLinkValid": "請輸入下載鏈接", - "downloadLinkHit": "請輸入下載鏈接,支援 HTTP/HTTPS/MAGNET@append", - "downloadLinkHitDesktop": ",也可以通過拖拽Torrent文件至此以新增任務", - "download": "下載", - "noFileSelected": "沒有選取的文件", - "noStoragePermission": "沒有存儲權限", - "selectFile": "選擇文件", - "rename": "重命名", - "basic": "基礎", - "advanced": "進階", - "general": "通用設定", - "downloadDir": "下載目録", - "downloadDirValid": "請選擇下載目録", - "connections": "同時連接數", - "maxRunning": "最大同時下載數", - "items": "@count 項", - "subscribeTracker": "已訂閲的追蹤者列表", - "subscribeFail": "訂閲失敗", - "update": "更新", - "updateDaily": "每天自動更新", - "lastUpdate": "上次更新時間: @time", - "addTracker": "增加新的追蹤者", - "addTrackerHit": "請輸入追蹤者服務器地址,每行一條", - "ui": "介面", - "theme": "佈景主題", - "themeSystem": "跟隨系統", - "themeLight": "亮色主題", - "themeDark": "深色主題", - "locale": "語言", - "about": "關於", - "homepage": "訪問主頁", - "version": "版本資訊", - "protocol": "端口設定", - "port": "端口", - "apiToken": "接口秘鑰", - "notSet": "未設定", - "set": "已設定", - "effectAfterRestart": "重啟後生效", - "startAll": "全部開始", - "pauseAll": "全部暫停", - "deleteTask": "刪除任務", - "deleteTaskTip": "保留已經下載的文件", - "delete": "刪除", - "newVersionTitle": "發現新版本 @version", - "newVersionUpdate": "立刻更新", - "newVersionLater": "稍後更新", - "extensions": "外掛程序", - "extensionInstallUrl": "安裝URL", - "extensionInstallSuccess": "安裝成功", - "extensionUpdateSuccess": "更新完成", - "extensionDelete": "解除安裝", - "extensionAlreadyLatest": "已是最新版本", - "extensionFind": "取得外掛", - "extensionDevelop": "開發外掛", - "history": "歷史記錄", - "clearHistory": "清空記錄", - "noHistoryFound": "還沒有記錄", - "serviceTitle": "下載服務", - "serviceText": "執行中" -} \ No newline at end of file diff --git a/ui/flutter/lib/api/model/downloader_config.dart b/ui/flutter/lib/api/model/downloader_config.dart index 9abb9c66f..88124307a 100644 --- a/ui/flutter/lib/api/model/downloader_config.dart +++ b/ui/flutter/lib/api/model/downloader_config.dart @@ -8,6 +8,7 @@ class DownloaderConfig { int maxRunning = 0; ProtocolConfig protocolConfig = ProtocolConfig(); ExtraConfig extra = ExtraConfig(); + ProxyConfig proxy = ProxyConfig(); DownloaderConfig(); @@ -66,6 +67,22 @@ class ExtraConfig { Map toJson() => _$ExtraConfigToJson(this); } +@JsonSerializable() +class ProxyConfig { + bool enable = false; + String scheme = ''; + String host = ''; + String usr = ''; + String pwd = ''; + + ProxyConfig(); + + factory ProxyConfig.fromJson(Map json) => + _$ProxyConfigFromJson(json); + + Map toJson() => _$ProxyConfigToJson(this); +} + @JsonSerializable() class ExtraConfigBt { List trackerSubscribeUrls = []; diff --git a/ui/flutter/lib/api/model/downloader_config.g.dart b/ui/flutter/lib/api/model/downloader_config.g.dart index 9addee001..66d129845 100644 --- a/ui/flutter/lib/api/model/downloader_config.g.dart +++ b/ui/flutter/lib/api/model/downloader_config.g.dart @@ -12,7 +12,8 @@ DownloaderConfig _$DownloaderConfigFromJson(Map json) => ..maxRunning = json['maxRunning'] as int ..protocolConfig = ProtocolConfig.fromJson( json['protocolConfig'] as Map?) - ..extra = ExtraConfig.fromJson(json['extra'] as Map?); + ..extra = ExtraConfig.fromJson(json['extra'] as Map?) + ..proxy = ProxyConfig.fromJson(json['proxy'] as Map); Map _$DownloaderConfigToJson(DownloaderConfig instance) => { @@ -20,6 +21,7 @@ Map _$DownloaderConfigToJson(DownloaderConfig instance) => 'maxRunning': instance.maxRunning, 'protocolConfig': instance.protocolConfig.toJson(), 'extra': instance.extra.toJson(), + 'proxy': instance.proxy.toJson(), }; ProtocolConfig _$ProtocolConfigFromJson(Map json) => @@ -65,6 +67,22 @@ Map _$ExtraConfigToJson(ExtraConfig instance) => 'bt': instance.bt.toJson(), }; +ProxyConfig _$ProxyConfigFromJson(Map json) => ProxyConfig() + ..enable = json['enable'] as bool + ..scheme = json['scheme'] as String + ..host = json['host'] as String + ..usr = json['usr'] as String + ..pwd = json['pwd'] as String; + +Map _$ProxyConfigToJson(ProxyConfig instance) => + { + 'enable': instance.enable, + 'scheme': instance.scheme, + 'host': instance.host, + 'usr': instance.usr, + 'pwd': instance.pwd, + }; + ExtraConfigBt _$ExtraConfigBtFromJson(Map json) => ExtraConfigBt() ..trackerSubscribeUrls = (json['trackerSubscribeUrls'] as List) diff --git a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart index fc294fc90..71539cad7 100644 --- a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart +++ b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart @@ -16,7 +16,7 @@ import 'package:window_manager/window_manager.dart'; import '../../../../api/api.dart'; import '../../../../api/model/downloader_config.dart'; import '../../../../core/common/start_config.dart'; -import '../../../../generated/locales.g.dart'; +import '../../../../i18n/message.dart'; import '../../../../util/locale_manager.dart'; import '../../../../util/log_util.dart'; import '../../../../util/package_info.dart'; @@ -185,9 +185,7 @@ class AppController extends GetxController with WindowListener, TrayListener { MenuItem( label: 'donate'.tr, onClick: (menuItem) => { - launchUrl( - Uri.parse( - "https://github.com/GopeedLab/gopeed/blob/main/.donate/index.md#donate"), + launchUrl(Uri.parse("https://docs.gopeed.com/donate.html"), mode: LaunchMode.externalApplication) }, ), @@ -376,7 +374,7 @@ class AppController extends GetxController with WindowListener, TrayListener { } if (extra.locale.isEmpty) { final systemLocale = getLocaleKey(PlatformDispatcher.instance.locale); - extra.locale = AppTranslation.translations.containsKey(systemLocale) + extra.locale = messages.keys.containsKey(systemLocale) ? systemLocale : getLocaleKey(fallbackLocale); } @@ -384,6 +382,10 @@ class AppController extends GetxController with WindowListener, TrayListener { // default select all tracker subscribe urls extra.bt.trackerSubscribeUrls.addAll(allTrackerSubscribeUrls); } + final proxy = config.proxy; + if (proxy.scheme.isEmpty) { + proxy.scheme = 'http'; + } if (config.downloadDir.isEmpty) { if (Util.isDesktop()) { diff --git a/ui/flutter/lib/app/modules/app/views/app_view.dart b/ui/flutter/lib/app/modules/app/views/app_view.dart index a930ba20c..5642f2f7a 100644 --- a/ui/flutter/lib/app/modules/app/views/app_view.dart +++ b/ui/flutter/lib/app/modules/app/views/app_view.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:get/get.dart'; -import '../../../../generated/locales.g.dart'; +import '../../../../i18n/message.dart'; import '../../../../theme/theme.dart'; import '../../../../util/locale_manager.dart'; import '../../../routes/app_pages.dart'; @@ -20,8 +20,7 @@ class AppView extends GetView { theme: GopeedTheme.light, darkTheme: GopeedTheme.dark, themeMode: ThemeMode.values.byName(config.extra.themeMode), - // translations: messages, - translationsKeys: AppTranslation.translations, + translations: messages, locale: toLocale(config.extra.locale), fallbackLocale: fallbackLocale, localizationsDelegates: const [ @@ -29,8 +28,7 @@ class AppView extends GetView { GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], - supportedLocales: - AppTranslation.translations.keys.map((e) => toLocale(e)).toList(), + supportedLocales: messages.keys.keys.map((e) => toLocale(e)).toList(), getPages: AppPages.routes, ); } diff --git a/ui/flutter/lib/app/modules/extension/views/extension_view.dart b/ui/flutter/lib/app/modules/extension/views/extension_view.dart index d8c31d1a1..3a6881e72 100644 --- a/ui/flutter/lib/app/modules/extension/views/extension_view.dart +++ b/ui/flutter/lib/app/modules/extension/views/extension_view.dart @@ -208,20 +208,15 @@ class ExtensionView extends GetView { ? IconButton( onPressed: () { launchUrl( - Uri.parse(extension.homepage), - mode: LaunchMode - .externalApplication); + Uri.parse(extension.homepage)); }, icon: const Icon(Icons.home)) : null, extension.repository?.url.isNotEmpty == true ? IconButton( onPressed: () { - launchUrl( - Uri.parse( - extension.repository!.url), - mode: LaunchMode - .externalApplication); + launchUrl(Uri.parse( + extension.repository!.url)); }, icon: const Icon(Icons.code)) : null, diff --git a/ui/flutter/lib/app/modules/setting/views/setting_view.dart b/ui/flutter/lib/app/modules/setting/views/setting_view.dart index cbeb27adf..495259ef6 100644 --- a/ui/flutter/lib/app/modules/setting/views/setting_view.dart +++ b/ui/flutter/lib/app/modules/setting/views/setting_view.dart @@ -7,7 +7,7 @@ import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:url_launcher/url_launcher.dart'; -import '../../../../generated/locales.g.dart'; +import '../../../../i18n/message.dart'; import '../../../../util/input_formatter.dart'; import '../../../../util/locale_manager.dart'; import '../../../../util/message.dart'; @@ -272,8 +272,7 @@ class SettingView extends GetView { )); final buildLocale = _buildConfigItem( 'locale', - () => AppTranslation - .translations[downloaderCfg.value.extra.locale]!['label']!, + () => messages.keys[downloaderCfg.value.extra.locale]!['label']!, (Key key) => DropdownButton( key: key, value: downloaderCfg.value.extra.locale, @@ -287,23 +286,26 @@ class SettingView extends GetView { await debounceSave(); }, - items: AppTranslation.translations.keys + items: messages.keys.keys .map((e) => DropdownMenuItem( value: e, - child: Text(AppTranslation.translations[e]!['label']!), + child: Text(messages.keys[e]!['label']!), )) .toList(), )); // about config items start - buildHomepage() => ListTile( - title: Text('homepage'.tr), - subtitle: const Text('https://github.com/GopeedLab/gopeed'), - onTap: () { - launchUrl(Uri.parse('https://github.com/GopeedLab/gopeed'), - mode: LaunchMode.externalApplication); - }, - ); + buildHomepage() { + const homePage = 'https://gopeed.com'; + return ListTile( + title: Text('homepage'.tr), + subtitle: const Text(homePage), + onTap: () { + launchUrl(Uri.parse(homePage), mode: LaunchMode.externalApplication); + }, + ); + } + buildVersion() { bool isNewVersion(String current, String latest) { if (latest == "") { @@ -363,7 +365,167 @@ class SettingView extends GetView { ); } - // advanced config items start + // advanced config proxy items start + final buildProxy = _buildConfigItem( + 'proxy', + () => downloaderCfg.value.proxy.enable + ? '${downloaderCfg.value.proxy.scheme}://${downloaderCfg.value.proxy.host}' + : 'notSet'.tr, + (Key key) { + final proxy = downloaderCfg.value.proxy; + + final switcher = Switch( + value: proxy.enable, + onChanged: (bool value) async { + if (value != proxy.enable) { + downloaderCfg.update((val) { + val!.proxy.enable = value; + }); + + await debounceSave(); + } + }, + ); + + final items = [ + SizedBox( + width: 150, + child: DropdownButtonFormField( + value: proxy.scheme, + onChanged: (value) async { + if (value != null && value != proxy.scheme) { + proxy.scheme = value; + + await debounceSave(); + } + }, + items: const [ + DropdownMenuItem( + value: 'http', + child: Text('HTTP'), + ), + DropdownMenuItem( + value: 'https', + child: Text('HTTPS'), + ), + DropdownMenuItem( + value: 'socks5', + child: Text('SOCKS5'), + ), + ], + ), + ) + ]; + + final arr = proxy.host.split(':'); + var ip = ''; + var port = ''; + if (arr.length > 1) { + ip = arr[0]; + port = arr[1]; + } + + final ipController = TextEditingController(text: ip); + final portController = TextEditingController(text: port); + updateAddress() async { + final newAddress = '${ipController.text}:${portController.text}'; + if (newAddress != startCfg.value.address) { + proxy.host = newAddress; + + await debounceSave(); + } + } + + ipController.addListener(updateAddress); + portController.addListener(updateAddress); + items.addAll([ + const Padding(padding: EdgeInsets.only(left: 20)), + SizedBox( + width: 150, + child: TextFormField( + controller: ipController, + decoration: const InputDecoration( + labelText: 'IP', + contentPadding: EdgeInsets.all(0.0), + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp('[0-9.]')), + ], + ), + ), + const Padding(padding: EdgeInsets.only(left: 10)), + SizedBox( + width: 150, + child: TextFormField( + controller: portController, + decoration: InputDecoration( + labelText: 'port'.tr, + contentPadding: const EdgeInsets.all(0.0), + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + NumericalRangeFormatter(min: 0, max: 65535), + ], + ), + ), + ]); + + final usrController = TextEditingController(text: proxy.usr); + final pwdController = TextEditingController(text: proxy.pwd); + + final auth = [ + SizedBox( + width: 150, + child: TextFormField( + controller: usrController, + decoration: InputDecoration( + labelText: 'username'.tr, + contentPadding: const EdgeInsets.all(0.0), + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp('[0-9.]')), + ], + ), + ), + const Padding(padding: EdgeInsets.only(left: 10)), + SizedBox( + width: 150, + child: TextFormField( + controller: pwdController, + decoration: InputDecoration( + labelText: 'password'.tr, + contentPadding: const EdgeInsets.all(0.0), + ), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + NumericalRangeFormatter(min: 0, max: 65535), + ], + ), + ), + ]; + + return Form( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _addPadding([ + switcher, + Row( + children: items, + ), + Row( + children: auth, + ), + ]), + ), + ); + }, + ); + + // advanced config API items start final buildApiProtocol = _buildConfigItem( 'protocol', () => startCfg.value.network == 'tcp' @@ -424,7 +586,7 @@ class SettingView extends GetView { items.addAll([ const Padding(padding: EdgeInsets.only(left: 20)), SizedBox( - width: 200, + width: 150, child: TextFormField( controller: ipController, decoration: const InputDecoration( @@ -439,7 +601,7 @@ class SettingView extends GetView { ), const Padding(padding: EdgeInsets.only(left: 10)), SizedBox( - width: 200, + width: 150, child: TextFormField( controller: portController, decoration: InputDecoration( @@ -553,21 +715,42 @@ class SettingView extends GetView { ]), ), ), - Column( - children: [ + // Column( + // children: [ + // Card( + // child: Column( + // children: [ + // ..._addDivider([ + // buildApiProtocol(), + // Util.isDesktop() && startCfg.value.network == 'tcp' + // ? buildApiToken() + // : null, + // ]), + // ], + // )), + // ], + // ), + SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _addPadding([ + Text('network'.tr), Card( child: Column( - children: [ - ..._addDivider([ - buildApiProtocol(), - Util.isDesktop() && startCfg.value.network == 'tcp' - ? buildApiToken() - : null, - ]), - ], + children: _addDivider([buildProxy()]), )), - ], - ), + const Text('API'), + Card( + child: Column( + children: _addDivider([ + buildApiProtocol(), + Util.isDesktop() && startCfg.value.network == 'tcp' + ? buildApiToken() + : null, + ]), + )), + ]), + )) ], ).paddingOnly(left: 16, right: 16, top: 16, bottom: 16)), ), diff --git a/ui/flutter/lib/generated/locales.g.dart b/ui/flutter/lib/generated/locales.g.dart deleted file mode 100644 index 5180c28e7..000000000 --- a/ui/flutter/lib/generated/locales.g.dart +++ /dev/null @@ -1,524 +0,0 @@ -// DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart - -// ignore_for_file: lines_longer_than_80_chars -// ignore: avoid_classes_with_only_static_members -class AppTranslation { - static Map> translations = { - 'en_US': Locales.en_US, - 'fa_IR': Locales.fa_IR, - 'ja_JP': Locales.ja_JP, - 'ru_RU': Locales.ru_RU, - 'zh_CN': Locales.zh_CN, - 'zh_TW': Locales.zh_TW, - }; -} - -class LocaleKeys { - LocaleKeys._(); - static const label = 'label'; - static const error = 'error'; - static const tip = 'tip'; - static const confirm = 'confirm'; - static const cancel = 'cancel'; - static const selectAll = 'selectAll'; - static const task = 'task'; - static const downloading = 'downloading'; - static const downloaded = 'downloaded'; - static const setting = 'setting'; - static const donate = 'donate'; - static const exit = 'exit'; - static const create = 'create'; - static const directDownload = 'directDownload'; - static const advancedOptions = 'advancedOptions'; - static const downloadLink = 'downloadLink'; - static const downloadLinkValid = 'downloadLinkValid'; - static const downloadLinkHit = 'downloadLinkHit'; - static const downloadLinkHitDesktop = 'downloadLinkHitDesktop'; - static const download = 'download'; - static const noFileSelected = 'noFileSelected'; - static const noStoragePermission = 'noStoragePermission'; - static const selectFile = 'selectFile'; - static const rename = 'rename'; - static const basic = 'basic'; - static const advanced = 'advanced'; - static const general = 'general'; - static const downloadDir = 'downloadDir'; - static const downloadDirValid = 'downloadDirValid'; - static const connections = 'connections'; - static const maxRunning = 'maxRunning'; - static const items = 'items'; - static const subscribeTracker = 'subscribeTracker'; - static const subscribeFail = 'subscribeFail'; - static const update = 'update'; - static const updateDaily = 'updateDaily'; - static const lastUpdate = 'lastUpdate'; - static const addTracker = 'addTracker'; - static const addTrackerHit = 'addTrackerHit'; - static const ui = 'ui'; - static const theme = 'theme'; - static const themeSystem = 'themeSystem'; - static const themeLight = 'themeLight'; - static const themeDark = 'themeDark'; - static const locale = 'locale'; - static const about = 'about'; - static const homepage = 'homepage'; - static const version = 'version'; - static const protocol = 'protocol'; - static const port = 'port'; - static const apiToken = 'apiToken'; - static const notSet = 'notSet'; - static const set = 'set'; - static const effectAfterRestart = 'effectAfterRestart'; - static const startAll = 'startAll'; - static const pauseAll = 'pauseAll'; - static const deleteTask = 'deleteTask'; - static const deleteTaskTip = 'deleteTaskTip'; - static const delete = 'delete'; - static const newVersionTitle = 'newVersionTitle'; - static const newVersionUpdate = 'newVersionUpdate'; - static const newVersionLater = 'newVersionLater'; - static const extensions = 'extensions'; - static const extensionInstallUrl = 'extensionInstallUrl'; - static const extensionInstallSuccess = 'extensionInstallSuccess'; - static const extensionUpdateSuccess = 'extensionUpdateSuccess'; - static const extensionDelete = 'extensionDelete'; - static const extensionAlreadyLatest = 'extensionAlreadyLatest'; - static const extensionFind = 'extensionFind'; - static const extensionDevelop = 'extensionDevelop'; - static const history = 'history'; - static const clearHistory = 'clearHistory'; - static const noHistoryFound = 'noHistoryFound'; - static const serviceTitle = 'serviceTitle'; - static const serviceText = 'serviceText'; -} - -class Locales { - static const en_US = { - 'label': 'English', - 'error': 'Error', - 'tip': 'Tip', - 'confirm': 'Confirm', - 'cancel': 'Cancel', - 'selectAll': 'Select All', - 'task': 'Tasks', - 'downloading': 'downloading', - 'downloaded': 'downloaded', - 'setting': 'Settings', - 'donate': 'Donate', - 'exit': 'Exit', - 'create': 'Create Task', - 'directDownload': 'Direct Download', - 'advancedOptions': 'Advanced Options', - 'downloadLink': 'Download Link', - 'downloadLinkValid': 'Please enter the download link', - 'downloadLinkHit': - 'Please enter the download link, HTTP/HTTPS/MAGNET supported@append', - 'downloadLinkHitDesktop': ', or drag the torrent file here directly', - 'download': 'Download', - 'noFileSelected': 'Please select at least one file to continue.', - 'noStoragePermission': 'Storage permission required', - 'selectFile': 'Select File', - 'rename': 'Rename', - 'basic': 'Basic', - 'advanced': 'Advanced', - 'general': 'General', - 'downloadDir': 'Download Directory', - 'downloadDirValid': 'Please select the download directory', - 'connections': 'Connections', - 'maxRunning': 'Max Running Tasks', - 'items': '@count items', - 'subscribeTracker': 'Subscribe Tracker', - 'subscribeFail': - 'Subscribe failed, please check network or try again later', - 'update': 'Update', - 'updateDaily': 'Update daily', - 'lastUpdate': 'Last update: @time', - 'addTracker': 'Add Tracker', - 'addTrackerHit': 'Please enter the tracker server url, one per line', - 'ui': 'UI', - 'theme': 'Theme', - 'themeSystem': 'System', - 'themeLight': 'Light', - 'themeDark': 'Dark', - 'locale': 'Language', - 'about': 'About', - 'homepage': 'Homepage', - 'version': 'Version', - 'protocol': 'Protocol', - 'port': 'Port', - 'apiToken': 'API Token', - 'notSet': 'NS', - 'set': 'SET', - 'effectAfterRestart': 'Effect after restart', - 'startAll': 'Start All', - 'pauseAll': 'Pause All', - 'deleteTask': 'Delete Task', - 'deleteTaskTip': 'Keep downloaded files', - 'delete': 'Delete', - 'newVersionTitle': 'Discover new version @version', - 'newVersionUpdate': 'Update Now', - 'newVersionLater': 'Later', - 'extensions': 'Extensions', - 'extensionInstallUrl': 'Install URL', - 'extensionInstallSuccess': 'Installed successfully', - 'extensionUpdateSuccess': 'Updated successfully', - 'extensionDelete': 'Delete Extension', - 'extensionAlreadyLatest': 'It\'s already the latest version', - 'extensionFind': 'Find Extensions', - 'extensionDevelop': 'Develop Extensions', - 'history': 'History', - 'clearHistory': 'Clear History', - 'noHistoryFound': 'No History Found', - 'serviceTitle': 'Download Service', - 'serviceText': 'Running', - }; - static const fa_IR = { - 'label': 'فارسی', - 'error': 'اررور', - 'tip': 'Tip', - 'confirm': 'تایید', - 'cancel': 'انصراف', - 'selectAll': 'انتخاب همه', - 'task': 'کار', - 'setting': 'تنظیمات', - 'donate': 'کمک مالی', - 'exit': 'خروج', - 'create': 'ایجاد کار', - 'downloading': 'دانلود کردن', - 'downloaded': 'دانلود شدن', - 'downloadLink': 'لینک دانلود', - 'downloadLinkValid': 'لطفا لینک دانلود را وارد کنید', - 'download': 'دانلود', - 'noFileSelected': 'لطفا حداقل یک فایل را جهت دانلود انتخاب کنید.', - 'noStoragePermission': 'مجوز ذخیره سازی مورد نیاز است', - 'selectFile': 'انتخاب فایل', - 'basic': 'پایه', - 'advanced': 'پیشرفته', - 'general': 'عمومی', - 'downloadDir': 'دایرکتوری دانلود', - 'downloadDirValid': 'لطفا دایرکتوری دانلود را انتخاب کنید', - 'connections': 'اتصالات', - 'items': '@count آیتم ها', - 'subscribeTracker': 'Subscribe Tracker', - 'subscribeFail': - 'Subscribe failed, please check network or try again later', - 'update': 'بروزرسانی', - 'updateDaily': 'بروزرسانی روزانه', - 'lastUpdate': 'Last update: @time', - 'addTracker': 'Add Tracker', - 'addTrackerHit': 'Please enter the tracker server url, one per line', - 'ui': 'رابط کاربری', - 'theme': 'پوسته', - 'themeSystem': 'سیستم', - 'themeLight': 'روشن', - 'themeDark': 'تاریک', - 'locale': 'Language', - 'about': 'درباره ی', - 'homepage': 'صفحه اصلی', - 'version': 'نسخه', - 'protocol': 'پروتکل', - 'port': 'پورت', - 'apiToken': 'API توکن', - 'notSet': 'NS', - 'set': 'SET', - 'effectAfterRestart': 'Effect after restart', - 'startAll': 'شروع همه', - 'pauseAll': 'توقف همه', - 'deleteTask': 'پاک کردن کار', - 'deleteTaskTip': 'فایل های دانلود شده را نگه دارد', - 'delete': 'پاک کردن', - 'newVersionTitle': '@version عنوان: کشف نسخه جدید', - 'newVersionUpdate': 'بروزرسانی', - 'newVersionLater': 'بعداً', - }; - static const ja_JP = { - 'label': '日本語', - 'error': 'エラー', - 'tip': 'ヒント', - 'confirm': '確認', - 'cancel': 'キャンセル', - 'selectAll': 'すべてを選択', - 'task': 'タスク', - 'downloading': 'ダウンロード中', - 'downloaded': 'ダウンロード済', - 'setting': 'セッティング', - 'donate': '寄付する', - 'exit': '終了', - 'create': 'タスクを作成', - 'downloadLink': 'ダウンロードリンク', - 'downloadLinkValid': 'ダウンロードリンクを入力してください', - 'downloadLinkHit': 'ダウンロード リンクを入力してください、HTTP/HTTPS/MAGNET support@append', - 'downloadLinkHitDesktop': 'または、torrent ファイルを直接ここにドラッグします', - 'download': 'ダウンロード', - 'noFileSelected': '続行するには少なくとも 1 つのファイルを選択してください。', - 'noStoragePermission': 'ストレージのパーミッションが必要', - 'selectFile': 'ファイルを選択', - 'basic': 'ベーシック', - 'advanced': 'アドバンスド', - 'general': 'ジェネラル', - 'downloadDir': 'ディレクトリのダウンロード', - 'downloadDirValid': 'ダウンロードディレクトリを選択してください', - 'connections': '接続', - 'maxRunning': '最大実行タスク', - 'items': '@count アイテム', - 'subscribeTracker': 'トラッカーを購読', - 'subscribeFail': '登録に失敗しました。ネットワークを確認するか、後でもう一度お試しください', - 'update': 'アップデート', - 'updateDaily': '毎日アップデート', - 'lastUpdate': '最終アップデート: @time', - 'addTracker': 'トラッカーを追加', - 'addTrackerHit': 'トラッカーサーバーの URL を 1 行に 1 つずつ入力してください', - 'ui': 'UI', - 'theme': 'テーマ', - 'themeSystem': 'システム', - 'themeLight': 'ライト', - 'themeDark': 'ダーク', - 'locale': '言語', - 'about': '概要', - 'homepage': 'ホームページ', - 'version': 'バージョン', - 'protocol': 'プロトコル', - 'port': 'ポート', - 'apiToken': 'API トークン', - 'notSet': '未設置', - 'set': '設置', - 'effectAfterRestart': '再起動後の効果', - 'startAll': 'すべてを開始', - 'pauseAll': 'すべてを一時停止', - 'deleteTask': 'タスクを削除', - 'deleteTaskTip': 'ダウンロードしたファイルを保持', - 'delete': '削除', - 'newVersionTitle': '新しいバージョン @version を発見する', - 'newVersionUpdate': 'アップデート', - 'newVersionLater': '後で', - }; - static const ru_RU = { - 'label': 'Русский(Россия)', - 'error': 'Ошибка', - 'tip': 'Подсказка', - 'confirm': 'Подтвердить', - 'cancel': 'Отмена', - 'selectAll': 'Выбрать все', - 'task': 'Задачи', - 'downloading': 'Загрузка', - 'downloaded': 'Загружено', - 'setting': 'Настройки', - 'donate': 'Пожертвовать', - 'exit': 'Выход', - 'create': 'Создать задачу', - 'advancedOptions': 'Расширенные опции', - 'downloadLink': 'Ссылка на скачивание', - 'downloadLinkValid': 'Пожалуйста, введите ссылку для скачивания', - 'downloadLinkHit': - 'Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET поддерживаются@append', - 'downloadLinkHitDesktop': ', или перетащите сюда торрент-файл', - 'download': 'Скачать', - 'noFileSelected': 'Файл не выбран', - 'noStoragePermission': 'Требуется доступ к хранилищу', - 'selectFile': 'Выбрать файл', - 'rename': 'Переименовать', - 'basic': 'Основные параметры', - 'advanced': 'Расширенные', - 'general': 'Общие', - 'downloadDir': 'Папка загрузки', - 'downloadDirValid': 'Пожалуйста, выберите папку для загрузки', - 'connections': 'Соединений', - 'maxRunning': 'Максимальное количество активных задач', - 'items': '@подсчет элементов', - 'subscribeTracker': 'Подпись трекера', - 'subscribeFail': - 'Подписка не удалась, пожалуйста, проверьте сеть или повторите попытку позже', - 'update': 'Обновить', - 'updateDaily': 'Обновлять ежедневно', - 'lastUpdate': 'Последнее обновление: @время', - 'addTracker': 'Добавить трекер', - 'addTrackerHit': 'Пожалуйста, введите url трекера, по одному в строке', - 'ui': 'UI', - 'theme': 'Тема', - 'themeSystem': 'Системная', - 'themeLight': 'Светлая', - 'themeDark': 'Темная', - 'locale': 'Язык', - 'about': 'О приложении', - 'homepage': 'Домашняя страница', - 'version': 'Версия', - 'protocol': 'Протокол', - 'port': 'Порт', - 'apiToken': 'API Токен', - 'notSet': 'Не установлено', - 'set': 'Установлено', - 'effectAfterRestart': 'Эффект после перезагрузки', - 'startAll': 'Запустить все', - 'pauseAll': 'Приостановить все', - 'deleteTask': 'Удалить задачу', - 'deleteTaskTip': 'Сохранить загруженные файлы', - 'delete': 'Удалить', - 'newVersionTitle': 'Обнаружена новая версия @version', - 'newVersionUpdate': 'Обновить', - 'newVersionLater': 'позже', - 'extensions': 'Расширения', - 'extensionInstallSuccess': 'Установка завершена', - 'extensionUpdateSuccess': 'Обновление завершено', - 'extensionDelete': 'Удалить расширение', - 'extensionAlreadyLatest': 'Это последняя версия', - 'extensionFind': 'Найти расширения', - 'extensionDevelop': 'Разработка расширений', - 'history': 'История', - 'clearHistory': 'Очистить историю', - 'noHistoryFound': 'История не найдена', - }; - static const zh_CN = { - 'label': '中文(简体)', - 'error': '错误', - 'tip': '提示', - 'confirm': '确认', - 'cancel': '取消', - 'selectAll': '全选', - 'task': '任务', - 'downloading': '下载中', - 'downloaded': '已完成', - 'setting': '设置', - 'donate': '打赏', - 'exit': '退出', - 'create': '创建任务', - 'directDownload': '直接下载', - 'advancedOptions': '高级选项', - 'downloadLink': '下载链接', - 'downloadLinkValid': '请输入下载链接', - 'downloadLinkHit': '请输入下载链接,支持 HTTP/HTTPS/MAGNET@append', - 'downloadLinkHitDesktop': ',也可以直接拖拽种子文件到此处', - 'download': '下载', - 'noFileSelected': '请至少选择一个文件下载', - 'noStoragePermission': '需要开启存储权限', - 'selectFile': '选择文件', - 'rename': '重命名', - 'basic': '基础', - 'advanced': '高级', - 'general': '通用', - 'downloadDir': '下载目录', - 'downloadDirValid': '请选择下载目录', - 'connections': '连接数', - 'maxRunning': '最大下载数', - 'items': '@count 项', - 'subscribeTracker': '订阅 Tracker', - 'subscribeFail': '订阅失败,请检查网络或稍后重试', - 'update': '更新', - 'updateDaily': '每天自动更新', - 'lastUpdate': '上次更新:@time', - 'addTracker': '添加 Tracker', - 'addTrackerHit': '请输入 tracker 服务器地址,每行一条', - 'ui': '界面', - 'theme': '主题', - 'themeSystem': '跟随系统', - 'themeLight': '明亮主题', - 'themeDark': '暗黑主题', - 'locale': '语言', - 'about': '关于', - 'homepage': '主页', - 'version': '版本', - 'protocol': '通讯协议', - 'port': '端口', - 'apiToken': '接口令牌', - 'notSet': '未设置', - 'set': '已设置', - 'effectAfterRestart': '此配置项将在重启应用后生效', - 'startAll': '全部开始', - 'pauseAll': '全部暂停', - 'deleteTask': '删除任务', - 'deleteTaskTip': '保留已下载的文件', - 'delete': '删除', - 'newVersionTitle': '发现新版本 @version', - 'newVersionUpdate': '立即更新', - 'newVersionLater': '稍后再说', - 'extensions': '扩展', - 'extensionInstallUrl': '安装链接', - 'extensionInstallSuccess': '安装成功', - 'extensionUpdateSuccess': '更新成功', - 'extensionDelete': '删除扩展', - 'extensionAlreadyLatest': '已经是最新版本', - 'extensionFind': '获取扩展', - 'extensionDevelop': '开发扩展', - 'history': '历史记录', - 'clearHistory': '清空历史记录', - 'noHistoryFound': '暂无历史记录', - 'serviceTitle': '下载服务', - 'serviceText': '运行中', - }; - static const zh_TW = { - 'label': '中文 (繁體) ', - 'error': '錯誤', - 'tip': '提示', - 'confirm': '確定', - 'cancel': '取消', - 'selectAll': '全選', - 'task': '任務', - 'downloading': '下載中', - 'downloaded': '已完成', - 'setting': '設定', - 'donate': '斗內', - 'exit': '離開', - 'create': '新增任務', - 'directDownload': '直接下載', - 'advancedOptions': '進階選項', - 'downloadLink': '下載鏈接', - 'downloadLinkValid': '請輸入下載鏈接', - 'downloadLinkHit': '請輸入下載鏈接,支援 HTTP/HTTPS/MAGNET@append', - 'downloadLinkHitDesktop': ',也可以通過拖拽Torrent文件至此以新增任務', - 'download': '下載', - 'noFileSelected': '沒有選取的文件', - 'noStoragePermission': '沒有存儲權限', - 'selectFile': '選擇文件', - 'rename': '重命名', - 'basic': '基礎', - 'advanced': '進階', - 'general': '通用設定', - 'downloadDir': '下載目録', - 'downloadDirValid': '請選擇下載目録', - 'connections': '同時連接數', - 'maxRunning': '最大同時下載數', - 'items': '@count 項', - 'subscribeTracker': '已訂閲的追蹤者列表', - 'subscribeFail': '訂閲失敗', - 'update': '更新', - 'updateDaily': '每天自動更新', - 'lastUpdate': '上次更新時間: @time', - 'addTracker': '增加新的追蹤者', - 'addTrackerHit': '請輸入追蹤者服務器地址,每行一條', - 'ui': '介面', - 'theme': '佈景主題', - 'themeSystem': '跟隨系統', - 'themeLight': '亮色主題', - 'themeDark': '深色主題', - 'locale': '語言', - 'about': '關於', - 'homepage': '訪問主頁', - 'version': '版本資訊', - 'protocol': '端口設定', - 'port': '端口', - 'apiToken': '接口秘鑰', - 'notSet': '未設定', - 'set': '已設定', - 'effectAfterRestart': '重啟後生效', - 'startAll': '全部開始', - 'pauseAll': '全部暫停', - 'deleteTask': '刪除任務', - 'deleteTaskTip': '保留已經下載的文件', - 'delete': '刪除', - 'newVersionTitle': '發現新版本 @version', - 'newVersionUpdate': '立刻更新', - 'newVersionLater': '稍後更新', - 'extensions': '外掛程序', - 'extensionInstallUrl': '安裝URL', - 'extensionInstallSuccess': '安裝成功', - 'extensionUpdateSuccess': '更新完成', - 'extensionDelete': '解除安裝', - 'extensionAlreadyLatest': '已是最新版本', - 'extensionFind': '取得外掛', - 'extensionDevelop': '開發外掛', - 'history': '歷史記錄', - 'clearHistory': '清空記錄', - 'noHistoryFound': '還沒有記錄', - 'serviceTitle': '下載服務', - 'serviceText': '執行中', - }; -} diff --git a/ui/flutter/lib/i18n/langs/en_us.dart b/ui/flutter/lib/i18n/langs/en_us.dart new file mode 100644 index 000000000..662f7d386 --- /dev/null +++ b/ui/flutter/lib/i18n/langs/en_us.dart @@ -0,0 +1,84 @@ +const enUS = { + 'en_US': { + 'label': 'English', + 'error': 'Error', + 'tip': 'Tip', + 'confirm': 'Confirm', + 'cancel': 'Cancel', + 'selectAll': 'Select All', + 'task': 'Tasks', + 'downloading': 'downloading', + 'downloaded': 'downloaded', + 'setting': 'Settings', + 'donate': 'Donate', + 'exit': 'Exit', + 'create': 'Create Task', + 'directDownload': 'Direct Download', + 'advancedOptions': 'Advanced Options', + 'downloadLink': 'Download Link', + 'downloadLinkValid': 'Please enter the download link', + 'downloadLinkHit': + 'Please enter the download link, HTTP/HTTPS/MAGNET supported@append', + 'downloadLinkHitDesktop': ', or drag the torrent file here directly', + 'download': 'Download', + 'noFileSelected': 'Please select at least one file to continue.', + 'noStoragePermission': 'Storage permission required', + 'selectFile': 'Select File', + 'rename': 'Rename', + 'basic': 'Basic', + 'advanced': 'Advanced', + 'general': 'General', + 'downloadDir': 'Download Directory', + 'downloadDirValid': 'Please select the download directory', + 'connections': 'Connections', + 'maxRunning': 'Max Running Tasks', + 'items': '@count items', + 'subscribeTracker': 'Subscribe Tracker', + 'subscribeFail': + 'Subscribe failed, please check network or try again later', + 'update': 'Update', + 'updateDaily': 'Update daily', + 'lastUpdate': 'Last update: @time', + 'addTracker': 'Add Tracker', + 'addTrackerHit': 'Please enter the tracker server url, one per line', + 'ui': 'UI', + 'theme': 'Theme', + 'themeSystem': 'System', + 'themeLight': 'Light', + 'themeDark': 'Dark', + 'locale': 'Language', + 'about': 'About', + 'homepage': 'Homepage', + 'version': 'Version', + 'protocol': 'Protocol', + 'port': 'Port', + 'apiToken': 'API Token', + 'notSet': 'NS', + 'set': 'SET', + 'effectAfterRestart': 'Effect after restart', + 'startAll': 'Start All', + 'pauseAll': 'Pause All', + 'deleteTask': 'Delete Task', + 'deleteTaskTip': 'Keep downloaded files', + 'delete': 'Delete', + 'newVersionTitle': 'Discover new version @version', + 'newVersionUpdate': 'Update Now', + 'newVersionLater': 'Later', + 'extensions': 'Extensions', + 'extensionInstallUrl': 'Install URL', + 'extensionInstallSuccess': 'Installed successfully', + 'extensionUpdateSuccess': 'Updated successfully', + 'extensionDelete': 'Delete Extension', + 'extensionAlreadyLatest': 'It\'s already the latest version', + 'extensionFind': 'Find Extensions', + 'extensionDevelop': 'Develop Extensions', + 'history': 'History', + 'clearHistory': 'Clear History', + 'noHistoryFound': 'No History Found', + 'serviceTitle': 'Download Service', + 'serviceText': 'Running', + 'proxy': 'Proxy', + 'username': 'Username', + 'password': 'Password', + }, +}; diff --git a/ui/flutter/lib/i18n/langs/fa_ir.dart b/ui/flutter/lib/i18n/langs/fa_ir.dart new file mode 100644 index 000000000..c72062eef --- /dev/null +++ b/ui/flutter/lib/i18n/langs/fa_ir.dart @@ -0,0 +1,61 @@ +const faIR = { + "fa_IR": { + 'label': 'فارسی', + 'error': 'اررور', + 'tip': 'Tip', + 'confirm': 'تایید', + 'cancel': 'انصراف', + 'selectAll': 'انتخاب همه', + 'task': 'کار', + 'setting': 'تنظیمات', + 'donate': 'کمک مالی', + 'exit': 'خروج', + 'create': 'ایجاد کار', + 'downloading': 'دانلود کردن', + 'downloaded': 'دانلود شدن', + 'downloadLink': 'لینک دانلود', + 'downloadLinkValid': 'لطفا لینک دانلود را وارد کنید', + 'download': 'دانلود', + 'noFileSelected': 'لطفا حداقل یک فایل را جهت دانلود انتخاب کنید.', + 'noStoragePermission': 'مجوز ذخیره سازی مورد نیاز است', + 'selectFile': 'انتخاب فایل', + 'basic': 'پایه', + 'advanced': 'پیشرفته', + 'general': 'عمومی', + 'downloadDir': 'دایرکتوری دانلود', + 'downloadDirValid': 'لطفا دایرکتوری دانلود را انتخاب کنید', + 'connections': 'اتصالات', + 'items': '@count آیتم ها', + 'subscribeTracker': 'Subscribe Tracker', + 'subscribeFail': + 'Subscribe failed, please check network or try again later', + 'update': 'بروزرسانی', + 'updateDaily': 'بروزرسانی روزانه', + 'lastUpdate': 'Last update: @time', + 'addTracker': 'Add Tracker', + 'addTrackerHit': 'Please enter the tracker server url, one per line', + 'ui': 'رابط کاربری', + 'theme': 'پوسته', + 'themeSystem': 'سیستم', + 'themeLight': 'روشن', + 'themeDark': 'تاریک', + 'locale': 'Language', + 'about': 'درباره ی', + 'homepage': 'صفحه اصلی', + 'version': 'نسخه', + 'protocol': 'پروتکل', + 'port': 'پورت', + 'apiToken': 'API توکن', + 'notSet': 'NS', + 'set': 'SET', + 'effectAfterRestart': 'Effect after restart', + 'startAll': 'شروع همه', + 'pauseAll': 'توقف همه', + 'deleteTask': 'پاک کردن کار', + 'deleteTaskTip': 'فایل های دانلود شده را نگه دارد', + 'delete': 'پاک کردن', + 'newVersionTitle': '@version عنوان: کشف نسخه جدید', + 'newVersionUpdate': 'بروزرسانی', + 'newVersionLater': 'بعداً', + } +}; diff --git a/ui/flutter/lib/i18n/langs/ja_jp.dart b/ui/flutter/lib/i18n/langs/ja_jp.dart new file mode 100644 index 000000000..e93540516 --- /dev/null +++ b/ui/flutter/lib/i18n/langs/ja_jp.dart @@ -0,0 +1,63 @@ +const jaJP = { + "ja_JP": { + 'label': '日本語', + 'error': 'エラー', + 'tip': 'ヒント', + 'confirm': '確認', + 'cancel': 'キャンセル', + 'selectAll': 'すべてを選択', + 'task': 'タスク', + 'downloading': 'ダウンロード中', + 'downloaded': 'ダウンロード済', + 'setting': 'セッティング', + 'donate': '寄付する', + 'exit': '終了', + 'create': 'タスクを作成', + 'downloadLink': 'ダウンロードリンク', + 'downloadLinkValid': 'ダウンロードリンクを入力してください', + 'downloadLinkHit': 'ダウンロード リンクを入力してください、HTTP/HTTPS/MAGNET support@append', + 'downloadLinkHitDesktop': 'または、torrent ファイルを直接ここにドラッグします', + 'download': 'ダウンロード', + 'noFileSelected': '続行するには少なくとも 1 つのファイルを選択してください。', + 'noStoragePermission': 'ストレージのパーミッションが必要', + 'selectFile': 'ファイルを選択', + 'basic': 'ベーシック', + 'advanced': 'アドバンスド', + 'general': 'ジェネラル', + 'downloadDir': 'ディレクトリのダウンロード', + 'downloadDirValid': 'ダウンロードディレクトリを選択してください', + 'connections': '接続', + 'maxRunning': '最大実行タスク', + 'items': '@count アイテム', + 'subscribeTracker': 'トラッカーを購読', + 'subscribeFail': '登録に失敗しました。ネットワークを確認するか、後でもう一度お試しください', + 'update': 'アップデート', + 'updateDaily': '毎日アップデート', + 'lastUpdate': '最終アップデート: @time', + 'addTracker': 'トラッカーを追加', + 'addTrackerHit': 'トラッカーサーバーの URL を 1 行に 1 つずつ入力してください', + 'ui': 'UI', + 'theme': 'テーマ', + 'themeSystem': 'システム', + 'themeLight': 'ライト', + 'themeDark': 'ダーク', + 'locale': '言語', + 'about': '概要', + 'homepage': 'ホームページ', + 'version': 'バージョン', + 'protocol': 'プロトコル', + 'port': 'ポート', + 'apiToken': 'API トークン', + 'notSet': '未設置', + 'set': '設置', + 'effectAfterRestart': '再起動後の効果', + 'startAll': 'すべてを開始', + 'pauseAll': 'すべてを一時停止', + 'deleteTask': 'タスクを削除', + 'deleteTaskTip': 'ダウンロードしたファイルを保持', + 'delete': '削除', + 'newVersionTitle': '新しいバージョン @version を発見する', + 'newVersionUpdate': 'アップデート', + 'newVersionLater': '後で', + } +}; diff --git a/ui/flutter/lib/i18n/langs/ru_ru.dart b/ui/flutter/lib/i18n/langs/ru_ru.dart new file mode 100644 index 000000000..dba5433db --- /dev/null +++ b/ui/flutter/lib/i18n/langs/ru_ru.dart @@ -0,0 +1,77 @@ +const ruRU = { + 'ru_RU': { + 'label': 'Русский(Россия)', + 'error': 'Ошибка', + 'tip': 'Подсказка', + 'confirm': 'Подтвердить', + 'cancel': 'Отмена', + 'selectAll': 'Выбрать все', + 'task': 'Задачи', + 'downloading': 'Загрузка', + 'downloaded': 'Загружено', + 'setting': 'Настройки', + 'donate': 'Пожертвовать', + 'exit': 'Выход', + 'create': 'Создать задачу', + 'advancedOptions': 'Расширенные опции', + 'downloadLink': 'Ссылка на скачивание', + 'downloadLinkValid': 'Пожалуйста, введите ссылку для скачивания', + 'downloadLinkHit': + 'Пожалуйста, введите ссылку для скачивания, HTTP/HTTPS/MAGNET поддерживаются@append', + 'downloadLinkHitDesktop': ', или перетащите сюда торрент-файл', + 'download': 'Скачать', + 'noFileSelected': 'Файл не выбран', + 'noStoragePermission': 'Требуется доступ к хранилищу', + 'selectFile': 'Выбрать файл', + 'rename': 'Переименовать', + 'basic': 'Основные параметры', + 'advanced': 'Расширенные', + 'general': 'Общие', + 'downloadDir': 'Папка загрузки', + 'downloadDirValid': 'Пожалуйста, выберите папку для загрузки', + 'connections': 'Соединений', + 'maxRunning': 'Максимальное количество активных задач', + 'items': '@подсчет элементов', + 'subscribeTracker': 'Подпись трекера', + 'subscribeFail': + 'Подписка не удалась, пожалуйста, проверьте сеть или повторите попытку позже', + 'update': 'Обновить', + 'updateDaily': 'Обновлять ежедневно', + 'lastUpdate': 'Последнее обновление: @время', + 'addTracker': 'Добавить трекер', + 'addTrackerHit': 'Пожалуйста, введите url трекера, по одному в строке', + 'ui': 'UI', + 'theme': 'Тема', + 'themeSystem': 'Системная', + 'themeLight': 'Светлая', + 'themeDark': 'Темная', + 'locale': 'Язык', + 'about': 'О приложении', + 'homepage': 'Домашняя страница', + 'version': 'Версия', + 'protocol': 'Протокол', + 'port': 'Порт', + 'apiToken': 'API Токен', + 'notSet': 'Не установлено', + 'set': 'Установлено', + 'effectAfterRestart': 'Эффект после перезагрузки', + 'startAll': 'Запустить все', + 'pauseAll': 'Приостановить все', + 'deleteTask': 'Удалить задачу', + 'deleteTaskTip': 'Сохранить загруженные файлы', + 'delete': 'Удалить', + 'newVersionTitle': 'Обнаружена новая версия @version', + 'newVersionUpdate': 'Обновить', + 'newVersionLater': 'позже', + 'extensions': 'Расширения', + 'extensionInstallSuccess': 'Установка завершена', + 'extensionUpdateSuccess': 'Обновление завершено', + 'extensionDelete': 'Удалить расширение', + 'extensionAlreadyLatest': 'Это последняя версия', + 'extensionFind': 'Найти расширения', + 'extensionDevelop': 'Разработка расширений', + 'history': 'История', + 'clearHistory': 'Очистить историю', + 'noHistoryFound': 'История не найдена', + } +}; diff --git a/ui/flutter/lib/i18n/langs/zh_cn.dart b/ui/flutter/lib/i18n/langs/zh_cn.dart new file mode 100644 index 000000000..15c7779eb --- /dev/null +++ b/ui/flutter/lib/i18n/langs/zh_cn.dart @@ -0,0 +1,82 @@ +const zhCN = { + 'zh_CN': { + 'label': '中文(简体)', + 'error': '错误', + 'tip': '提示', + 'confirm': '确认', + 'cancel': '取消', + 'selectAll': '全选', + 'task': '任务', + 'downloading': '下载中', + 'downloaded': '已完成', + 'setting': '设置', + 'donate': '打赏', + 'exit': '退出', + 'create': '创建任务', + 'directDownload': '直接下载', + 'advancedOptions': '高级选项', + 'downloadLink': '下载链接', + 'downloadLinkValid': '请输入下载链接', + 'downloadLinkHit': '请输入下载链接,支持 HTTP/HTTPS/MAGNET@append', + 'downloadLinkHitDesktop': ',也可以直接拖拽种子文件到此处', + 'download': '下载', + 'noFileSelected': '请至少选择一个文件下载', + 'noStoragePermission': '需要开启存储权限', + 'selectFile': '选择文件', + 'rename': '重命名', + 'basic': '基础', + 'advanced': '高级', + 'general': '通用', + 'downloadDir': '下载目录', + 'downloadDirValid': '请选择下载目录', + 'connections': '连接数', + 'maxRunning': '最大下载数', + 'items': '@count 项', + 'subscribeTracker': '订阅 Tracker', + 'subscribeFail': '订阅失败,请检查网络或稍后重试', + 'update': '更新', + 'updateDaily': '每天自动更新', + 'lastUpdate': '上次更新:@time', + 'addTracker': '添加 Tracker', + 'addTrackerHit': '请输入 tracker 服务器地址,每行一条', + 'ui': '界面', + 'theme': '主题', + 'themeSystem': '跟随系统', + 'themeLight': '明亮主题', + 'themeDark': '暗黑主题', + 'locale': '语言', + 'about': '关于', + 'homepage': '主页', + 'version': '版本', + 'protocol': '通讯协议', + 'port': '端口', + 'apiToken': '接口令牌', + 'notSet': '未设置', + 'set': '已设置', + 'effectAfterRestart': '此配置项将在重启应用后生效', + 'startAll': '全部开始', + 'pauseAll': '全部暂停', + 'deleteTask': '删除任务', + 'deleteTaskTip': '保留已下载的文件', + 'delete': '删除', + 'newVersionTitle': '发现新版本 @version', + 'newVersionUpdate': '立即更新', + 'newVersionLater': '稍后再说', + 'extensions': '扩展', + 'extensionInstallUrl': '安装链接', + 'extensionInstallSuccess': '安装成功', + 'extensionUpdateSuccess': '更新成功', + 'extensionDelete': '删除扩展', + 'extensionAlreadyLatest': '已经是最新版本', + 'extensionFind': '获取扩展', + 'extensionDevelop': '开发扩展', + 'history': '历史记录', + 'clearHistory': '清空历史记录', + 'noHistoryFound': '暂无历史记录', + 'serviceTitle': '下载服务', + 'serviceText': '运行中', + 'proxy': '代理', + 'username': '用户名', + 'password': '密码' + } +}; diff --git a/ui/flutter/lib/i18n/langs/zh_tw.dart b/ui/flutter/lib/i18n/langs/zh_tw.dart new file mode 100644 index 000000000..dbaa48093 --- /dev/null +++ b/ui/flutter/lib/i18n/langs/zh_tw.dart @@ -0,0 +1,79 @@ +const zhTW = { + "zh_TW": { + 'label': '中文 (繁體) ', + 'error': '錯誤', + 'tip': '提示', + 'confirm': '確定', + 'cancel': '取消', + 'selectAll': '全選', + 'task': '任務', + 'downloading': '下載中', + 'downloaded': '已完成', + 'setting': '設定', + 'donate': '斗內', + 'exit': '離開', + 'create': '新增任務', + 'directDownload': '直接下載', + 'advancedOptions': '進階選項', + 'downloadLink': '下載鏈接', + 'downloadLinkValid': '請輸入下載鏈接', + 'downloadLinkHit': '請輸入下載鏈接,支援 HTTP/HTTPS/MAGNET@append', + 'downloadLinkHitDesktop': ',也可以通過拖拽Torrent文件至此以新增任務', + 'download': '下載', + 'noFileSelected': '沒有選取的文件', + 'noStoragePermission': '沒有存儲權限', + 'selectFile': '選擇文件', + 'rename': '重命名', + 'basic': '基礎', + 'advanced': '進階', + 'general': '通用設定', + 'downloadDir': '下載目録', + 'downloadDirValid': '請選擇下載目録', + 'connections': '同時連接數', + 'maxRunning': '最大同時下載數', + 'items': '@count 項', + 'subscribeTracker': '已訂閲的追蹤者列表', + 'subscribeFail': '訂閲失敗', + 'update': '更新', + 'updateDaily': '每天自動更新', + 'lastUpdate': '上次更新時間: @time', + 'addTracker': '增加新的追蹤者', + 'addTrackerHit': '請輸入追蹤者服務器地址,每行一條', + 'ui': '介面', + 'theme': '佈景主題', + 'themeSystem': '跟隨系統', + 'themeLight': '亮色主題', + 'themeDark': '深色主題', + 'locale': '語言', + 'about': '關於', + 'homepage': '訪問主頁', + 'version': '版本資訊', + 'protocol': '端口設定', + 'port': '端口', + 'apiToken': '接口秘鑰', + 'notSet': '未設定', + 'set': '已設定', + 'effectAfterRestart': '重啟後生效', + 'startAll': '全部開始', + 'pauseAll': '全部暫停', + 'deleteTask': '刪除任務', + 'deleteTaskTip': '保留已經下載的文件', + 'delete': '刪除', + 'newVersionTitle': '發現新版本 @version', + 'newVersionUpdate': '立刻更新', + 'newVersionLater': '稍後更新', + 'extensions': '外掛程序', + 'extensionInstallUrl': '安裝URL', + 'extensionInstallSuccess': '安裝成功', + 'extensionUpdateSuccess': '更新完成', + 'extensionDelete': '解除安裝', + 'extensionAlreadyLatest': '已是最新版本', + 'extensionFind': '取得外掛', + 'extensionDevelop': '開發外掛', + 'history': '歷史記錄', + 'clearHistory': '清空記錄', + 'noHistoryFound': '還沒有記錄', + 'serviceTitle': '下載服務', + 'serviceText': '執行中', + } +}; diff --git a/ui/flutter/lib/i18n/message.dart b/ui/flutter/lib/i18n/message.dart new file mode 100644 index 000000000..12f463ef6 --- /dev/null +++ b/ui/flutter/lib/i18n/message.dart @@ -0,0 +1,23 @@ +import 'package:get/get.dart'; +import 'package:gopeed/i18n/langs/fa_ir.dart'; +import 'package:gopeed/i18n/langs/ja_jp.dart'; +import 'package:gopeed/i18n/langs/zh_tw.dart'; + +import 'langs/en_us.dart'; +import 'langs/ru_ru.dart'; +import 'langs/zh_cn.dart'; + +final messages = _Messages(); + +class _Messages extends Translations { + // just include available locales here + @override + Map> get keys => { + ...zhCN, + ...enUS, + ...ruRU, + ...zhTW, + ...faIR, + ...jaJP, + }; +} diff --git a/ui/flutter/lib/main.dart b/ui/flutter/lib/main.dart index 8ca0359b8..060e83f79 100644 --- a/ui/flutter/lib/main.dart +++ b/ui/flutter/lib/main.dart @@ -7,7 +7,7 @@ import 'api/api.dart' as api; import 'app/modules/app/controllers/app_controller.dart'; import 'app/modules/app/views/app_view.dart'; import 'core/libgopeed_boot.dart'; -import 'generated/locales.g.dart'; +import 'i18n/message.dart'; import 'util/locale_manager.dart'; import 'util/log_util.dart'; import 'util/mac_secure_util.dart'; @@ -72,12 +72,10 @@ Future onStart() async { // if is debug mode, check language message is complete,change debug locale to your comfortable language if you want if (kDebugMode) { - final mainLang = getLocaleKey(debugLocale); - final fullMessages = AppTranslation.translations[mainLang]; - AppTranslation.translations.keys - .where((e) => e != mainLang) - .forEach((lang) { - final langMessages = AppTranslation.translations[lang]; + final debugLang = getLocaleKey(debugLocale); + final fullMessages = messages.keys[debugLang]; + messages.keys.keys.where((e) => e != debugLang).forEach((lang) { + final langMessages = messages.keys[lang]; if (langMessages == null) { logger.w("missing language: $lang"); return;