Add translation system for LURE CLI
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Elara 2023-01-12 19:41:52 -08:00
parent e772ecf2ab
commit 076f90bbd7
11 changed files with 295 additions and 41 deletions

View File

@ -187,13 +187,13 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
return nil, nil, err
}
err = cliutils.PromptViewScript(script, vars.Name, cfg.PagerStyle)
err = cliutils.PromptViewScript(script, vars.Name, cfg.PagerStyle, translator)
if err != nil {
log.Fatal("Failed to prompt user to view build script").Err(err).Send()
}
if !archMatches(vars.Architectures) {
buildAnyway, err := cliutils.YesNoPrompt("Your system's CPU architecture doesn't match this package. Do you want to build anyway?", true)
buildAnyway, err := cliutils.YesNoPrompt("Your system's CPU architecture doesn't match this package. Do you want to build anyway?", true, translator)
if err != nil {
return nil, nil, err
}
@ -268,7 +268,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
}
log.Info("Installing build dependencies").Send()
installPkgs(ctx, cliutils.FlattenPkgs(found, "install"), notFound, mgr)
installPkgs(ctx, cliutils.FlattenPkgs(found, "install", translator), notFound, mgr)
}
var builtDeps, builtNames, repoDeps []string
@ -280,7 +280,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
return nil, nil, err
}
scripts := getScriptPaths(cliutils.FlattenPkgs(found, "install"))
scripts := getScriptPaths(cliutils.FlattenPkgs(found, "install", translator))
for _, script := range scripts {
pkgPaths, pkgNames, err := buildPackage(ctx, script, mgr)
if err != nil {
@ -491,7 +491,7 @@ func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]st
}
if len(buildDeps) > 0 {
removeBuildDeps, err := cliutils.YesNoPrompt("Would you like to remove build dependencies?", false)
removeBuildDeps, err := cliutils.YesNoPrompt("Would you like to remove build dependencies?", false, translator)
if err != nil {
return nil, nil, err
}

9
go.mod
View File

@ -15,12 +15,14 @@ require (
github.com/mholt/archiver/v4 v4.0.0-alpha.7
github.com/mitchellh/mapstructure v1.5.0
github.com/muesli/reflow v0.3.0
github.com/pelletier/go-toml/v2 v2.0.5
github.com/pelletier/go-toml/v2 v2.0.6
github.com/twitchtv/twirp v8.1.3+incompatible
github.com/urfave/cli/v2 v2.16.3
go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668
github.com/urfave/cli/v2 v2.23.7
go.arsenm.dev/logger v0.0.0-20230104225304-d706171ea6df
go.arsenm.dev/translate v0.0.0-20230113025904-5ad1ec0ed296
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b
golang.org/x/sys v0.3.0
golang.org/x/text v0.6.0
google.golang.org/protobuf v1.27.1
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.20.0
@ -91,7 +93,6 @@ require (
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
lukechampine.com/uint128 v1.2.0 // indirect

21
go.sum
View File

@ -194,8 +194,8 @@ github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4Y
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -225,13 +225,15 @@ github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hg
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
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/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU=
@ -239,8 +241,8 @@ github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vF
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk=
github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY=
github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
@ -252,8 +254,10 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668 h1:7dSmQ79slzFpcii8zgQbEStxpkTPvq3tzWc7KX5uwGc=
go.arsenm.dev/logger v0.0.0-20221220032833-ba8a3cfb4668/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw=
go.arsenm.dev/logger v0.0.0-20230104225304-d706171ea6df h1:8mBHvEe7BJmpOeKSMA5YLqrGo9dCpePocTeR0C1+/2w=
go.arsenm.dev/logger v0.0.0-20230104225304-d706171ea6df/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw=
go.arsenm.dev/translate v0.0.0-20230113025904-5ad1ec0ed296 h1:uOJuOOn/sPe4YX9MD98tCoeLQTopIk17dJt0fwCeJrk=
go.arsenm.dev/translate v0.0.0-20230113025904-5ad1ec0ed296/go.mod h1:+rZV+tkYEPgZyP0OWBH477vWNwxN3pcAcukcjzgQjco=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
@ -297,8 +301,9 @@ golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View File

@ -27,6 +27,7 @@ import (
"github.com/urfave/cli/v2"
"go.arsenm.dev/lure/distro"
"go.arsenm.dev/lure/internal/cliutils"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/overrides"
"go.arsenm.dev/lure/internal/repos"
"gopkg.in/yaml.v3"
@ -52,7 +53,7 @@ func infoCmd(c *cli.Context) error {
os.Exit(1)
}
pkgs := cliutils.FlattenPkgs(found, "show")
pkgs := cliutils.FlattenPkgs(found, "show", translator)
var names []string
all := c.Bool("all")
@ -65,7 +66,7 @@ func infoCmd(c *cli.Context) error {
names, err = overrides.Resolve(
info,
overrides.DefaultOpts.
WithLanguages([]string{overrides.SystemLang()}),
WithLanguages([]string{config.SystemLang()}),
)
if err != nil {
log.Fatal("Error resolving overrides").Err(err).Send()

View File

@ -53,7 +53,7 @@ func installCmd(c *cli.Context) error {
log.Fatal("Error finding packages").Err(err).Send()
}
installPkgs(c.Context, cliutils.FlattenPkgs(found, "install"), notFound, mgr)
installPkgs(c.Context, cliutils.FlattenPkgs(found, "install", translator), notFound, mgr)
return nil
}

View File

@ -5,16 +5,18 @@ import (
"github.com/AlecAivazis/survey/v2"
"go.arsenm.dev/logger/log"
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/db"
"go.arsenm.dev/lure/internal/pager"
"go.arsenm.dev/translate"
)
// YesNoPrompt asks the user a yes or no question, using def as the default answer
func YesNoPrompt(msg string, def bool) (bool, error) {
func YesNoPrompt(msg string, def bool, t translate.Translator) (bool, error) {
var answer bool
err := survey.AskOne(
&survey.Confirm{
Message: msg,
Message: t.TranslateTo(msg, config.Language),
Default: def,
},
&answer,
@ -25,8 +27,8 @@ func YesNoPrompt(msg string, def bool) (bool, error) {
// PromptViewScript asks the user if they'd like to see a script,
// shows it if they answer yes, then asks if they'd still like to
// continue, and exits if they answer no.
func PromptViewScript(script, name, style string) error {
view, err := YesNoPrompt("Would you like to view the build script for "+name, false)
func PromptViewScript(script, name, style string, t translate.Translator) error {
view, err := YesNoPrompt(t.TranslateTo("Would you like to view the build script for", config.Language)+" "+name, false, t)
if err != nil {
return err
}
@ -37,13 +39,13 @@ func PromptViewScript(script, name, style string) error {
return err
}
cont, err := YesNoPrompt("Would you still like to continue?", false)
cont, err := YesNoPrompt("Would you still like to continue?", false, t)
if err != nil {
return err
}
if !cont {
log.Fatal("User chose not to continue after reading script").Send()
log.Fatal(t.TranslateTo("User chose not to continue after reading script", config.Language)).Send()
}
}
@ -70,11 +72,11 @@ func ShowScript(path, name, style string) error {
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
// of packages by prompting the user if multiple packages match.
func FlattenPkgs(found map[string][]db.Package, verb string) []db.Package {
func FlattenPkgs(found map[string][]db.Package, verb string, t translate.Translator) []db.Package {
var outPkgs []db.Package
for _, pkgs := range found {
if len(pkgs) > 1 {
choices, err := PkgPrompt(pkgs, verb)
choices, err := PkgPrompt(pkgs, verb, t)
if err != nil {
log.Fatal("Error prompting for choice of package").Send()
}
@ -88,7 +90,7 @@ func FlattenPkgs(found map[string][]db.Package, verb string) []db.Package {
// PkgPrompt asks the user to choose between multiple packages.
// The user may choose multiple packages.
func PkgPrompt(options []db.Package, verb string) ([]db.Package, error) {
func PkgPrompt(options []db.Package, verb string, t translate.Translator) ([]db.Package, error) {
names := make([]string, len(options))
for i, option := range options {
names[i] = option.Repository + "/" + option.Name + " " + option.Version
@ -96,7 +98,7 @@ func PkgPrompt(options []db.Package, verb string) ([]db.Package, error) {
prompt := &survey.MultiSelect{
Options: names,
Message: "Choose which package(s) to " + verb,
Message: t.TranslateTo("Choose which package(s) to "+verb, config.Language),
}
var choices []int

30
internal/config/lang.go Normal file
View File

@ -0,0 +1,30 @@
package config
import (
"os"
"strings"
"go.arsenm.dev/logger/log"
"golang.org/x/text/language"
)
var Language language.Tag
func init() {
lang := SystemLang()
tag, err := language.Parse(lang)
if err != nil {
log.Fatal("Error parsing system language").Err(err).Send()
}
base, _ := tag.Base()
Language = language.Make(base.String())
}
func SystemLang() string {
lang := os.Getenv("LANG")
lang, _, _ = strings.Cut(lang, ".")
if lang == "" {
lang = "en"
}
return lang
}

View File

@ -1,7 +1,6 @@
package overrides
import (
"os"
"reflect"
"runtime"
"strings"
@ -216,12 +215,3 @@ func parseLangs(langs []string, tags []language.Tag) ([]string, error) {
out = slices.Compact(out)
return out, nil
}
func SystemLang() string {
lang := os.Getenv("LANG")
lang, _, _ = strings.Cut(lang, ".")
if lang == "" {
lang = "en"
}
return lang
}

17
main.go
View File

@ -20,6 +20,7 @@ package main
import (
"context"
"embed"
"fmt"
"os"
"os/signal"
@ -33,12 +34,26 @@ import (
"go.arsenm.dev/lure/internal/config"
"go.arsenm.dev/lure/internal/db"
"go.arsenm.dev/lure/manager"
"go.arsenm.dev/translate"
)
//go:generate scripts/gen-version.sh
//go:embed translations
var translationFS embed.FS
var translator translate.Translator
func init() {
log.Logger = logger.NewCLI(os.Stderr)
logger := logger.NewCLI(os.Stderr)
t, err := translate.NewFromFS(translationFS)
if err != nil {
logger.Fatal("Error creating new translator").Err(err).Send()
}
translator = t
log.Logger = translate.NewLogger(logger, t, config.Language)
}
func main() {

107
translations/lure.en.toml Normal file
View File

@ -0,0 +1,107 @@
[[translation]]
id = 1228660974
value = 'Pulling repository'
[[translation]]
id = 2779805870
value = 'Repository up to date'
[[translation]]
id = 1433222829
value = 'Would you like to view the build script for'
[[translation]]
id = 2470847050
value = 'Failed to prompt user to view build script'
[[translation]]
id = 855659503
value = 'Would you still like to continue?'
[[translation]]
id = 1997041569
value = 'User chose not to continue after reading script'
[[translation]]
id = 2347700990
value = 'Building package'
[[translation]]
id = 2105058868
value = 'Downloading sources'
[[translation]]
id = 1519177982
value = 'Error building package'
[[translation]]
id = 2125220917
value = 'Choose which package(s) to install'
[[translation]]
id = 812531604
value = 'Error prompting for choice of package'
[[translation]]
id = 1040982801
value = 'Updating version'
[[translation]]
id = 1014897988
value = 'Remove build dependencies?'
[[translation]]
id = 2205430948
value = 'Installing build dependencies'
[[translation]]
id = 2522710805
value = 'Installing dependencies'
[[translation]]
id = 3602138206
value = 'Error installing package'
[[translation]]
id = 2235794125
value = 'Would you like to remove build dependencies?'
[[translation]]
id = 2562049386
value = "Your system's CPU architecture doesn't match this package. Do you want to build anyway?"
[[translation]]
id = 4006393493
value = 'The checksums array must be the same length as sources'
[[translation]]
id = 3759891273
value = 'The package() function is required'
[[translation]]
id = 1057080231
value = 'Executing package()'
[[translation]]
id = 2687735200
value = 'Executing prepare()'
[[translation]]
id = 535572372
value = 'Executing version()'
[[translation]]
id = 436644691
value = 'Executing build()'
[[translation]]
id = 1579384326
value = 'name'
[[translation]]
id = 3206337475
value = 'version'
[[translation]]
id = 1810056261
value = 'new'

103
translations/lure.ru.toml Normal file
View File

@ -0,0 +1,103 @@
[[translation]]
id = 1228660974
value = 'Скачивание репозитория'
[[translation]]
id = 2779805870
value = 'Репозиторий уже обновлен'
[[translation]]
id = 1433222829
value = 'Показать скрипт для пакета'
[[translation]]
id = 2470847050
value = 'Не удалось предложить просмотреть скрипт'
[[translation]]
id = 855659503
value = 'Продолжить?'
[[translation]]
id = 1997041569
value = 'Пользователь решил не продолжать после просмотра скрипта'
[[translation]]
id = 2347700990
value = 'Сборка пакета'
[[translation]]
id = 2105058868
value = 'Скачивание файлов'
[[translation]]
id = 1519177982
value = 'Ошибка при сборке пакета'
[[translation]]
id = 2125220917
value = 'Выберите, какие пакеты установить'
[[translation]]
id = 812531604
value = 'Ошибка при запросе выбора пакета'
[[translation]]
id = 1040982801
value = 'Обновление версии'
[[translation]]
id = 2235794125
value = 'Удалить зависимости сборки?'
[[translation]]
id = 2205430948
value = 'Установка зависимостей сборки'
[[translation]]
id = 2522710805
value = 'Установка зависимостей'
[[translation]]
id = 3602138206
value = 'Ошибка при установке пакета'
[[translation]]
id = 1057080231
value = 'Вызов функции package()'
[[translation]]
id = 2687735200
value = 'Вызов функции prepare()'
[[translation]]
id = 535572372
value = 'Вызов функции version()'
[[translation]]
id = 436644691
value = 'Вызов функции build()'
[[translation]]
id = 2562049386
value = "Архитектура процессора вашей системы не соответствует этому пакету. Продолжать несмотря на это?"
[[translation]]
id = 3759891273
value = 'Функция package() необходима'
[[translation]]
id = 4006393493
value = 'Массив checksums должен быть той же длины, что и sources'
[[translation]]
id = 1579384326
value = 'название'
[[translation]]
id = 3206337475
value = 'версия'
[[translation]]
id = 1810056261
value = 'новая'