Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add term-prefix setting and filtering #77

Merged
merged 7 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ builds:
- >
-s -w
-X github.com/leancodepl/poe2arb/cmd.Version={{.Version}}
-X github.com/leancodepl/poe2arb/cmd.Commit={{.Commit}}
-X github.com/leancodepl/poe2arb/cmd.BuiltDate={{.Date}}
universal_binaries:
- replace: true
changelog:
Expand Down
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2022 LeanCode Sp. z o.o.
Copyright 2023 LeanCode Sp. z o.o.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -198,4 +198,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
38 changes: 32 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ into your Flutter workspace in one command:

If a command-line flag is not specified, an environment variable is used, then `l10n.yaml` option, then it fallbacks to default.

| Description | Flag | Env | `l10n.yaml` |
|-------------------------------------------------------------------------------------------------------------------|------------------------|------------------|-----------------------|
| **Required.** POEditor project ID. It is visible in the URL of the project on POEditor website. | `-p`<br>`--project-id` | | `poeditor-project-id` |
| **Required.** POEditor API read-only access token. Available in [Account settings > API access][poeditor-tokens]. | `-t`<br>`--token` | `POEDITOR_TOKEN` | |
| ARB files output directory.<br>Defaults to current directory. | `-o`<br>`--output-dir` | | `arb-dir` |
| Exported languages override.<br>Defaults to using all languages from POEditor. | `--langs` | | `poeditor-langs` |
| Description | Flag | Env | `l10n.yaml` |
|-------------------------------------------------------------------------------------------------------------------|------------------------|------------------|------------------------|
| **Required.** POEditor project ID. It is visible in the URL of the project on POEditor website. | `-p`<br>`--project-id` | | `poeditor-project-id` |
| **Required.** POEditor API read-only access token. Available in [Account settings > API access][poeditor-tokens]. | `-t`<br>`--token` | `POEDITOR_TOKEN` | |
| ARB files output directory.<br>Defaults to current directory. | `-o`<br>`--output-dir` | | `arb-dir` |
| Exported languages override.<br>Defaults to using all languages from POEditor. | `--langs` | | `poeditor-langs` |
| Term prefix, used to filter generated messages.<br>Defaults to empty. | `--term-prefix` | | `poeditor-term-prefix` |

### Conversion

Expand All @@ -69,6 +70,8 @@ For conversion, you need to pass the translation file language in the

By default, a template ARB file is generated. So no empty message is skipped and attributes are generated. If you want to skip that, pass `--no-template` flag.

You may filter terms with `--term-prefix`. Defaults to empty (no prefix).

Currently, only an stdin/stdout is supported for the `poe2arb convert` command.

