Files
AdGuardHome/scripts/translations/download.go
Elizaveta Egorova b00d982884 AGDNS-3312 Add blocked services group separation
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: c70212ec7 ff0ef4f39
Author: 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

commit 1a865f0856
Author: 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
    
    commit c2e1b8edee
    Author: Eugene Burkov <E.Burkov@AdGuard.COM>
    Date:   Mon Nov 10 18:03:23 2025 +0300
    
        all: imp translations script
    
    commit c930dcff36
    Author: Eugene Burkov <E.Burkov@AdGuard.COM>
    Date:   Mon Nov 10 17:52:44 2025 +0300
    
        scripts: revert master

commit c28e13b4e1
Author: Elizaveta <e.egorova@adguard.com>
Date:   Wed Nov 5 16:43:13 2025 +0300

    move project key const

commit ee977f5993
Author: Elizaveta <e.egorova@adguard.com>
Date:   Wed Nov 5 16:25:34 2025 +0300

    update download script

commit 244c1011b0
Author: Elizaveta <e.egorova@adguard.com>
Date:   Sat Nov 1 15:59:04 2025 +0300

    restore master toggle

commit 40418ea8cf
Author: Elizaveta <e.egorova@adguard.com>
Date:   Sat Nov 1 14:59:57 2025 +0300

    update saveToFile func

commit e4f3c677d4
Author: Elizaveta <e.egorova@adguard.com>
Date:   Sat Nov 1 14:52:14 2025 +0300

    go linter

commit 5359590953
Author: Elizaveta <e.egorova@adguard.com>
Date:   Sat Nov 1 14:44:39 2025 +0300

    remove en group tr from __locales

commit a91215dfad
Author: Elizaveta <e.egorova@adguard.com>
Date:   Sat Nov 1 14:43:39 2025 +0300

    add translations

commit c65f80048d
Author: Elizaveta <e.egorova@adguard.com>
Date:   Sat Nov 1 12:15:07 2025 +0300

    fix ui

commit 893433bd8f
Author: Elizaveta <e.egorova@adguard.com>
Date:   Fri Oct 31 17:22:44 2025 +0300

    fix comment

commit 26148996c9
Author: Elizaveta <e.egorova@adguard.com>
Date:   Fri Oct 31 16:49:46 2025 +0300

    styles

commit 2785958252
Author: Elizaveta <e.egorova@adguard.com>
Date:   Fri Oct 31 16:48:46 2025 +0300

    linter

... and 6 more commits
2025-11-19 14:29:26 +03:00

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
}