mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2025-12-20 01:11:03 +08:00
Squashed commit of the following: commit 0c931c596316aa3322a8123877650a53b2ba31c3 Author: Elizaveta <e.egorova@adguard.com> Date: Wed Nov 19 14:14:04 2025 +0300 spaces commit 90b8bed07e392c32f2a4c963329563250ca06100 Author: Elizaveta <e.egorova@adguard.com> Date: Wed Nov 19 13:55:56 2025 +0300 update changelog commit 1a0dea21ccdc82603da4f42bd5396b8cbb29160a Merge: c70212ec7ff0ef4f39Author: Elizaveta <e.egorova@adguard.com> Date: Wed Nov 19 13:54:39 2025 +0300 Merge branch 'master' into AGDNS-3312 commit c70212ec761714528216f5f5dafe1f6683c480b5 Author: Elizaveta <e.egorova@adguard.com> Date: Tue Nov 18 17:21:26 2025 +0300 remove redundant line commit b08a8d0341fd7a042f63f960c784a11dce915a18 Author: Elizaveta <e.egorova@adguard.com> Date: Tue Nov 18 14:02:32 2025 +0300 lint commit c78f40bc6986a64b6a8606790b2307076f5b12c8 Author: Elizaveta <e.egorova@adguard.com> Date: Tue Nov 18 14:02:07 2025 +0300 update translations commit dd53e8b47361fa1b138151dc504fe5ee138391c4 Author: Elizaveta <e.egorova@adguard.com> Date: Tue Nov 18 13:55:27 2025 +0300 remove accordion commit af2bd3bdfd71d93fcf83d7fa836d85c319293d6d Author: Elizaveta <e.egorova@adguard.com> Date: Fri Nov 14 16:09:22 2025 +0300 linter commit 19eb2bfdc12f9c2a4c4f525027811719a72da214 Author: Elizaveta <e.egorova@adguard.com> Date: Fri Nov 14 16:07:48 2025 +0300 fix commit fc9a32299e778270354ce3fadd046437cda63a0d Author: Elizaveta <e.egorova@adguard.com> Date: Fri Nov 14 14:29:29 2025 +0300 fix translations commit 9c4dbc5fdebe00f4aa545172ffd733b71028a243 Author: Elizaveta <e.egorova@adguard.com> Date: Thu Nov 13 19:22:51 2025 +0300 linter commit 90107c52fa8391294201ecf5cfb437716e7d1eca Author: Elizaveta <e.egorova@adguard.com> Date: Thu Nov 13 18:07:26 2025 +0300 fix styles commit 4dd170e91a14dcab5f7b168cdf6f410847ad1dda Author: Elizaveta <e.egorova@adguard.com> Date: Thu Nov 13 18:01:52 2025 +0300 update translations commit1a865f0856Author: Eugene Burkov <e.burkov@adguard.com> Date: Wed Nov 12 16:06:19 2025 +0300 Pull request 2523: AGDNS-3398-imp-translations-script Squashed commit of the following: commit 3753e6d308c9dbe23d1c7c41cf57457b364cb253 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Wed Nov 12 15:43:55 2025 +0300 translations: imp code, docs commit 5389ec67eddbe33a99c3c893cb5b63c131d34e84 Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Wed Nov 12 14:14:08 2025 +0300 all: fix lint, imp code commitc2e1b8edeeAuthor: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Nov 10 18:03:23 2025 +0300 all: imp translations script commitc930dcff36Author: Eugene Burkov <E.Burkov@AdGuard.COM> Date: Mon Nov 10 17:52:44 2025 +0300 scripts: revert master commitc28e13b4e1Author: Elizaveta <e.egorova@adguard.com> Date: Wed Nov 5 16:43:13 2025 +0300 move project key const commitee977f5993Author: Elizaveta <e.egorova@adguard.com> Date: Wed Nov 5 16:25:34 2025 +0300 update download script commit244c1011b0Author: Elizaveta <e.egorova@adguard.com> Date: Sat Nov 1 15:59:04 2025 +0300 restore master toggle commit40418ea8cfAuthor: Elizaveta <e.egorova@adguard.com> Date: Sat Nov 1 14:59:57 2025 +0300 update saveToFile func commite4f3c677d4Author: Elizaveta <e.egorova@adguard.com> Date: Sat Nov 1 14:52:14 2025 +0300 go linter commit5359590953Author: Elizaveta <e.egorova@adguard.com> Date: Sat Nov 1 14:44:39 2025 +0300 remove en group tr from __locales commita91215dfadAuthor: Elizaveta <e.egorova@adguard.com> Date: Sat Nov 1 14:43:39 2025 +0300 add translations commitc65f80048dAuthor: Elizaveta <e.egorova@adguard.com> Date: Sat Nov 1 12:15:07 2025 +0300 fix ui commit893433bd8fAuthor: Elizaveta <e.egorova@adguard.com> Date: Fri Oct 31 17:22:44 2025 +0300 fix comment commit26148996c9Author: Elizaveta <e.egorova@adguard.com> Date: Fri Oct 31 16:49:46 2025 +0300 styles commit2785958252Author: Elizaveta <e.egorova@adguard.com> Date: Fri Oct 31 16:48:46 2025 +0300 linter ... and 6 more commits
214 lines
4.9 KiB
Go
214 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/AdguardTeam/golibs/errors"
|
|
"github.com/AdguardTeam/golibs/ioutil"
|
|
"github.com/AdguardTeam/golibs/logutil/slogutil"
|
|
"github.com/AdguardTeam/golibs/netutil"
|
|
"github.com/AdguardTeam/golibs/syncutil"
|
|
"github.com/AdguardTeam/golibs/validate"
|
|
)
|
|
|
|
// download and save all translations.
|
|
func (c *twoskyClient) download(ctx context.Context, l *slog.Logger) {
|
|
numWorker, err := parseDownloadArgs()
|
|
if err != nil {
|
|
usage(err.Error())
|
|
}
|
|
|
|
downloadURI := c.uri.JoinPath("download")
|
|
|
|
wg := &sync.WaitGroup{}
|
|
reqCh := make(chan downloadRequest, numWorker)
|
|
|
|
dw := &downloadWorker{
|
|
ctx: ctx,
|
|
l: l,
|
|
failed: syncutil.NewMap[string, struct{}](),
|
|
client: &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
},
|
|
reqCh: reqCh,
|
|
}
|
|
|
|
for range numWorker {
|
|
wg.Go(dw.run)
|
|
}
|
|
|
|
for _, baseFile := range c.localizableFiles {
|
|
dir, file := filepath.Split(baseFile)
|
|
|
|
for _, lang := range c.langs {
|
|
uri := translationURL(downloadURI, file, c.projectID, lang)
|
|
|
|
reqCh <- downloadRequest{
|
|
uri: uri,
|
|
dir: dir,
|
|
}
|
|
}
|
|
}
|
|
|
|
close(reqCh)
|
|
wg.Wait()
|
|
|
|
printFailedLocales(ctx, l, dw.failed)
|
|
}
|
|
|
|
// parseDownloadArgs parses command-line arguments for the download command.
|
|
func parseDownloadArgs() (numWorker int, err error) {
|
|
flagSet := flag.NewFlagSet("download", flag.ExitOnError)
|
|
flagSet.IntVar(&numWorker, "n", 1, "number of concurrent downloads")
|
|
|
|
err = flagSet.Parse(os.Args[2:])
|
|
if err != nil {
|
|
// Don't wrap the error since it's informative enough as is.
|
|
return 0, err
|
|
}
|
|
|
|
return numWorker, validate.Positive("count", numWorker)
|
|
}
|
|
|
|
// printFailedLocales prints sorted list of failed downloads, if any. l and
|
|
// failed must not be nil.
|
|
func printFailedLocales(
|
|
ctx context.Context,
|
|
l *slog.Logger,
|
|
failed *syncutil.Map[string, struct{}],
|
|
) {
|
|
var keys []string
|
|
for k := range failed.Range {
|
|
keys = append(keys, k)
|
|
}
|
|
|
|
if len(keys) == 0 {
|
|
return
|
|
}
|
|
|
|
slices.Sort(keys)
|
|
|
|
l.InfoContext(ctx, "failed", "locales", keys)
|
|
}
|
|
|
|
// downloadWorker is a worker for downloading translations. It uses URLs
|
|
// received from the channel to download translations and save them to files.
|
|
// Failures are stored in the failed map. All fields must not be nil.
|
|
type downloadWorker struct {
|
|
ctx context.Context
|
|
l *slog.Logger
|
|
failed *syncutil.Map[string, struct{}]
|
|
client *http.Client
|
|
reqCh <-chan downloadRequest
|
|
}
|
|
|
|
// downloadRequest is a request to download a translation. All fields must not
|
|
// be empty.
|
|
type downloadRequest struct {
|
|
uri *url.URL
|
|
dir string
|
|
}
|
|
|
|
// run handles the channel of URLs, one by one. It returns when the channel is
|
|
// closed. It's used to be run in a separate goroutine.
|
|
func (w *downloadWorker) run() {
|
|
for req := range w.reqCh {
|
|
q := req.uri.Query()
|
|
code := q.Get("language")
|
|
|
|
err := saveToFile(w.ctx, w.l, w.client, req.uri, code, req.dir)
|
|
if err != nil {
|
|
w.l.ErrorContext(w.ctx, "download worker", slogutil.KeyError, err)
|
|
w.failed.Store(code, struct{}{})
|
|
}
|
|
}
|
|
}
|
|
|
|
// saveToFile downloads translation by url and saves it to a file, or returns
|
|
// error.
|
|
func saveToFile(
|
|
ctx context.Context,
|
|
l *slog.Logger,
|
|
client *http.Client,
|
|
uri *url.URL,
|
|
code string,
|
|
localesDir string,
|
|
) (err error) {
|
|
data, err := getTranslation(ctx, l, client, uri.String())
|
|
if err != nil {
|
|
return fmt.Errorf("getting translation %q: %s", code, err)
|
|
}
|
|
|
|
if data[len(data)-1] != '\n' {
|
|
data = append(data, '\n')
|
|
}
|
|
|
|
name := filepath.Join(localesDir, code+".json")
|
|
err = os.WriteFile(name, data, 0o664)
|
|
if err != nil {
|
|
return fmt.Errorf("writing file: %s", err)
|
|
}
|
|
|
|
fmt.Println(name)
|
|
|
|
return nil
|
|
}
|
|
|
|
// getTranslation returns received translation data and error. If err is not
|
|
// nil, data may contain a response from server for inspection. Otherwise, the
|
|
// data is guaranteed to be non-empty.
|
|
func getTranslation(
|
|
ctx context.Context,
|
|
l *slog.Logger,
|
|
client *http.Client,
|
|
url string,
|
|
) (data []byte, err error) {
|
|
resp, err := client.Get(url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("requesting: %w", err)
|
|
}
|
|
|
|
defer slogutil.CloseAndLog(ctx, l, resp.Body, slog.LevelError)
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
err = fmt.Errorf("url: %q; status code: %s", url, http.StatusText(resp.StatusCode))
|
|
|
|
// Go on and download the body for inspection.
|
|
}
|
|
|
|
limitReader := ioutil.LimitReader(resp.Body, readLimit.Bytes())
|
|
|
|
data, readErr := io.ReadAll(limitReader)
|
|
if readErr != nil {
|
|
return nil, errors.WithDeferred(err, readErr)
|
|
}
|
|
|
|
return data, validate.NotEmptySlice("response", data)
|
|
}
|
|
|
|
// translationURL returns a new url.URL with provided query parameters.
|
|
func translationURL(baseURL *url.URL, baseFile, projectID string, lang langCode) (uri *url.URL) {
|
|
uri = netutil.CloneURL(baseURL)
|
|
|
|
q := uri.Query()
|
|
q.Set("format", "json")
|
|
q.Set("filename", baseFile)
|
|
q.Set("project", projectID)
|
|
q.Set("language", string(lang))
|
|
|
|
uri.RawQuery = q.Encode()
|
|
|
|
return uri
|
|
}
|