```
Expand All @@ -80,6 +83,29 @@ poe2arb convert io --lang en < Hello_World_English.json > lib/l10n/app_en.arb
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

If you wish to use one POEditor project for multiple packages, ideally you do not want
one package's terms to pollute all other packages. This is what term prefixes are for.

Term names in POEditor can be defined starting with a prefix (only letters), followed by a colon `:`.
E.g. `loans:helpPage_title` or `design_system:modalClose`. Then, in your `l10n.yaml` or with the `--term-prefix` flag you may
define which terms should be imported, filtered by the prefix.

If you don't pass a prefix to `poe2arb` (or pass an empty one), it will only import the terms that have no prefix. If you pass
prefix to `poe2arb`, it will import only the terms with this prefix.

<details><summary>Examples</summary>

| Term name in POEditor | `--term-prefix` or<br>`poeditor-term-prefix` (`l10n.yaml`) | Message name in ARB |
|-----------------------|------------------------------------------------------------|---------------------|
| `appTitle` | none | `appTitle` |
| `somePrefix:appTitle` | none | not imported |
| `appTitle` | `somePrefix` | not imported |
| `somePrefix:appTitle` | `somePrefix` | `appTitle` |

</details>

### Placeholders

Placeholders can be as simple as a text between brackets, but they can also be
Expand Down
11 changes: 9 additions & 2 deletions cmd/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func init() {
convertCmd.PersistentFlags().StringP(langFlag, "l", "", "Language of the input")
convertCmd.MarkPersistentFlagRequired(langFlag)

convertCmd.PersistentFlags().StringP(termPrefixFlag, "", "", "POEditor term prefix")
convertCmd.PersistentFlags().Bool(noTemplateFlag, false, "Whether the output should NOT be generated as a template ARB")

convertCmd.AddCommand(convertIoCmd)
Expand All @@ -37,8 +38,14 @@ func init() {
func runConvertIo(cmd *cobra.Command, args []string) error {
lang, _ := cmd.Flags().GetString(langFlag)
noTemplate, _ := cmd.Flags().GetBool(noTemplateFlag)

conv := converter.NewConverter(os.Stdin, lang, !noTemplate, true)
termPrefix, _ := cmd.Flags().GetString(termPrefixFlag)

conv := converter.NewConverter(os.Stdin, &converter.ConverterOptions{
Lang: lang,
Template: !noTemplate,
RequireResourceAttributes: true,
TermPrefix: termPrefix,
})

return conv.Convert(os.Stdout)
}
37 changes: 26 additions & 11 deletions cmd/poe.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"os"
"path"
"regexp"
"strings"

"github.com/leancodepl/poe2arb/converter"
Expand All @@ -15,25 +16,30 @@ import (
"github.com/spf13/cobra"
)

var poeCmd = &cobra.Command{
Use: "poe",
Short: "Exports POEditor terms and converts them to ARB. " +
"Must be run from the Flutter project root directory or its subdirectory.",
SilenceErrors: true,
SilenceUsage: true,
RunE: runPoe,
}
var (
poeCmd = &cobra.Command{
Use: "poe",
Short: "Exports POEditor terms and converts them to ARB. " +
"Must be run from the Flutter project root directory or its subdirectory.",
SilenceErrors: true,
SilenceUsage: true,
RunE: runPoe,
}
termPrefixRegexp = regexp.MustCompile("[a-zA-Z]*")
)

const (
projectIDFlag = "project-id"
tokenFlag = "token"
termPrefixFlag = "term-prefix"
outputDirFlag = "output-dir"
overrideLangsFlag = "langs"
)

func init() {
poeCmd.Flags().StringP(projectIDFlag, "p", "", "POEditor project ID")
poeCmd.Flags().StringP(tokenFlag, "t", "", "POEditor API token")
poeCmd.Flags().StringP(termPrefixFlag, "", "", "POEditor term prefix")
poeCmd.Flags().StringP(outputDirFlag, "o", "", `Output directory [default: "."]`)
poeCmd.Flags().StringSliceP(overrideLangsFlag, "", []string{}, "Override downloaded languages")
}
Expand Down Expand Up @@ -75,7 +81,7 @@ func runPoe(cmd *cobra.Command, args []string) error {
for _, lang := range langs {
template := options.TemplateLocale == lang.Code

err := poeCmd.ExportLanguage(lang, template, options.RequireResourceAttributes)
err := poeCmd.ExportLanguage(lang, template)
if err != nil {
msg := fmt.Sprintf("exporting %s (%s) language", lang.Name, lang.Code)
return errors.Wrap(err, msg)
Expand Down Expand Up @@ -154,6 +160,10 @@ func validatePoeOptions(options *poeOptions) []error {
errs = append(errs, errors.New("no POEditor API token provided"))
}

if !termPrefixRegexp.MatchString(options.TermPrefix) {
errs = append(errs, errors.New("term prefix must contain only letters or be empty"))
}

return errs
}

Expand Down Expand Up @@ -209,7 +219,7 @@ func (c *poeCommand) EnsureOutputDirectory() error {
return nil
}

func (c *poeCommand) ExportLanguage(lang poeditor.Language, template, requireResourceAttributes bool) error {
func (c *poeCommand) ExportLanguage(lang poeditor.Language, template bool) error {
logSub := c.log.Info("fetching JSON export for %s (%s)", lang.Name, lang.Code).Sub()
url, err := c.client.GetExportURL(c.options.ProjectID, lang.Code)
if err != nil {
Expand All @@ -232,7 +242,12 @@ func (c *poeCommand) ExportLanguage(lang poeditor.Language, template, requireRes

convertLogSub := logSub.Info("converting JSON to ARB").Sub()

conv := converter.NewConverter(resp.Body, lang.Code, template, requireResourceAttributes)
conv := converter.NewConverter(resp.Body, &converter.ConverterOptions{
Lang: lang.Code,
Template: template,
RequireResourceAttributes: c.options.RequireResourceAttributes,
TermPrefix: c.options.TermPrefix,
})
err = conv.Convert(file)
if err != nil {
convertLogSub.Error(err.Error())
Expand Down
25 changes: 23 additions & 2 deletions cmd/poe_options_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ type poeOptionsSelector struct {

// poeOptions describes options passed or otherwise obtained to the poe command.
type poeOptions struct {
ProjectID string
Token string
ProjectID string
Token string
TermPrefix string

ARBPrefix string
TemplateLocale string
OutputDir string
Expand All @@ -41,6 +43,11 @@ func (s *poeOptionsSelector) SelectOptions() (*poeOptions, error) {
return nil, err
}

termPrefix, err := s.SelectTermPrefix()
if err != nil {
return nil, err
}

arbPrefix, templateLocale, err := s.SelectARBPrefixAndTemplate()
if err != nil {
return nil, err
Expand All @@ -61,6 +68,7 @@ func (s *poeOptionsSelector) SelectOptions() (*poeOptions, error) {
return &poeOptions{
ProjectID: projectID,
Token: token,
TermPrefix: termPrefix,
ARBPrefix: arbPrefix,
TemplateLocale: templateLocale,
OutputDir: outputDir,
Expand Down Expand Up @@ -95,6 +103,19 @@ func (s *poeOptionsSelector) SelectToken() (string, error) {
return s.env.Token, nil
}

// SelectTermPrefix returns POEditor term prefix option from available sources.
func (s *poeOptionsSelector) SelectTermPrefix() (string, error) {
fromCmd, err := s.flags.GetString(termPrefixFlag)
if err != nil {
return "", err
}
if fromCmd != "" {
return fromCmd, nil
}

return s.l10n.POEditorTermPrefix, nil
}

// SelectARBPrefix returns ARB files prefix option from available sources.
func (s *poeOptionsSelector) SelectARBPrefixAndTemplate() (prefix, templateLocale string, err error) {
prefix, err = prefixFromTemplateFileName(s.l10n.TemplateArbFile)
Expand Down
59 changes: 49 additions & 10 deletions cmd/version.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package cmd

import "github.com/spf13/cobra"

var (
// Version is the version of the application. It is set during the build process using ldflags.
Version = "dev"
// Commit is the commit hash of the application. It is set during the build process using ldflags.
Commit = "none"
// Date is the date of the build. It is set during the build process using ldflags.
BuiltDate = "unknown"
import (
"errors"
"runtime/debug"

"github.com/spf13/cobra"
)

// Version is the version of the application. It is set during the build process using ldflags.
var Version string

var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of poe2arb",
Expand All @@ -20,7 +19,47 @@ var versionCmd = &cobra.Command{
func runVersion(cmd *cobra.Command, args []string) error {
log := getLogger(cmd)

log.Info("poe2arb version %s, commit %s, built at %s", Version, Commit, BuiltDate)
revision, time, modified, err := getVcsInfo()
if err != nil {
return err
}

msg := "poe2arb"
if Version != "" {
msg += " version " + Version
} else {
msg += " built from source"
}

msg += ", commit " + revision

if modified {
msg += " (with local modifications)"
}

msg += ", built at " + time

log.Info(msg)

return nil
}

func getVcsInfo() (revision, time string, modified bool, err error) {
info, ok := debug.ReadBuildInfo()
if !ok {
err = errors.New("error reading build info")
return
}

for _, setting := range info.Settings {
if setting.Key == "vcs.revision" {
revision = setting.Value
} else if setting.Key == "vcs.time" {
time = setting.Value
} else if setting.Key == "vcs.modified" {
modified = setting.Value == "true"
}
}

return
}
35 changes: 25 additions & 10 deletions converter/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package converter
import (
"encoding/json"
"io"
"regexp"
"strings"

"github.com/pkg/errors"
Expand All @@ -13,25 +14,30 @@ import (
type Converter struct {
input io.Reader

lang string
template bool

lang string
template bool
requireResourceAttributes bool
termPrefix string
}

type ConverterOptions struct {
Lang string
Template bool
RequireResourceAttributes bool
TermPrefix string
}

func NewConverter(
input io.Reader,
lang string,
template bool,
requireResourceAttributes bool,
options *ConverterOptions,
) *Converter {
return &Converter{
input: input,

lang: lang,
template: template,

requireResourceAttributes: requireResourceAttributes,
lang: options.Lang,
template: options.Template,
requireResourceAttributes: options.RequireResourceAttributes,
termPrefix: options.TermPrefix,
}
}

Expand All @@ -45,9 +51,18 @@ func (c *Converter) Convert(output io.Writer) error {
arb := orderedmap.New[string, any]()
arb.Set(localeKey, c.lang)

prefixedRegexp := regexp.MustCompile("(?:([a-zA-Z]+):)?(.*)")
var errs []error

for _, term := range jsonContents {
// Filter by term prefix
matches := prefixedRegexp.FindStringSubmatch(term.Term)
if matches[1] == c.termPrefix {
term.Term = matches[2]
} else {
continue
}

message, err := c.parseTerm(term)
if err != nil {
err = errors.Wrapf(err, `decoding term "%s" failed`, term.Term)
Expand Down
Loading