diff --git a/README.md b/README.md index 7fb6117..1dc19bf 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,8 @@ poe2arb convert io --lang en < Hello_World_English.json > lib/l10n/app_en.arb ### Seeding POEditor project -**EXPERIMENTAL FEATURE** +> [!WARNING] +> **EXPERIMENTAL FEATURE** If you're setting up a project from some template code, you probably already have some ARB files that need to be imported into the POEditor project. Using the POEditor's built-in tool won't give a satisfying result, @@ -96,8 +97,8 @@ with already populated translations is inadvisable. ## Syntax & supported features -Term name must be a valid Dart field name, additionaly, it must start with a -lowercase letter ([Flutter's constraint][term-name-constraint]). +> [!IMPORTANT] +> Term name must be a valid Dart field name, additionaly, it must start with a lowercase letter ([Flutter's constraint][term-name-constraint]). ### Term prefix filtering @@ -150,6 +151,7 @@ Available placeholder types: **Only template files can define placeholders with their type and format.** In non-template languages, placeholders' types and formats are ignored and no logical errors are reported. +> [!NOTE] > \*If you're using Flutter 3.5 or older, you need to specify format for numeric placeholders. > Otherwise `flutter gen-l10n` will fail. You can look at the legacy placeholder syntax diagrams > [for placeholders here][flutter35-placeholders-diagram] and for [plural's `count` placeholders here][flutter35-count-placeholders-diagram]. @@ -198,6 +200,20 @@ other: Andy has {count} kilograms of {fruit}. You must provide at least `other` plural category for your translations, otherwise it won't be converted. +## Constraining version for a Flutter project + +You can constrain poe2arb version by specifying `poe2arb-version` option in `l10n.yaml`. + +```yaml +# Available formats: +poe2arb-version: "0.5.1" # Specific version +poe2arb-version: ">=0.5.1, <0.7" # Version range +poe2arb-version: ">0.5.1" # Minimum version +poe2arb-version: "<=0.7" # Maximum version +``` + +You can find more information about version constraints format [here][go-version]. + ## Contributing ### Formatting @@ -255,6 +271,7 @@ git push origin v0.1.1 [flutter35-count-placeholders-diagram]: https://github.com/leancodepl/poe2arb/blob/24be17d6721698526c879b3fada87183b359e8e8/art/count-placeholder-syntax.svg [placeholder-diagram-img]: art/placeholder-syntax.svg [count-placeholder-diagram-img]: art/count-placeholder-syntax.svg +[go-version]: https://github.com/hashicorp/go-version [gofumpt]: https://github.com/mvdan/gofumpt [gofmt]: https://pkg.go.dev/cmd/gofmt [staticcheck]: https://staticcheck.io diff --git a/cmd/flutter_config_version_guard.go b/cmd/flutter_config_version_guard.go new file mode 100644 index 0000000..d354850 --- /dev/null +++ b/cmd/flutter_config_version_guard.go @@ -0,0 +1,80 @@ +package cmd + +import ( + "context" + "fmt" + "os" + + "github.com/hashicorp/go-version" + "github.com/leancodepl/poe2arb/flutter" + "github.com/spf13/cobra" +) + +type flutterConfigKey struct{} + +type flutterConfigVersionGuard struct{} + +func flutterConfigFromCommand(cmd *cobra.Command) *flutter.FlutterConfig { + return cmd.Context().Value(flutterConfigKey{}).(*flutter.FlutterConfig) +} + +// GetFlutterConfigAndEnsureSufficientVersion gets Flutter project configuration, +// puts it in the command's context and verifies if poe2arb version matches constraint. +func (fcvg flutterConfigVersionGuard) GetFlutterConfigAndEnsureSufficientVersion(cmd *cobra.Command, _ []string) error { + log := getLogger(cmd) + + logSub := log.Info("loading Flutter config").Sub() + + flutterCfg, err := fcvg.getFlutterConfig() + if err != nil { + logSub.Error("failed: " + err.Error()) + return err + } + + err = fcvg.ensureSufficientVersion(flutterCfg.L10n.Poe2ArbVersion) + if err != nil { + logSub.Error("failed: " + err.Error()) + return err + } + + ctx := context.WithValue(cmd.Context(), flutterConfigKey{}, flutterCfg) + cmd.SetContext(ctx) + + return nil +} + +func (flutterConfigVersionGuard) ensureSufficientVersion(versionConstraint string) error { + if versionConstraint == "" { + return nil + } + + constraint, err := version.NewConstraint(versionConstraint) + if err != nil { + return fmt.Errorf("invalid poe2arb-version format in l10n.yaml: %s", versionConstraint) + } + + version, err := version.NewVersion(Version) + if err != nil { + return fmt.Errorf("poe2arb version format is invalid: %w", err) + } + + if !constraint.Check(version) { + return fmt.Errorf("poe2arb version %s does not match constraint %s defined in l10n.yaml", version, versionConstraint) + } + + return nil +} + +func (flutterConfigVersionGuard) getFlutterConfig() (*flutter.FlutterConfig, error) { + workDir, err := os.Getwd() + if err != nil { + return nil, err + } + + flutterCfg, err := flutter.NewFromDirectory(workDir) + if err != nil { + return nil, err + } + + return flutterCfg, nil +} diff --git a/cmd/poe.go b/cmd/poe.go index a7d7de8..70ea13c 100644 --- a/cmd/poe.go +++ b/cmd/poe.go @@ -24,6 +24,7 @@ var ( SilenceErrors: true, SilenceUsage: true, RunE: runPoe, + PreRunE: versionGuard.GetFlutterConfigAndEnsureSufficientVersion, } termPrefixRegexp = regexp.MustCompile("[a-zA-Z]*") ) @@ -103,10 +104,7 @@ func getOptionsSelector(cmd *cobra.Command) (*poeOptionsSelector, error) { return nil, err } - flutterCfg, err := getFlutterConfig() - if err != nil { - return nil, err - } + flutterCfg := flutterConfigFromCommand(cmd) return &poeOptionsSelector{ flags: cmd.Flags(), @@ -115,20 +113,6 @@ func getOptionsSelector(cmd *cobra.Command) (*poeOptionsSelector, error) { }, nil } -func getFlutterConfig() (*flutter.FlutterConfig, error) { - workDir, err := os.Getwd() - if err != nil { - return nil, err - } - - flutterCfg, err := flutter.NewFromDirectory(workDir) - if err != nil { - return nil, err - } - - return flutterCfg, nil -} - type poeCommand struct { options *poeOptions client *poeditor.Client diff --git a/cmd/poe2arb.go b/cmd/poe2arb.go index a231477..da27ecd 100644 --- a/cmd/poe2arb.go +++ b/cmd/poe2arb.go @@ -9,14 +9,15 @@ import ( "github.com/spf13/cobra" ) -var rootCmd = &cobra.Command{ - Use: "poe2arb", - Short: "POEditor JSON to Flutter ARB converter", -} - -type ctxKey int +var ( + rootCmd = &cobra.Command{ + Use: "poe2arb", + Short: "POEditor JSON to Flutter ARB converter", + } + versionGuard = flutterConfigVersionGuard{} +) -const loggerKey = ctxKey(1) +type loggerKey struct{} func Execute(logger *log.Logger) { rootCmd.AddCommand(convertCmd) @@ -24,7 +25,7 @@ func Execute(logger *log.Logger) { rootCmd.AddCommand(seedCmd) rootCmd.AddCommand(versionCmd) - ctx := context.WithValue(context.Background(), loggerKey, logger) + ctx := context.WithValue(context.Background(), loggerKey{}, logger) if err := rootCmd.ExecuteContext(ctx); err != nil { os.Exit(1) @@ -32,5 +33,5 @@ func Execute(logger *log.Logger) { } func getLogger(cmd *cobra.Command) *log.Logger { - return cmd.Context().Value(loggerKey).(*log.Logger) + return cmd.Context().Value(loggerKey{}).(*log.Logger) } diff --git a/cmd/seed.go b/cmd/seed.go index 9cc46d7..0aa4174 100644 --- a/cmd/seed.go +++ b/cmd/seed.go @@ -19,6 +19,7 @@ var seedCmd = &cobra.Command{ SilenceErrors: true, SilenceUsage: true, RunE: runSeed, + PreRunE: versionGuard.GetFlutterConfigAndEnsureSufficientVersion, } func init() { diff --git a/flutter/flutter_config.go b/flutter/flutter_config.go index 4ec3921..4683efc 100644 --- a/flutter/flutter_config.go +++ b/flutter/flutter_config.go @@ -33,6 +33,7 @@ type L10n struct { POEditorProjectID string `yaml:"poeditor-project-id"` POEditorLangs []string `yaml:"poeditor-langs"` POEditorTermPrefix string `yaml:"poeditor-term-prefix"` + Poe2ArbVersion string `yaml:"poe2arb-version"` } func newDefaultL10n() *L10n { diff --git a/flutter/flutter_config_test.go b/flutter/flutter_config_test.go index a730c4e..e808d91 100644 --- a/flutter/flutter_config_test.go +++ b/flutter/flutter_config_test.go @@ -63,7 +63,7 @@ func TestNewFromDirectory(t *testing.T) { err = os.WriteFile(filepath.Join(dir, "pubspec.yaml"), []byte{}, 0o666) assert.NoError(t, err) - l10nContents := `{arb-dir: this-is/arb-dir/test, poeditor-project-id: 123123}` + l10nContents := `{arb-dir: this-is/arb-dir/test, poeditor-project-id: 123123, poe2arb-version: ">=0.5.0, <0.7"}` err = os.WriteFile(filepath.Join(dir, "l10n.yaml"), []byte(l10nContents), 0o666) assert.NoError(t, err) @@ -75,5 +75,6 @@ func TestNewFromDirectory(t *testing.T) { assert.Equal(t, "this-is/arb-dir/test", cfg.L10n.ARBDir) assert.Equal(t, "123123", cfg.L10n.POEditorProjectID) + assert.Equal(t, ">=0.5.0, <0.7", cfg.L10n.Poe2ArbVersion) }) } diff --git a/go.mod b/go.mod index 5d54794..81fc26e 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/TwiN/go-color v1.4.0 github.com/caarlos0/env/v6 v6.10.1 + github.com/hashicorp/go-version v1.7.0 github.com/spf13/cobra v1.6.1 github.com/wk8/go-ordered-map/v2 v2.1.1 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 3798500..ea400da 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=