Skip to content

Commit

Permalink
Add templating option (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeReaper authored Oct 26, 2024
1 parent 3967846 commit 424f217
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 28 deletions.
17 changes: 17 additions & 0 deletions cmd/translations.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,22 @@ translations.swift:
static let SOMETHING = NSLocalizedString("SOMETHING", comment: "")
static func SOMETHING_WITH_ARGUMENTS(_ p1: String, _ p2: String) -> String { return NSLocalizedString("SOMETHING_WITH_ARGUMENTS", comment: "").replacingOccurrences(of: "%1", with: p1).replacingOccurrences(of: "%2", with: p2) }
}
You can support your own golang text template to change the output, the above output is generated with the following default template:
// swiftlint:disable all
import Foundation
public struct Translations {
{{- range . }}
{{- if .Arguments }}
static func {{ .Name }}({{ .Arguments }}) -> String { return NSLocalizedString("{{ .Name }}", comment: ""){{ .Replacements }} }
{{- else }}
static let {{ .Name }} = NSLocalizedString("{{ .Name }}", comment: "")
{{- end }}
{{- end }}
}
`
var flags translations.Flags
var configurations []string
Expand All @@ -109,6 +125,7 @@ translations.swift:
cmd.Flags().StringArrayVarP(&configurations, "configuration", "c", make([]string, 0), "A configuration string consisting of space separated row index and output path. Multiple configurations can be added, but one is required")
cmd.Flags().IntVarP(&flags.DefaultValueIndex, "main-index", "m", 0, "Required by type ios and by option fill-in. The index of the main/default language row")
cmd.Flags().StringVarP(&flags.Output, "output", "o", "", "Required for type ios. A path for the generated output")
cmd.Flags().StringVarP(&flags.Template, "template", "p", "", "Only for type ios and optional. A path for the template to generate from")
cmd.Flags().BoolVarP(&flags.FillIn, "fill-in", "l", false, "Fill in the value from the main/default language if a value is missing for the current language")
cmd.MarkFlagRequired("input")
cmd.MarkFlagRequired("kind")
Expand Down
17 changes: 17 additions & 0 deletions docs/generated/lane_translations_generate.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ translations.swift:
static func SOMETHING_WITH_ARGUMENTS(_ p1: String, _ p2: String) -> String { return NSLocalizedString("SOMETHING_WITH_ARGUMENTS", comment: "").replacingOccurrences(of: "%1", with: p1).replacingOccurrences(of: "%2", with: p2) }
}

You can support your own golang text template to change the output, the above output is generated with the following default template:

// swiftlint:disable all
import Foundation
public struct Translations {
{{- range . }}
{{- if .Arguments }}
static func {{ .Name }}({{ .Arguments }}) -> String { return NSLocalizedString("{{ .Name }}", comment: ""){{ .Replacements }} }
{{- else }}
static let {{ .Name }} = NSLocalizedString("{{ .Name }}", comment: "")
{{- end }}
{{- end }}
}




```
lane translations generate [flags]
Expand All @@ -63,6 +79,7 @@ lane translations generate [flags]
-i, --input string Path to a CSV file containing a key row and a row for each language (Required)
-m, --main-index int Required by type ios and by option fill-in. The index of the main/default language row
-o, --output string Required for type ios. A path for the generated output
-p, --template string Only for type ios and optional. A path for the template to generate from
-t, --type string The type of output to generate, valid options are 'ios', 'android' or 'json' (Required)
```

Expand Down
7 changes: 7 additions & 0 deletions internal/translations/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Flags struct {
DefaultValueIndex int
Output string
FillIn bool
Template string
}

func (f *Flags) validate() error {
Expand Down Expand Up @@ -44,6 +45,12 @@ func (f *Flags) validate() error {
return err
}

if len(f.Template) > 0 {
if _, err := os.Stat(f.Template); err != nil {
return err
}
}

if isIOS {
if len(f.Output) == 0 {
return fmt.Errorf("output not provided")
Expand Down
26 changes: 26 additions & 0 deletions internal/translations/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,32 @@ var validationCases = []struct {
},
true,
},
{
"ios-with-template-valid",
Flags{
Input: "testdata/input.csv",
Kind: "ios",
KeyIndex: 1,
DefaultValueIndex: 1,
Output: "testdata/out.put",
FillIn: true,
Template: "testdata/templated-ios-support/file.tmpl",
},
true,
},
{
"ios-with-template-invalid",
Flags{
Input: "testdata/input.csv",
Kind: "ios",
KeyIndex: 1,
DefaultValueIndex: 1,
Output: "testdata/out.put",
FillIn: true,
Template: "does/not/exist.file",
},
false,
},
}

func TestFlagsValidate(t *testing.T) {
Expand Down
33 changes: 33 additions & 0 deletions internal/translations/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,36 @@ func TestConfigurationCases(t *testing.T) {
})
}
}

func TestTemplatedIos(t *testing.T) {
flags := Flags{
Input: "testdata/templated-ios-support/input.csv",
Kind: "ios",
KeyIndex: 1,
DefaultValueIndex: 2,
Output: "../../build/Translations.swift",
Template: "testdata/templated-ios-support/file.tmpl",
}
configurations := []string{"2 ../../build/en.strings"}

err := Generate(context.Background(), &flags, configurations)

assert.Nil(t, err)
equalFiles(t, "testdata/templated-ios-support/ios-swift.expected", "../../build/Translations.swift")
}

func TestInvalidTemplate(t *testing.T) {
flags := Flags{
Input: "testdata/templated-ios-support/input.csv",
Kind: "ios",
KeyIndex: 1,
DefaultValueIndex: 2,
Output: "../../build/Translations.swift",
Template: "testdata/templated-ios-support/invalid.tmpl",
}
configurations := []string{"2 ../../build/en.strings"}

err := Generate(context.Background(), &flags, configurations)

assert.NotNil(t, err)
}
25 changes: 19 additions & 6 deletions internal/translations/language_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,25 @@ func newLanguageFiles(flags *Flags, configurations []string) ([]languageFileWrit

switch flags.Kind {
case iosKind:
supporter := &iosSupportLanguageFile{file: &languageFile{
path: flags.Output,
keyIndex: flags.KeyIndex,
valueIndex: flags.DefaultValueIndex,
useFallback: false,
}}
var template string

if len(flags.Template) > 0 {
buf, err := os.ReadFile(flags.Template)
if err != nil {
return nil, err
}
template = string(buf)
}

supporter := &iosSupportLanguageFile{
file: &languageFile{
path: flags.Output,
keyIndex: flags.KeyIndex,
valueIndex: flags.DefaultValueIndex,
useFallback: false,
},
template: template,
}
list = append(list, supporter)
}

Expand Down
58 changes: 39 additions & 19 deletions internal/translations/language_file.ios.support.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,56 @@ import (
"io"
"regexp"
"strings"
"text/template"
)

type iosSupportLanguageFile struct {
file *languageFile
file *languageFile
template string
}

func (f *iosSupportLanguageFile) Write(translations *translationData) error {
return f.file.write(f, translations)
}

func (f *iosSupportLanguageFile) write(translation *translation, io io.Writer) error {
regex := regexp.MustCompile(`%([0-9]+)`)
type line struct {
Name string
Arguments string
Replacements string
}

header := `// swiftlint:disable all
const iosSupportTemplate = `// swiftlint:disable all
import Foundation
struct Translations {
`
footer := `}
public struct Translations {
{{- range . }}
{{- if .Arguments }}
static func {{ .Name }}({{ .Arguments }}) -> String { return NSLocalizedString("{{ .Name }}", comment: ""){{ .Replacements }} }
{{- else }}
static let {{ .Name }} = NSLocalizedString("{{ .Name }}", comment: "")
{{- end }}
{{- end }}
}
`

