feat: translation support
All checks were successful
continuous-integration/drone/push Build is passing

See #483
This commit is contained in:
2025-08-19 11:22:52 +02:00
parent 5cf6048ecb
commit 4e205cf13e
108 changed files with 11217 additions and 1645 deletions

View File

@ -12,6 +12,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter"
gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/tagcmp"
@ -22,10 +23,10 @@ import (
)
var RecipeReleaseCommand = &cobra.Command{
Use: "release <recipe> [version] [flags]",
Aliases: []string{"rl"},
Short: "Release a new recipe version",
Long: `Create a new version of a recipe.
Use: i18n.G("release <recipe> [version] [flags]"),
Aliases: []string{i18n.G("rl")},
Short: i18n.G("Release a new recipe version"),
Long: i18n.G(`Create a new version of a recipe.
These versions are then published on the Co-op Cloud recipe catalogue. These
versions take the following form:
@ -44,7 +45,7 @@ major and therefore require intervention while doing the upgrade work.
Publish your new release to git.coopcloud.tech with "--publish/-p". This
requires that you have permission to git push to these repositories and have
your SSH keys configured on your account.`,
your SSH keys configured on your account.`),
Args: cobra.RangeArgs(1, 2),
ValidArgsFunction: func(
cmd *cobra.Command,
@ -74,7 +75,7 @@ your SSH keys configured on your account.`,
mainAppVersion := imagesTmp[mainApp]
if mainAppVersion == "" {
log.Fatalf("main app service version for %s is empty?", recipe.Name)
log.Fatal(i18n.G("main app service version for %s is empty?", recipe.Name))
}
var tagString string
@ -84,12 +85,12 @@ your SSH keys configured on your account.`,
if tagString != "" {
if _, err := tagcmp.Parse(tagString); err != nil {
log.Fatalf("cannot parse %s, invalid tag specified?", tagString)
log.Fatal(i18n.G("cannot parse %s, invalid tag specified?", tagString))
}
}
if (internal.Major || internal.Minor || internal.Patch) && tagString != "" {
log.Fatal("cannot specify tag and bump type at the same time")
log.Fatal(i18n.G("cannot specify tag and bump type at the same time"))
}
if tagString != "" {
@ -117,19 +118,19 @@ your SSH keys configured on your account.`,
}
if !isClean {
log.Infof("%s currently has these unstaged changes 👇", recipe.Name)
log.Info(i18n.G("%s currently has these unstaged changes 👇", recipe.Name))
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
log.Fatal(err)
}
}
if len(tags) > 0 {
log.Warnf("previous git tags detected, assuming this is a new semver release")
log.Warn(i18n.G("previous git tags detected, assuming this is a new semver release"))
if err := createReleaseFromPreviousTag(tagString, mainAppVersion, recipe, tags); err != nil {
log.Fatal(err)
}
} else {
log.Warnf("no tag specified and no previous tag available for %s, assuming this is the initial release", recipe.Name)
log.Warn(i18n.G("no tag specified and no previous tag available for %s, assuming this is the initial release", recipe.Name))
if err := createReleaseFromTag(recipe, tagString, mainAppVersion); err != nil {
if cleanUpErr := cleanUpTag(recipe, tagString); err != nil {
@ -181,7 +182,7 @@ func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
}
if missingTag {
return services, fmt.Errorf("app service is missing image tag?")
return services, errors.New(i18n.G("app service is missing image tag?"))
}
return services, nil
@ -245,7 +246,7 @@ func btoi(b bool) int {
// getTagCreateOptions constructs git tag create options
func getTagCreateOptions(tag string) (git.CreateTagOptions, error) {
msg := fmt.Sprintf("chore: publish %s release", tag)
msg := i18n.G("chore: publish %s release", tag)
return git.CreateTagOptions{Message: msg}, nil
}
@ -273,13 +274,13 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
if _, err := os.Stat(nextReleaseNotePath); err == nil {
// release/next note exists. Move it to release/<tag>
if internal.Dry {
log.Debugf("dry run: move release note from 'next' to %s", tag)
log.Debug(i18n.G("dry run: move release note from 'next' to %s", tag))
return nil
}
if !internal.NoInput {
prompt := &survey.Confirm{
Message: "Use release note in release/next?",
Message: i18n.G("Use release note in release/next?"),
}
if err := survey.AskOne(prompt, &addNextAsReleaseNotes); err != nil {
@ -313,7 +314,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
}
prompt := &survey.Input{
Message: "Release Note (leave empty for no release note)",
Message: i18n.G("Release Note (leave empty for no release note)"),
}
var releaseNote string
@ -338,7 +339,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
func commitRelease(recipe recipe.Recipe, tag string) error {
if internal.Dry {
log.Debugf("dry run: no changes committed")
log.Debug(i18n.G("dry run: no changes committed"))
return nil
}
@ -349,7 +350,7 @@ func commitRelease(recipe recipe.Recipe, tag string) error {
if isClean {
if !internal.Dry {
return fmt.Errorf("no changes discovered in %s, nothing to publish?", recipe.Dir)
return errors.New(i18n.G("no changes discovered in %s, nothing to publish?", recipe.Dir))
}
}
@ -363,7 +364,7 @@ func commitRelease(recipe recipe.Recipe, tag string) error {
func tagRelease(tagString string, repo *git.Repository) error {
if internal.Dry {
log.Debugf("dry run: no git tag created (%s)", tagString)
log.Debug(i18n.G("dry run: no git tag created (%s)", tagString))
return nil
}
@ -383,20 +384,20 @@ func tagRelease(tagString string, repo *git.Repository) error {
}
hash := formatter.SmallSHA(head.Hash().String())
log.Debugf(fmt.Sprintf("created tag %s at %s", tagString, hash))
log.Debug(i18n.G("created tag %s at %s", tagString, hash))
return nil
}
func pushRelease(recipe recipe.Recipe, tagString string) error {
if internal.Dry {
log.Info("dry run: no changes published")
log.Info(i18n.G("dry run: no changes published"))
return nil
}
if !publish && !internal.NoInput {
prompt := &survey.Confirm{
Message: "publish new release?",
Message: i18n.G("publish new release?"),
}
if err := survey.AskOne(prompt, &publish); err != nil {
@ -409,9 +410,9 @@ func pushRelease(recipe recipe.Recipe, tagString string) error {
return err
}
url := fmt.Sprintf("%s/src/tag/%s", recipe.GitURL, tagString)
log.Infof("new release published: %s", url)
log.Info(i18n.G("new release published: %s", url))
} else {
log.Info("no -p/--publish passed, not publishing")
log.Info(i18n.G("no -p/--publish passed, not publishing"))
}
return nil
@ -426,7 +427,7 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip
bumpType := btoi(internal.Major)*4 + btoi(internal.Minor)*2 + btoi(internal.Patch)
if bumpType != 0 {
if (bumpType & (bumpType - 1)) != 0 {
return fmt.Errorf("you can only use one of: --major, --minor, --patch")
return errors.New(i18n.G("you can only use one of: --major, --minor, --patch"))
}
}
@ -483,12 +484,12 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip
}
if lastGitTag.String() == tagString {
log.Fatalf("latest git tag (%s) and synced label (%s) are the same?", lastGitTag, tagString)
log.Fatal(i18n.G("latest git tag (%s) and synced label (%s) are the same?", lastGitTag, tagString))
}
if !internal.NoInput {
prompt := &survey.Confirm{
Message: fmt.Sprintf("current: %s, new: %s, correct?", lastGitTag, tagString),
Message: i18n.G("current: %s, new: %s, correct?", lastGitTag, tagString),
}
var ok bool
@ -497,7 +498,7 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip
}
if !ok {
log.Fatal("exiting as requested")
log.Fatal(i18n.G("exiting as requested"))
}
}
@ -506,15 +507,15 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip
}
if err := commitRelease(recipe, tagString); err != nil {
log.Fatalf("failed to commit changes: %s", err.Error())
log.Fatal(i18n.G("failed to commit changes: %s", err.Error()))
}
if err := tagRelease(tagString, repo); err != nil {
log.Fatalf("failed to tag release: %s", err.Error())
log.Fatal(i18n.G("failed to tag release: %s", err.Error()))
}
if err := pushRelease(recipe, tagString); err != nil {
log.Fatalf("failed to publish new release: %s", err.Error())
log.Fatal(i18n.G("failed to publish new release: %s", err.Error()))
}
return nil
@ -533,7 +534,7 @@ func cleanUpTag(recipe recipe.Recipe, tag string) error {
}
}
log.Debugf("removed freshly created tag %s", tag)
log.Debug(i18n.G("removed freshly created tag %s", tag))
return nil
}
@ -545,20 +546,20 @@ func getLabelVersion(recipe recipe.Recipe, prompt bool) (string, error) {
}
if initTag == "" {
log.Fatalf("unable to read version for %s from synced label. Did you try running \"abra recipe sync %s\" already?", recipe.Name, recipe.Name)
log.Fatal(i18n.G("unable to read version for %s from synced label. Did you try running \"abra recipe sync %s\" already?", recipe.Name, recipe.Name))
}
log.Warnf("discovered %s as currently synced recipe label", initTag)
log.Warn(i18n.G("discovered %s as currently synced recipe label", initTag))
if prompt && !internal.NoInput {
var response bool
prompt := &survey.Confirm{Message: fmt.Sprintf("use %s as the new version?", initTag)}
prompt := &survey.Confirm{Message: i18n.G("use %s as the new version?", initTag)}
if err := survey.AskOne(prompt, &response); err != nil {
return "", err
}
if !response {
return "", fmt.Errorf("please fix your synced label for %s and re-run this command", recipe.Name)
return "", errors.New(i18n.G("please fix your synced label for %s and re-run this command", recipe.Name))
}
}
@ -572,42 +573,41 @@ var (
func init() {
RecipeReleaseCommand.Flags().BoolVarP(
&internal.Dry,
"dry-run",
"r",
i18n.G("dry-run"),
i18n.G("r"),
false,
"report changes that would be made",
i18n.G("report changes that would be made"),
)
RecipeReleaseCommand.Flags().BoolVarP(
&internal.Major,
"major",
"x",
i18n.G("major"),
i18n.G("x"),
false,
"increase the major part of the version",
i18n.G("increase the major part of the version"),
)
RecipeReleaseCommand.Flags().BoolVarP(
&internal.Minor,
"minor",
"y",
i18n.G("minor"),
i18n.G("y"),
false,
"increase the minor part of the version",
i18n.G("increase the minor part of the version"),
)
RecipeReleaseCommand.Flags().BoolVarP(
&internal.Patch,
"patch",
"z",
i18n.G("patch"),
i18n.G("z"),
false,
"increase the patch part of the version",
i18n.G("increase the patch part of the version"),
)
RecipeReleaseCommand.Flags().BoolVarP(
&publish,
"publish",
"p",
i18n.G("publish"),
i18n.G("p"),
false,
"publish changes to git.coopcloud.tech",
i18n.G("publish changes to git.coopcloud.tech"),
)
}