From 424f217f7e3098469d987933b5745e0c6ba9a71b Mon Sep 17 00:00:00 2001 From: Jakob Jensen Date: Sat, 26 Oct 2024 19:37:20 +0200 Subject: [PATCH] Add templating option (#60) --- cmd/translations.go | 17 ++++++ docs/generated/lane_translations_generate.md | 17 ++++++ internal/translations/flags.go | 7 +++ internal/translations/flags_test.go | 26 +++++++++ internal/translations/generator_test.go | 33 +++++++++++ internal/translations/language_file.go | 25 ++++++-- .../translations/language_file.ios.support.go | 58 +++++++++++++------ .../testdata/fill-in/ios-swift.expected | 2 +- .../testdata/ios-swift-empty.expected | 2 +- .../translations/testdata/ios-swift.expected | 2 +- .../testdata/templated-ios-support/file.tmpl | 15 +++++ .../testdata/templated-ios-support/input.csv | 3 + .../templated-ios-support/invalid.tmpl | 16 +++++ .../templated-ios-support/ios-swift.expected | 10 ++++ 14 files changed, 205 insertions(+), 28 deletions(-) create mode 100644 internal/translations/testdata/templated-ios-support/file.tmpl create mode 100644 internal/translations/testdata/templated-ios-support/input.csv create mode 100644 internal/translations/testdata/templated-ios-support/invalid.tmpl create mode 100644 internal/translations/testdata/templated-ios-support/ios-swift.expected diff --git a/cmd/translations.go b/cmd/translations.go index 86b3045..f0cd7f2 100644 --- a/cmd/translations.go +++ b/cmd/translations.go @@ -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 @@ -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") diff --git a/docs/generated/lane_translations_generate.md b/docs/generated/lane_translations_generate.md index 1f23d23..aa9cf80 100644 --- a/docs/generated/lane_translations_generate.md +++ b/docs/generated/lane_translations_generate.md @@ -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] @@ -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) ``` diff --git a/internal/translations/flags.go b/internal/translations/flags.go index bf02cec..e7cc01f 100644 --- a/internal/translations/flags.go +++ b/internal/translations/flags.go @@ -14,6 +14,7 @@ type Flags struct { DefaultValueIndex int Output string FillIn bool + Template string } func (f *Flags) validate() error { @@ -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") diff --git a/internal/translations/flags_test.go b/internal/translations/flags_test.go index 47cbd39..3196f8e 100644 --- a/internal/translations/flags_test.go +++ b/internal/translations/flags_test.go @@ -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) { diff --git a/internal/translations/generator_test.go b/internal/translations/generator_test.go index cacf0cf..16f5d1f 100644 --- a/internal/translations/generator_test.go +++ b/internal/translations/generator_test.go @@ -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) +} diff --git a/internal/translations/language_file.go b/internal/translations/language_file.go index f544532..9762d73 100644 --- a/internal/translations/language_file.go +++ b/internal/translations/language_file.go @@ -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) } diff --git a/internal/translations/language_file.ios.support.go b/internal/translations/language_file.ios.support.go index 18ca625..3f67bc1 100644 --- a/internal/translations/language_file.ios.support.go +++ b/internal/translations/language_file.ios.support.go @@ -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) @@ -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) } diff --git a/internal/translations/testdata/fill-in/ios-swift.expected b/internal/translations/testdata/fill-in/ios-swift.expected index 56ade64..747e907 100644 --- a/internal/translations/testdata/fill-in/ios-swift.expected +++ b/internal/translations/testdata/fill-in/ios-swift.expected @@ -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: "") } diff --git a/internal/translations/testdata/ios-swift-empty.expected b/internal/translations/testdata/ios-swift-empty.expected index 3c5e4fc..ecb36a7 100644 --- a/internal/translations/testdata/ios-swift-empty.expected +++ b/internal/translations/testdata/ios-swift-empty.expected @@ -1,4 +1,4 @@ // swiftlint:disable all import Foundation -struct Translations { +public struct Translations { } diff --git a/internal/translations/testdata/ios-swift.expected b/internal/translations/testdata/ios-swift.expected index a763b63..6958914 100644 --- a/internal/translations/testdata/ios-swift.expected +++ b/internal/translations/testdata/ios-swift.expected @@ -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: "") diff --git a/internal/translations/testdata/templated-ios-support/file.tmpl b/internal/translations/testdata/templated-ios-support/file.tmpl new file mode 100644 index 0000000..e2fd738 --- /dev/null +++ b/internal/translations/testdata/templated-ios-support/file.tmpl @@ -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 }} +} \ No newline at end of file diff --git a/internal/translations/testdata/templated-ios-support/input.csv b/internal/translations/testdata/templated-ios-support/input.csv new file mode 100644 index 0000000..390c163 --- /dev/null +++ b/internal/translations/testdata/templated-ios-support/input.csv @@ -0,0 +1,3 @@ +KEY,EN,DA +SOMETHING,Something,Noget +SOMETHING_WITH_ARGUMENTS,Something with %1 and %2,Noget med %1 og %2 diff --git a/internal/translations/testdata/templated-ios-support/invalid.tmpl b/internal/translations/testdata/templated-ios-support/invalid.tmpl new file mode 100644 index 0000000..a7cc38a --- /dev/null +++ b/internal/translations/testdata/templated-ios-support/invalid.tmpl @@ -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 }} +} \ No newline at end of file diff --git a/internal/translations/testdata/templated-ios-support/ios-swift.expected b/internal/translations/testdata/templated-ios-support/ios-swift.expected new file mode 100644 index 0000000..3c32acf --- /dev/null +++ b/internal/translations/testdata/templated-ios-support/ios-swift.expected @@ -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) + } +} \ No newline at end of file