_, err := io.Write([]byte(header))
func (f *iosSupportLanguageFile) write(translation *translation, io io.Writer) error {
regex := regexp.MustCompile(`%([0-9]+)`)

tmpl := f.template
if tmpl == "" {
tmpl = iosSupportTemplate
}

generator, err := template.New("tmpl").Parse(tmpl)
if err != nil {
return err
}

list := make([]*line, 0)
for _, k := range translation.keys {
key := strings.ToUpper(k)
value := translation.get(k)
var item *line

var line string
matches := regex.FindAllStringSubmatch(value, -1)
if len(matches) > 0 {
arguments := make([]string, 0)
Expand All @@ -44,19 +64,19 @@ struct Translations {
arguments = append(arguments, fmt.Sprintf("_ p%s: String", match))
replacements = append(replacements, fmt.Sprintf(".replacingOccurrences(of: \"%%%s\", with: p%s)", match, match))
}
argumentsString := strings.Join(arguments, ", ")
replacementsString := strings.Join(replacements, "")
line = fmt.Sprintf("\tstatic func %s(%s) -> String { return NSLocalizedString(\"%s\", comment: \"\")%s }\n", key, argumentsString, key, replacementsString)
item = &line{
Name: key,
Arguments: strings.Join(arguments, ", "),
Replacements: strings.Join(replacements, ""),
}
} else {
line = fmt.Sprintf("\tstatic let %s = NSLocalizedString(\"%s\", comment: \"\")\n", key, key)
item = &line{
Name: key,
}
}

_, err := io.Write([]byte(line))
if err != nil {
return err
}
list = append(list, item)
}

_, err = io.Write([]byte(footer))
return err
return generator.Execute(io, list)
}
2 changes: 1 addition & 1 deletion internal/translations/testdata/fill-in/ios-swift.expected
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// swiftlint:disable all
import Foundation
struct Translations {
public struct Translations {
static let SOMETHING = NSLocalizedString("SOMETHING", comment: "")
static let SOMETHING_ONLY_IN_EN = NSLocalizedString("SOMETHING_ONLY_IN_EN", comment: "")
}
2 changes: 1 addition & 1 deletion internal/translations/testdata/ios-swift-empty.expected
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swiftlint:disable all
import Foundation
struct Translations {
public struct Translations {
}
2 changes: 1 addition & 1 deletion internal/translations/testdata/ios-swift.expected
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// swiftlint:disable all
import Foundation
struct Translations {
public struct Translations {
static let SOMETHING = NSLocalizedString("SOMETHING", comment: "")
static let SOMETHING_ESCAPED = NSLocalizedString("SOMETHING_ESCAPED", comment: "")
static let SOMETHING_FOR_XML = NSLocalizedString("SOMETHING_FOR_XML", comment: "")
Expand Down
15 changes: 15 additions & 0 deletions internal/translations/testdata/templated-ios-support/file.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// swiftlint:disable all
import Foundation
struct Texts {
static var bundle = Bundle.main
{{- range . }}
{{- if .Arguments }}
static func {{ .Name }}({{ .Arguments }}) -> String {
return NSLocalizedString("{{ .Name }}", bundle: bundle, comment: "{{ .Name }}")
{{ .Replacements }}
}
{{- else }}
static let {{ .Name }} = NSLocalizedString("{{ .Name }}", bundle: bundle, comment: "{{ .Name }}")
{{- end }}
{{- end }}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
KEY,EN,DA
SOMETHING,Something,Noget
SOMETHING_WITH_ARGUMENTS,Something with %1 and %2,Noget med %1 og %2
16 changes: 16 additions & 0 deletions internal/translations/testdata/templated-ios-support/invalid.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{{ define content }}
// swiftlint:disable all
import Foundation
struct Texts {
static var bundle = Bundle.main
{{- range . }}
{{- if .Arguments }}
static func {{ .Name }}({{ .Arguments }}) -> String {
return NSLocalizedString("{{ .Name }}", bundle: bundle, comment: "{{ .Name }}")
{{ .Replacements }}
}
{{- else }}
static let {{ .Name }} = NSLocalizedString("{{ .Name }}", bundle: bundle, comment: "{{ .Name }}")
{{- end }}
{{- end }}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// swiftlint:disable all
import Foundation
struct Texts {
static var bundle = Bundle.main
static let SOMETHING = NSLocalizedString("SOMETHING", bundle: bundle, comment: "SOMETHING")
static func SOMETHING_WITH_ARGUMENTS(_ p1: String, _ p2: String) -> String {
return NSLocalizedString("SOMETHING_WITH_ARGUMENTS", bundle: bundle, comment: "SOMETHING_WITH_ARGUMENTS")
.replacingOccurrences(of: "%1", with: p1).replacingOccurrences(of: "%2", with: p2)
}
}

0 comments on commit 424f217

Please sign in to comment.