From cd59424a16320552b33589868c507c09b905e7ff Mon Sep 17 00:00:00 2001 From: Roman Perekhod Date: Sun, 16 Apr 2023 12:54:01 +0200 Subject: [PATCH 1/5] Make Emails translatable via transifex #6025 The transifex translation add in to the email templates. The optional environment variable NOTIFICATIONS_TRANSLATION_PATH added to config. --- .../make-emails-translatable-via-transifex.md | 8 ++ services/notifications/Makefile | 24 +++++ services/notifications/README.md | 29 +++++ services/notifications/pkg/config/config.go | 1 + services/notifications/pkg/email/composer.go | 41 +++++++ services/notifications/pkg/email/email.go | 51 ++++++++- .../notifications/pkg/email/l10n/.tx/config | 9 ++ .../locale/de_DE/LC_MESSAGES/notifications.po | 102 ++++++++++++++++++ services/notifications/pkg/email/templates.go | 93 ++++++++++++++++ .../shares/shareCreated.email.body.tmpl | 14 +-- .../shares/shareCreated.email.subject.tmpl | 1 - .../shares/shareExpired.email.body.tmpl | 14 +-- .../shares/shareExpired.email.subject.tmpl | 1 - .../spaces/membershipExpired.email.body.tmpl | 14 +-- .../membershipExpired.email.subject.tmpl | 1 - .../spaces/sharedSpace.email.body.tmpl | 14 +-- .../spaces/sharedSpace.email.subject.tmpl | 1 - .../spaces/unsharedSpace.email.body.tmpl | 14 +-- .../spaces/unsharedSpace.email.subject.tmpl | 1 - services/notifications/pkg/service/service.go | 12 +-- services/notifications/pkg/service/shares.go | 5 +- services/notifications/pkg/service/spaces.go | 7 +- services/userlog/pkg/service/conversion.go | 4 +- 23 files changed, 382 insertions(+), 79 deletions(-) create mode 100644 changelog/unreleased/make-emails-translatable-via-transifex.md create mode 100644 services/notifications/pkg/email/composer.go create mode 100755 services/notifications/pkg/email/l10n/.tx/config create mode 100644 services/notifications/pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po create mode 100644 services/notifications/pkg/email/templates.go delete mode 100644 services/notifications/pkg/email/templates/shares/shareCreated.email.subject.tmpl delete mode 100644 services/notifications/pkg/email/templates/shares/shareExpired.email.subject.tmpl delete mode 100644 services/notifications/pkg/email/templates/spaces/membershipExpired.email.subject.tmpl delete mode 100644 services/notifications/pkg/email/templates/spaces/sharedSpace.email.subject.tmpl delete mode 100644 services/notifications/pkg/email/templates/spaces/unsharedSpace.email.subject.tmpl diff --git a/changelog/unreleased/make-emails-translatable-via-transifex.md b/changelog/unreleased/make-emails-translatable-via-transifex.md new file mode 100644 index 0000000000..55baae8f90 --- /dev/null +++ b/changelog/unreleased/make-emails-translatable-via-transifex.md @@ -0,0 +1,8 @@ +Enhancement: Notifications + +Make Emails translatable via transifex +The transifex translation add in to the email templates. +The optional environment variable NOTIFICATIONS_TRANSLATION_PATH added to config. + +https://github.com/owncloud/ocis/pull/6038 +https://github.com/owncloud/ocis/issues/6025 diff --git a/services/notifications/Makefile b/services/notifications/Makefile index 2982566e5b..b64d1a6f53 100644 --- a/services/notifications/Makefile +++ b/services/notifications/Makefile @@ -1,6 +1,10 @@ SHELL := bash NAME := notifications +# Where to write the files generated by this makefile. +OUTPUT_DIR = ./pkg/email/l10n +TEMPLATE_FILE = ./pkg/email/l10n/notifications.pot + include ../../.make/recursion.mk ############ tooling ############ @@ -29,6 +33,26 @@ ci-go-generate: # CI runs ci-node-generate automatically before this target .PHONY: ci-node-generate ci-node-generate: +############ translations ######## +.PHONY: l10n-pull +l10n-pull: + cd $(OUTPUT_DIR) && tx pull -a --skip --minimum-perc=75 + +.PHONY: l10n-push +l10n-push: + cd $(OUTPUT_DIR) && tx push -s --skip + +.PHONY: l10n-read +l10n-read: $(GO_XGETTEXT) + go-xgettext -o $(OUTPUT_DIR)/notifications.pot --keyword=Template --add-comments -s pkg/email/l10n/templates.go + +.PHONY: l10n-write +l10n-write: + +.PHONY: l10n-clean +l10n-clean: + rm -f $(TEMPLATE_FILE); + ############ licenses ############ .PHONY: ci-node-check-licenses ci-node-check-licenses: diff --git a/services/notifications/README.md b/services/notifications/README.md index 51d58bc9b8..700a62b348 100644 --- a/services/notifications/README.md +++ b/services/notifications/README.md @@ -2,3 +2,32 @@ The notification service is responsible for sending emails to users informing them about events that happened. To do this it hooks into the event system and listens for certain events that the users need to be informed about. +## Translations + +The `translations` service has embedded translations sourced via transifex to provide a basic set of translated languages. +These embedded translations are available for all deployment scenarios. In addition, the service supports custom +translations, though it is currently not possible to just add custom translations to embedded ones. If custom +translations are configured, the embedded ones are not used. To configure custom translations, +the `NOTIFICATIONS_TRANSLATION_PATH` environment variable needs to point to a base folder that will contain the translation +files. This path must be available from all instances of the translations service, a shared storage is recommended. +Translation files must be of type [.po](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#PO-Files) +or [.mo](https://www.gnu.org/software/gettext/manual/html_node/Binaries.html). For each language, the filename needs to +be `translations.po` (or `translations.mo`) and stored in a folder structure defining the language code. In general the path/name +pattern for a translation file needs to be: + +```text +{NOTIFICATIONS_TRANSLATION_PATH}/{language-code}/LC_MESSAGES/translations.po +``` + +The language code pattern is composed of `language[_territory]` where `language` is the base language and `_territory` +is optional and defines a country. + +For example, for the language `de_DE`, one needs to place the corresponding translation files +to `{NOTIFICATIONS_TRANSLATION_PATH}/de_DE/LC_MESSAGES/translations.po`. + +### Translation Rules + +* If a requested language code is not available, the service tries to fall back to the base language if available. +For example, if `de_DE` is not available, the service tries to fall back to translations in the `de` folder. +* If the base language `de` is also not available, the service falls back to the system's default English (`en`), +which is the source of the texts provided by the code. diff --git a/services/notifications/pkg/config/config.go b/services/notifications/pkg/config/config.go index 90afa48b46..ebf5b0dddf 100644 --- a/services/notifications/pkg/config/config.go +++ b/services/notifications/pkg/config/config.go @@ -28,6 +28,7 @@ type Notifications struct { Events Events `yaml:"events"` MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;NOTIFICATIONS_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary to access resources from other services."` EmailTemplatePath string `yaml:"email_template_path" env:"OCIS_EMAIL_TEMPLATE_PATH;NOTIFICATIONS_EMAIL_TEMPLATE_PATH" desc:"Path to Email notification templates overriding embedded ones."` + TranslationPath string `yaml:"translation_path" env:"NOTIFICATIONS_TRANSLATION_PATH" desc:"(optional) Set this to a path with custom translations to overwrite the builtin translations. See the documentation for more details."` RevaGateway string `yaml:"reva_gateway" env:"OCIS_REVA_GATEWAY;REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata" deprecationVersion:"3.0" removalVersion:"3.1" deprecationInfo:"REVA_GATEWAY changing name for consistency" deprecationReplacement:"OCIS_REVA_GATEWAY"` GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"` } diff --git a/services/notifications/pkg/email/composer.go b/services/notifications/pkg/email/composer.go new file mode 100644 index 0000000000..ece92e0a21 --- /dev/null +++ b/services/notifications/pkg/email/composer.go @@ -0,0 +1,41 @@ +package email + +import ( + "embed" + "io/fs" + "strings" + + "github.com/leonelquinteros/gotext" +) + +var ( + //go:embed l10n/locale + _translationFS embed.FS + _domain = "notifications" +) + +// ComposeMessage renders the message based on template +func ComposeMessage(template, locale string, path string) string { + raw := loadTemplate(template, locale, path) + return replacePlaceholders(raw) +} + +func loadTemplate(template, locale string, path string) string { + // Create Locale with library path and language code and load default domain + var l *gotext.Locale + if path == "" { + filesystem, _ := fs.Sub(_translationFS, "l10n/locale") + l = gotext.NewLocaleFS(locale, filesystem) + } else { // use custom path instead + l = gotext.NewLocale(path, locale) + } + l.AddDomain(_domain) // make domain configurable only if needed + return l.Get(template) +} + +func replacePlaceholders(raw string) string { + for o, n := range _placeholders { + raw = strings.ReplaceAll(raw, o, n) + } + return raw +} diff --git a/services/notifications/pkg/email/email.go b/services/notifications/pkg/email/email.go index d3302ccdf7..4171ea59b8 100644 --- a/services/notifications/pkg/email/email.go +++ b/services/notifications/pkg/email/email.go @@ -1,8 +1,12 @@ +// Package email implements utility for rendering the Email. +// +// The email package supports transifex translation for email templates. package email import ( "bytes" "embed" + "html" "html/template" "path/filepath" ) @@ -13,7 +17,33 @@ var ( ) // RenderEmailTemplate renders the email template for a new share -func RenderEmailTemplate(templateName string, templateVariables map[string]string, emailTemplatePath string) (string, error) { +func RenderEmailTemplate(et MessageTemplate, locale string, emailTemplatePath string, translationPath string, vars map[string]interface{}) (string, string, error) { + rawsub := ComposeMessage(et.Subject, locale, translationPath) + // replace the body email placeholders with the values + subject, err := executeRaw(rawsub, vars) + if err != nil { + return "", "", err + } + + bodyPlaceholders := map[string]interface{}{} + bodyPlaceholders["Greeting"] = ComposeMessage(et.Greeting, locale, translationPath) + bodyPlaceholders["MessageBody"] = ComposeMessage(et.MessageBody, locale, translationPath) + bodyPlaceholders["CallToAction"] = ComposeMessage(et.CallToAction, locale, translationPath) + + // replace the body email template placeholders with the translated template + rawBody, err := executeEmailTemplate(emailTemplatePath, et.bodyTemplate, bodyPlaceholders) + if err != nil { + return "", "", err + } + // replace the body email placeholders with the values + body, err := executeRaw(rawBody, vars) + if err != nil { + return "", "", err + } + return subject, body, nil +} + +func executeEmailTemplate(emailTemplatePath, templateName string, vars map[string]interface{}) (string, error) { var err error var tpl *template.Template // try to lookup the files in the filesystem @@ -25,10 +55,25 @@ func RenderEmailTemplate(templateName string, templateVariables map[string]strin return "", err } } - var writer bytes.Buffer - err = tpl.Execute(&writer, templateVariables) + str, err := executeTemplate(tpl, vars) if err != nil { return "", err } + return html.UnescapeString(str), err +} + +func executeRaw(raw string, vars map[string]interface{}) (string, error) { + tpl, err := template.New("").Parse(raw) + if err != nil { + return "", err + } + return executeTemplate(tpl, vars) +} + +func executeTemplate(tpl *template.Template, vars map[string]interface{}) (string, error) { + var writer bytes.Buffer + if err := tpl.Execute(&writer, vars); err != nil { + return "", err + } return writer.String(), nil } diff --git a/services/notifications/pkg/email/l10n/.tx/config b/services/notifications/pkg/email/l10n/.tx/config new file mode 100755 index 0000000000..b56891b8bb --- /dev/null +++ b/services/notifications/pkg/email/l10n/.tx/config @@ -0,0 +1,9 @@ +[main] +host = https://www.transifex.com + +[o:owncloud-org:p:owncloud:r:ocis-notifications] +file_filter = locale//LC_MESSAGES/notifications.po +minimum_perc = 75 +source_file = notifications.pot +source_lang = en +type = PO diff --git a/services/notifications/pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po b/services/notifications/pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po new file mode 100644 index 0000000000..a434807bea --- /dev/null +++ b/services/notifications/pkg/email/l10n/locale/de_DE/LC_MESSAGES/notifications.po @@ -0,0 +1,102 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "Project-Id-Version: \n" + "Report-Msgid-Bugs-To: EMAIL\n" + "POT-Creation-Date: 2023-04-14 16:58+0200\n" + "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" + "Last-Translator: FULL NAME \n" + "Language-Team: LANGUAGE \n" + "Language: \n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=CHARSET\n" + "Content-Transfer-Encoding: 8bit\n" + +#. UnsharedSpace email template, CallToAction field +#: pkg/email/templates.go:57 +msgid "Click here to check it: {ShareLink}" +msgstr "" + +#. ShareCreated email template, CallToAction field +#. SharedSpace email template, CallToAction field +#: pkg/email/templates.go:18 pkg/email/templates.go:43 +msgid "Click here to view it: {ShareLink}" +msgstr "" + +#. ShareCreated email template, Greeting field +#: pkg/email/templates.go:14 +msgid "Hello {ShareGrantee}" +msgstr "" + +#. ShareExpired email template, Greeting field +#: pkg/email/templates.go:26 +msgid "Hello {ShareGrantee}," +msgstr "" + +#. SharedSpace email template, Greeting field +#. UnsharedSpace email template, Greeting field +#. MembershipExpired email template, Greeting field +#: pkg/email/templates.go:39 pkg/email/templates.go:51 pkg/email/templates.go:65 +msgid "Hello {SpaceGrantee}," +msgstr "" + +#. MembershipExpired email template, Subject field +#: pkg/email/templates.go:63 +msgid "Membership of '{SpaceName}' expired at {ExpiredAt}" +msgstr "" + +#. ShareExpired email template, Subject field +#: pkg/email/templates.go:24 +msgid "Share to '{ShareFolder}' expired at {ExpiredAt}" +msgstr "" + +#. MembershipExpired email template, Message field +#: pkg/email/templates.go:67 +msgid "Your membership of space {SpaceName} has expired at {ExpiredAt}\n" + "\n" + "Even though this membership has expired you still might have access through other shares and/or space memberships" +msgstr "" + +#. ShareExpired email template, Message field +#: pkg/email/templates.go:28 +msgid "Your share to {ShareFolder} has expired at {ExpiredAt}\n" + "\n" + "Even though this share has been revoked you still might have access through other shares and/or space memberships." +msgstr "" + +#. ShareCreated email template, Message field +#: pkg/email/templates.go:16 +msgid "{ShareSharer} has shared \"{ShareFolder}\" with you." +msgstr "" + +#. ShareCreated email template, Subject field +#: pkg/email/templates.go:12 +msgid "{ShareSharer} shared '{ShareFolder}' with you" +msgstr "" + +#. SharedSpace email template, Message field +#: pkg/email/templates.go:41 +msgid "{SpaceSharer} has invited you to join \"{SpaceName}\"." +msgstr "" + +#. UnsharedSpace email template, Message field +#: pkg/email/templates.go:53 +msgid "{SpaceSharer} has removed you from \"{SpaceName}\".\n" + "\n" + "You might still have access through your other groups or direct membership." +msgstr "" + +#. SharedSpace email template, Subject field +#: pkg/email/templates.go:37 +msgid "{SpaceSharer} invited you to join {SpaceName}" +msgstr "" + +#. UnsharedSpace email template, Subject field +#: pkg/email/templates.go:49 +msgid "{SpaceSharer} removed you from {SpaceName}" +msgstr "" + diff --git a/services/notifications/pkg/email/templates.go b/services/notifications/pkg/email/templates.go new file mode 100644 index 0000000000..c42f205a55 --- /dev/null +++ b/services/notifications/pkg/email/templates.go @@ -0,0 +1,93 @@ +package email + +// Template marks the string as a translatable template +func Template(s string) string { return s } + +// the available templates +var ( + // Shares + ShareCreated = MessageTemplate{ + bodyTemplate: "shares/shareCreated.email.body.tmpl", + // ShareCreated email template, Subject field + Subject: Template(`{ShareSharer} shared '{ShareFolder}' with you`), + // ShareCreated email template, Greeting field + Greeting: Template(`Hello {ShareGrantee}`), + // ShareCreated email template, Message field + MessageBody: Template(`{ShareSharer} has shared "{ShareFolder}" with you.`), + // ShareCreated email template, CallToAction field + CallToAction: Template(`Click here to view it: {ShareLink}`), + } + + ShareExpired = MessageTemplate{ + bodyTemplate: "shares/shareExpired.email.body.tmpl", + // ShareExpired email template, Subject field + Subject: Template(`Share to '{ShareFolder}' expired at {ExpiredAt}`), + // ShareExpired email template, Greeting field + Greeting: Template(`Hello {ShareGrantee},`), + // ShareExpired email template, Message field + MessageBody: Template(`Your share to {ShareFolder} has expired at {ExpiredAt} + +Even though this share has been revoked you still might have access through other shares and/or space memberships.`), + } + + // Spaces templates + SharedSpace = MessageTemplate{ + bodyTemplate: "spaces/sharedSpace.email.body.tmpl", + // SharedSpace email template, Subject field + Subject: Template("{SpaceSharer} invited you to join {SpaceName}"), + // SharedSpace email template, Greeting field + Greeting: Template(`Hello {SpaceGrantee},`), + // SharedSpace email template, Message field + MessageBody: Template(`{SpaceSharer} has invited you to join "{SpaceName}".`), + // SharedSpace email template, CallToAction field + CallToAction: Template(`Click here to view it: {ShareLink}`), + } + + UnsharedSpace = MessageTemplate{ + bodyTemplate: "spaces/unsharedSpace.email.body.tmpl", + // UnsharedSpace email template, Subject field + Subject: Template(`{SpaceSharer} removed you from {SpaceName}`), + // UnsharedSpace email template, Greeting field + Greeting: Template(`Hello {SpaceGrantee},`), + // UnsharedSpace email template, Message field + MessageBody: Template(`{SpaceSharer} has removed you from "{SpaceName}". + +You might still have access through your other groups or direct membership.`), + // UnsharedSpace email template, CallToAction field + CallToAction: Template(`Click here to check it: {ShareLink}`), + } + + MembershipExpired = MessageTemplate{ + bodyTemplate: "spaces/membershipExpired.email.body.tmpl", + // MembershipExpired email template, Subject field + Subject: Template(`Membership of '{SpaceName}' expired at {ExpiredAt}`), + // MembershipExpired email template, Greeting field + Greeting: Template(`Hello {SpaceGrantee},`), + // MembershipExpired email template, Message field + MessageBody: Template(`Your membership of space {SpaceName} has expired at {ExpiredAt} + +Even though this membership has expired you still might have access through other shares and/or space memberships`), + } +) + +// holds the information to turn the raw template into a parseable go template +var _placeholders = map[string]string{ + "{ShareSharer}": "{{ .ShareSharer }}", + "{ShareFolder}": "{{ .ShareFolder }}", + "{ShareGrantee}": "{{ .ShareGrantee }}", + "{ShareLink}": "{{ .ShareLink }}", + "{SpaceName}": "{{ .SpaceName }}", + "{SpaceGrantee}": "{{ .SpaceGrantee }}", + "{SpaceSharer}": "{{ .SpaceSharer }}", + "{ExpiredAt}": "{{ .ExpiredAt }}", +} + +// MessageTemplate is the data structure for the email +type MessageTemplate struct { + // bodyTemplate represent the path to .tmpl file + bodyTemplate string + Subject string + Greeting string + MessageBody string + CallToAction string +} diff --git a/services/notifications/pkg/email/templates/shares/shareCreated.email.body.tmpl b/services/notifications/pkg/email/templates/shares/shareCreated.email.body.tmpl index a13c63e3dc..a09897c819 100644 --- a/services/notifications/pkg/email/templates/shares/shareCreated.email.body.tmpl +++ b/services/notifications/pkg/email/templates/shares/shareCreated.email.body.tmpl @@ -1,16 +1,8 @@ -Hello {{ .ShareGrantee }}, +{{ .Greeting }} -{{ .ShareSharer }} has shared "{{ .ShareFolder }}" with you. +{{ .MessageBody }} -Click here to view it: {{ .ShareLink }} - ----------------------------------------------------------- - -Hallo {{ .ShareGrantee }}, - -{{ .ShareSharer }} hat dich zu "{{ .ShareFolder }}" eingeladen. - -Klicke hier zum Anzeigen: {{ .ShareLink }} +{{ .CallToAction }} --- diff --git a/services/notifications/pkg/email/templates/shares/shareCreated.email.subject.tmpl b/services/notifications/pkg/email/templates/shares/shareCreated.email.subject.tmpl deleted file mode 100644 index 09dccb9d80..0000000000 --- a/services/notifications/pkg/email/templates/shares/shareCreated.email.subject.tmpl +++ /dev/null @@ -1 +0,0 @@ -{{ .ShareSharer }} shared '{{ .ShareFolder }}' with you \ No newline at end of file diff --git a/services/notifications/pkg/email/templates/shares/shareExpired.email.body.tmpl b/services/notifications/pkg/email/templates/shares/shareExpired.email.body.tmpl index 322f28e520..34a55c95df 100644 --- a/services/notifications/pkg/email/templates/shares/shareExpired.email.body.tmpl +++ b/services/notifications/pkg/email/templates/shares/shareExpired.email.body.tmpl @@ -1,16 +1,6 @@ -Hello {{ .ShareGrantee }}, +{{ .Greeting }} -Your share to {{ .ShareFolder }} has expired at {{ .ExpiredAt }} - -Even though this share has been revoked you still might have access through other shares and/or space memberships - ----------------------------------------------------------- - -Hallo {{ .ShareGrantee }}, - -Deine Freigabe zu {{ .ShareFolder }} ist am {{ .ExpiredAt }} abgelaufen. - -Obwohl diese Freigabe nicht mehr zur Verfügung steht, könntest du immer noch Zugriff über andere Freigaben und/oder Space Mitgliedschaften haben. +{{ .MessageBody }} --- diff --git a/services/notifications/pkg/email/templates/shares/shareExpired.email.subject.tmpl b/services/notifications/pkg/email/templates/shares/shareExpired.email.subject.tmpl deleted file mode 100644 index e9593dcd5a..0000000000 --- a/services/notifications/pkg/email/templates/shares/shareExpired.email.subject.tmpl +++ /dev/null @@ -1 +0,0 @@ -Share to '{{ .ShareFolder }}' expired at {{ .ExpiredAt }} \ No newline at end of file diff --git a/services/notifications/pkg/email/templates/spaces/membershipExpired.email.body.tmpl b/services/notifications/pkg/email/templates/spaces/membershipExpired.email.body.tmpl index 95ba3897ba..34a55c95df 100644 --- a/services/notifications/pkg/email/templates/spaces/membershipExpired.email.body.tmpl +++ b/services/notifications/pkg/email/templates/spaces/membershipExpired.email.body.tmpl @@ -1,16 +1,6 @@ -Hello {{ .SpaceGrantee }}, +{{ .Greeting }} -Your membership of space {{ .SpaceName }} has expired at {{ .ExpiredAt }} - -Even though this membership has expired you still might have access through other shares and/or space memberships - ----------------------------------------------------------- - -Hallo {{ .SpaceGrantee }}, - -Deine Mitgliedschaft zu dem Space {{ .SpaceName }} ist am {{ .ExpiredAt }} abgelaufen. - -Obwohl diese Mitgliedschaft nicht mehr zur Verfügung steht, könntest du immer noch Zugriff über andere Freigaben und/oder Space Mitgliedschaften haben. +{{ .MessageBody }} --- diff --git a/services/notifications/pkg/email/templates/spaces/membershipExpired.email.subject.tmpl b/services/notifications/pkg/email/templates/spaces/membershipExpired.email.subject.tmpl deleted file mode 100644 index 9365818388..0000000000 --- a/services/notifications/pkg/email/templates/spaces/membershipExpired.email.subject.tmpl +++ /dev/null @@ -1 +0,0 @@ -Membership of '{{ .SpaceName }}' expired at {{ .ExpiredAt }} \ No newline at end of file diff --git a/services/notifications/pkg/email/templates/spaces/sharedSpace.email.body.tmpl b/services/notifications/pkg/email/templates/spaces/sharedSpace.email.body.tmpl index 897e4eb66a..a09897c819 100644 --- a/services/notifications/pkg/email/templates/spaces/sharedSpace.email.body.tmpl +++ b/services/notifications/pkg/email/templates/spaces/sharedSpace.email.body.tmpl @@ -1,16 +1,8 @@ -Hello {{ .SpaceGrantee }}, +{{ .Greeting }} -{{ .SpaceSharer }} has invited you to join "{{ .SpaceName }}". +{{ .MessageBody }} -Click here to view it: {{ .ShareLink }} - ----------------------------------------------------------- - -Hallo {{ .SpaceGrantee }}, - -{{ .SpaceSharer }} hat dich in den Space "{{ .SpaceName }}" eingeladen. - -Klicke hier zum Anzeigen: {{ .ShareLink }} +{{ .CallToAction }} --- diff --git a/services/notifications/pkg/email/templates/spaces/sharedSpace.email.subject.tmpl b/services/notifications/pkg/email/templates/spaces/sharedSpace.email.subject.tmpl deleted file mode 100644 index e65e04292d..0000000000 --- a/services/notifications/pkg/email/templates/spaces/sharedSpace.email.subject.tmpl +++ /dev/null @@ -1 +0,0 @@ -{{ .SpaceSharer }} invited you to join {{ .SpaceName }} \ No newline at end of file diff --git a/services/notifications/pkg/email/templates/spaces/unsharedSpace.email.body.tmpl b/services/notifications/pkg/email/templates/spaces/unsharedSpace.email.body.tmpl index 4c6fa9ece4..a09897c819 100644 --- a/services/notifications/pkg/email/templates/spaces/unsharedSpace.email.body.tmpl +++ b/services/notifications/pkg/email/templates/spaces/unsharedSpace.email.body.tmpl @@ -1,16 +1,8 @@ -Hello {{ .SpaceGrantee }}, +{{ .Greeting }} -{{ .SpaceSharer }} has removed you from "{{ .SpaceName }}". +{{ .MessageBody }} -You might still have access through your other groups or direct membership. Click here to check it: {{ .ShareLink }} - ----------------------------------------------------------- - -Hallo {{ .SpaceGrantee }}, - -{{ .SpaceSharer }} hat dich aus dem Space "{{ .SpaceName }}" entfernt. - -Du könntest über deine anderen Gruppen oder deiner direkten Mitgliedschaft noch Zugriff haben. Klicke hier zum Überprüfen: {{ .ShareLink }} +{{ .CallToAction }} --- diff --git a/services/notifications/pkg/email/templates/spaces/unsharedSpace.email.subject.tmpl b/services/notifications/pkg/email/templates/spaces/unsharedSpace.email.subject.tmpl deleted file mode 100644 index c6628dfb3d..0000000000 --- a/services/notifications/pkg/email/templates/spaces/unsharedSpace.email.subject.tmpl +++ /dev/null @@ -1 +0,0 @@ -{{ .SpaceSharer }} removed you from {{ .SpaceName }} \ No newline at end of file diff --git a/services/notifications/pkg/service/service.go b/services/notifications/pkg/service/service.go index 9c3df81f91..daf329475a 100644 --- a/services/notifications/pkg/service/service.go +++ b/services/notifications/pkg/service/service.go @@ -54,6 +54,7 @@ type eventsNotifier struct { gwClient gateway.GatewayAPIClient machineAuthAPIKey string emailTemplatePath string + translationPath string ocisURL string } @@ -86,17 +87,12 @@ func (s eventsNotifier) Run() error { } } -func (s eventsNotifier) render(bodyTemplate string, subjTemplate string, values map[string]string) (string, string, error) { - msg, err := email.RenderEmailTemplate(bodyTemplate, values, s.emailTemplatePath) +func (s eventsNotifier) render(template email.MessageTemplate, values map[string]interface{}) (string, string, error) { + // The locate have to come from the user setting + sub, msg, err := email.RenderEmailTemplate(template, "en", s.emailTemplatePath, s.translationPath, values) if err != nil { return "", "", err } - - sub, err := email.RenderEmailTemplate(subjTemplate, values, s.emailTemplatePath) - if err != nil { - return "", "", err - } - return msg, sub, nil } diff --git a/services/notifications/pkg/service/shares.go b/services/notifications/pkg/service/shares.go index 6758849436..8e54ffb9fb 100644 --- a/services/notifications/pkg/service/shares.go +++ b/services/notifications/pkg/service/shares.go @@ -3,6 +3,7 @@ package service import ( "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/v2/services/notifications/pkg/email" "google.golang.org/protobuf/types/known/fieldmaskpb" ) @@ -41,7 +42,7 @@ func (s eventsNotifier) handleShareCreated(e events.ShareCreated) { } sharerDisplayName := owner.GetDisplayName() - msg, subj, err := s.render("shares/shareCreated.email.body.tmpl", "shares/shareCreated.email.subject.tmpl", map[string]string{ + msg, subj, err := s.render(email.ShareCreated, map[string]interface{}{ "ShareGrantee": shareGrantee, "ShareSharer": sharerDisplayName, "ShareFolder": resourceInfo.Name, @@ -84,7 +85,7 @@ func (s eventsNotifier) handleShareExpired(e events.ShareExpired) { return } - msg, subj, err := s.render("shares/shareExpired.email.body.tmpl", "shares/shareExpired.email.subject.tmpl", map[string]string{ + msg, subj, err := s.render(email.ShareExpired, map[string]interface{}{ "ShareGrantee": shareGrantee, "ShareFolder": resourceInfo.GetName(), "ExpiredAt": e.ExpiredAt.Format("2006-01-02 15:04:05"), diff --git a/services/notifications/pkg/service/spaces.go b/services/notifications/pkg/service/spaces.go index 44affdfcc4..f07501e6ec 100644 --- a/services/notifications/pkg/service/spaces.go +++ b/services/notifications/pkg/service/spaces.go @@ -4,6 +4,7 @@ import ( "github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/utils" + "github.com/owncloud/ocis/v2/services/notifications/pkg/email" ) func (s eventsNotifier) handleSpaceShared(e events.SpaceShared) { @@ -54,7 +55,7 @@ func (s eventsNotifier) handleSpaceShared(e events.SpaceShared) { } sharerDisplayName := owner.GetDisplayName() - msg, subj, err := s.render("spaces/sharedSpace.email.body.tmpl", "spaces/sharedSpace.email.subject.tmpl", map[string]string{ + msg, subj, err := s.render(email.SharedSpace, map[string]interface{}{ "SpaceGrantee": spaceGrantee, "SpaceSharer": sharerDisplayName, "SpaceName": resourceInfo.GetSpace().GetName(), @@ -116,7 +117,7 @@ func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { } sharerDisplayName := owner.GetDisplayName() - msg, subj, err := s.render("spaces/unsharedSpace.email.body.tmpl", "spaces/unsharedSpace.email.subject.tmpl", map[string]string{ + msg, subj, err := s.render(email.UnsharedSpace, map[string]interface{}{ "SpaceGrantee": spaceGrantee, "SpaceSharer": sharerDisplayName, "SpaceName": resourceInfo.GetSpace().Name, @@ -151,7 +152,7 @@ func (s eventsNotifier) handleSpaceMembershipExpired(e events.SpaceMembershipExp return } - msg, subj, err := s.render("spaces/membershipExpired.email.body.tmpl", "spaces/membershipExpired.email.subject.tmpl", map[string]string{ + msg, subj, err := s.render(email.MembershipExpired, map[string]interface{}{ "SpaceGrantee": shareGrantee, "SpaceName": e.SpaceName, "ExpiredAt": e.ExpiredAt.Format("2006-01-02 15:04:05"), diff --git a/services/userlog/pkg/service/conversion.go b/services/userlog/pkg/service/conversion.go index c2cd6081a9..aca682c0d9 100644 --- a/services/userlog/pkg/service/conversion.go +++ b/services/userlog/pkg/service/conversion.go @@ -355,8 +355,10 @@ func composeMessage(nt NotificationTemplate, locale string, path string, vars ma } message, err := executeTemplate(messageraw, vars) + if err != nil { + return "", "", "", "", err + } return subject, subjectraw, message, messageraw, err - } func loadTemplates(nt NotificationTemplate, locale string, path string) (string, string) { From 22f8076a4f4c1c254cdb92b2a0783eaaf44dcff6 Mon Sep 17 00:00:00 2001 From: Roman Perekhod Date: Mon, 17 Apr 2023 13:46:57 +0200 Subject: [PATCH 2/5] README.md updated. returned value swapped --- .../make-emails-translatable-via-transifex.md | 1 + services/notifications/README.md | 38 ++++++++- services/notifications/pkg/config/config.go | 2 +- services/notifications/pkg/email/email.go | 28 +++---- services/notifications/pkg/service/service.go | 6 +- .../notifications/pkg/service/service_test.go | 81 ++++++++++++++++--- services/notifications/pkg/service/shares.go | 4 +- services/notifications/pkg/service/spaces.go | 6 +- services/userlog/pkg/config/config.go | 2 +- services/userlog/pkg/service/conversion.go | 3 - 10 files changed, 128 insertions(+), 43 deletions(-) diff --git a/changelog/unreleased/make-emails-translatable-via-transifex.md b/changelog/unreleased/make-emails-translatable-via-transifex.md index 55baae8f90..d7b40f862b 100644 --- a/changelog/unreleased/make-emails-translatable-via-transifex.md +++ b/changelog/unreleased/make-emails-translatable-via-transifex.md @@ -3,6 +3,7 @@ Enhancement: Notifications Make Emails translatable via transifex The transifex translation add in to the email templates. The optional environment variable NOTIFICATIONS_TRANSLATION_PATH added to config. +The optional global environment variable OCIS_TRANSLATION_PATH added to notifications and userlog config. https://github.com/owncloud/ocis/pull/6038 https://github.com/owncloud/ocis/issues/6025 diff --git a/services/notifications/README.md b/services/notifications/README.md index 700a62b348..06925acea5 100644 --- a/services/notifications/README.md +++ b/services/notifications/README.md @@ -2,14 +2,48 @@ The notification service is responsible for sending emails to users informing them about events that happened. To do this it hooks into the event system and listens for certain events that the users need to be informed about. +## Email notification + +The `notifications` service has embedded email body templates. +The email templates contain placeholders `{{ .Greeting }}`, `{{ .MessageBody }}`, `{{ .CallToAction }}` that are +replaced with translations (See [Translations](#translations) in this readme). +These embedded templates are available for all deployment scenarios. In addition, the service supports custom +templates. +The custom email template takes precedence over the embedded one. If a custom email template exists, the embedded ones +are not used. To configure custom email templates, +the `NOTIFICATIONS_EMAIL_TEMPLATE_PATH` environment variable needs to point to a base folder that will contain the email +templates. The source template files provided by ocis are located +in [https://github.com/owncloud/ocis/tree/master/services/notifications/pkg/email/templates](https://github.com/owncloud/ocis/tree/master/services/notifications/pkg/email/templates) in the `shares` +and `spaces` subfolders: +[shares/shareCreated.email.body.tmpl](https://github.com/owncloud/ocis/blob/master/services/notifications/pkg/email/templates/shares/shareCreated.email.body.tmpl) +[shares/shareExpired.email.body.tmpl](https://github.com/owncloud/ocis/blob/master/services/notifications/pkg/email/templates/shares/shareExpired.email.body.tmpl) +[spaces/membershipExpired.email.body.tmpl](https://github.com/owncloud/ocis/blob/master/services/notifications/pkg/email/templates/spaces/membershipExpired.email.body.tmpl) +[spaces/sharedSpace.email.body.tmpl](https://github.com/owncloud/ocis/blob/master/services/notifications/pkg/email/templates/spaces/sharedSpace.email.body.tmpl) +[spaces/unsharedSpace.email.body.tmpl](https://github.com/owncloud/ocis/blob/master/services/notifications/pkg/email/templates/spaces/unsharedSpace.email.body.tmpl) + +Custom Email templates referenced via `NOTIFICATIONS_EMAIL_TEMPLATE_PATH` must be located in subfolders `shares` +and `spaces` and have the same names as the embedded templates. This naming must match the embedded ones. +```text +templates +│ +└───shares +│ │ shareCreated.email.body.tmpl +│ │ shareExpired.email.body.tmpl +│ +└───spaces + │ membershipExpired.email.body.tmpl + │ sharedSpace.email.body.tmpl + │ unsharedSpace.email.body.tmpl +``` + ## Translations -The `translations` service has embedded translations sourced via transifex to provide a basic set of translated languages. +The `notifications` service has embedded translations sourced via transifex to provide a basic set of translated languages. These embedded translations are available for all deployment scenarios. In addition, the service supports custom translations, though it is currently not possible to just add custom translations to embedded ones. If custom translations are configured, the embedded ones are not used. To configure custom translations, the `NOTIFICATIONS_TRANSLATION_PATH` environment variable needs to point to a base folder that will contain the translation -files. This path must be available from all instances of the translations service, a shared storage is recommended. +files. This path must be available from all instances of the notifications service, a shared storage is recommended. Translation files must be of type [.po](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#PO-Files) or [.mo](https://www.gnu.org/software/gettext/manual/html_node/Binaries.html). For each language, the filename needs to be `translations.po` (or `translations.mo`) and stored in a folder structure defining the language code. In general the path/name diff --git a/services/notifications/pkg/config/config.go b/services/notifications/pkg/config/config.go index ebf5b0dddf..65ebf1c76b 100644 --- a/services/notifications/pkg/config/config.go +++ b/services/notifications/pkg/config/config.go @@ -28,7 +28,7 @@ type Notifications struct { Events Events `yaml:"events"` MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;NOTIFICATIONS_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary to access resources from other services."` EmailTemplatePath string `yaml:"email_template_path" env:"OCIS_EMAIL_TEMPLATE_PATH;NOTIFICATIONS_EMAIL_TEMPLATE_PATH" desc:"Path to Email notification templates overriding embedded ones."` - TranslationPath string `yaml:"translation_path" env:"NOTIFICATIONS_TRANSLATION_PATH" desc:"(optional) Set this to a path with custom translations to overwrite the builtin translations. See the documentation for more details."` + TranslationPath string `yaml:"translation_path" env:"OCIS_TRANSLATION_PATH,NOTIFICATIONS_TRANSLATION_PATH" desc:"(optional) Set this to a path with custom translations to overwrite the builtin translations. Note that file and folder naming rules apply, see the documentation for more details."` RevaGateway string `yaml:"reva_gateway" env:"OCIS_REVA_GATEWAY;REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata" deprecationVersion:"3.0" removalVersion:"3.1" deprecationInfo:"REVA_GATEWAY changing name for consistency" deprecationReplacement:"OCIS_REVA_GATEWAY"` GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"` } diff --git a/services/notifications/pkg/email/email.go b/services/notifications/pkg/email/email.go index 4171ea59b8..283559b9c5 100644 --- a/services/notifications/pkg/email/email.go +++ b/services/notifications/pkg/email/email.go @@ -17,21 +17,21 @@ var ( ) // RenderEmailTemplate renders the email template for a new share -func RenderEmailTemplate(et MessageTemplate, locale string, emailTemplatePath string, translationPath string, vars map[string]interface{}) (string, string, error) { - rawsub := ComposeMessage(et.Subject, locale, translationPath) +func RenderEmailTemplate(mt MessageTemplate, locale string, emailTemplatePath string, translationPath string, vars map[string]interface{}) (string, string, error) { + // translate a message + mt.Subject = ComposeMessage(mt.Subject, locale, translationPath) + mt.Greeting = ComposeMessage(mt.Greeting, locale, translationPath) + mt.MessageBody = ComposeMessage(mt.MessageBody, locale, translationPath) + mt.CallToAction = ComposeMessage(mt.CallToAction, locale, translationPath) + // replace the body email placeholders with the values - subject, err := executeRaw(rawsub, vars) + subject, err := executeRaw(mt.Subject, vars) if err != nil { return "", "", err } - bodyPlaceholders := map[string]interface{}{} - bodyPlaceholders["Greeting"] = ComposeMessage(et.Greeting, locale, translationPath) - bodyPlaceholders["MessageBody"] = ComposeMessage(et.MessageBody, locale, translationPath) - bodyPlaceholders["CallToAction"] = ComposeMessage(et.CallToAction, locale, translationPath) - // replace the body email template placeholders with the translated template - rawBody, err := executeEmailTemplate(emailTemplatePath, et.bodyTemplate, bodyPlaceholders) + rawBody, err := executeEmailTemplate(emailTemplatePath, mt) if err != nil { return "", "", err } @@ -43,19 +43,19 @@ func RenderEmailTemplate(et MessageTemplate, locale string, emailTemplatePath st return subject, body, nil } -func executeEmailTemplate(emailTemplatePath, templateName string, vars map[string]interface{}) (string, error) { +func executeEmailTemplate(emailTemplatePath string, mt MessageTemplate) (string, error) { var err error var tpl *template.Template // try to lookup the files in the filesystem - tpl, err = template.ParseFiles(filepath.Join(emailTemplatePath, templateName)) + tpl, err = template.ParseFiles(filepath.Join(emailTemplatePath, mt.bodyTemplate)) if err != nil { // template has not been found in the fs, or path has not been specified => use embed templates - tpl, err = template.ParseFS(templatesFS, filepath.Join("templates/", templateName)) + tpl, err = template.ParseFS(templatesFS, filepath.Join("templates/", mt.bodyTemplate)) if err != nil { return "", err } } - str, err := executeTemplate(tpl, vars) + str, err := executeTemplate(tpl, mt) if err != nil { return "", err } @@ -70,7 +70,7 @@ func executeRaw(raw string, vars map[string]interface{}) (string, error) { return executeTemplate(tpl, vars) } -func executeTemplate(tpl *template.Template, vars map[string]interface{}) (string, error) { +func executeTemplate(tpl *template.Template, vars any) (string, error) { var writer bytes.Buffer if err := tpl.Execute(&writer, vars); err != nil { return "", err diff --git a/services/notifications/pkg/service/service.go b/services/notifications/pkg/service/service.go index daf329475a..714696e720 100644 --- a/services/notifications/pkg/service/service.go +++ b/services/notifications/pkg/service/service.go @@ -89,11 +89,7 @@ func (s eventsNotifier) Run() error { func (s eventsNotifier) render(template email.MessageTemplate, values map[string]interface{}) (string, string, error) { // The locate have to come from the user setting - sub, msg, err := email.RenderEmailTemplate(template, "en", s.emailTemplatePath, s.translationPath, values) - if err != nil { - return "", "", err - } - return msg, sub, nil + return email.RenderEmailTemplate(template, "en", s.emailTemplatePath, s.translationPath, values) } func (s eventsNotifier) send(ctx context.Context, u *user.UserId, g *group.GroupId, msg, subj, sender string) error { diff --git a/services/notifications/pkg/service/service_test.go b/services/notifications/pkg/service/service_test.go index a9c323473a..aad5618199 100644 --- a/services/notifications/pkg/service/service_test.go +++ b/services/notifications/pkg/service/service_test.go @@ -66,8 +66,19 @@ var _ = Describe("Notifications", func() { Entry("Share Created", testChannel{ expectedReceipients: map[string]bool{sharee.GetId().GetOpaqueId(): true}, expectedSubject: "Dr. S. Harer shared 'secrets of the board' with you", - expectedSender: sharer.GetDisplayName(), - done: make(chan struct{}), + expectedMessage: `Hello Dr. S. Harer + +Dr. S. Harer has shared "secrets of the board" with you. + +Click here to view it: files/shares/with-me + + +--- +ownCloud - Store. Share. Work. +https://owncloud.com +`, + expectedSender: sharer.GetDisplayName(), + done: make(chan struct{}), }, events.Event{ Event: events.ShareCreated{ Sharer: sharer.GetId(), @@ -80,8 +91,19 @@ var _ = Describe("Notifications", func() { Entry("Share Expired", testChannel{ expectedReceipients: map[string]bool{sharee.GetId().GetOpaqueId(): true}, expectedSubject: "Share to 'secrets of the board' expired at 2023-04-17 16:42:00", - expectedSender: sharer.GetDisplayName(), - done: make(chan struct{}), + expectedMessage: `Hello Dr. S. Harer, + +Your share to secrets of the board has expired at 2023-04-17 16:42:00 + +Even though this share has been revoked you still might have access through other shares and/or space memberships. + + +--- +ownCloud - Store. Share. Work. +https://owncloud.com +`, + expectedSender: sharer.GetDisplayName(), + done: make(chan struct{}), }, events.Event{ Event: events.ShareExpired{ ShareOwner: sharer.GetId(), @@ -94,8 +116,19 @@ var _ = Describe("Notifications", func() { Entry("Added to Space", testChannel{ expectedReceipients: map[string]bool{sharee.GetId().GetOpaqueId(): true}, expectedSubject: "Dr. S. Harer invited you to join secret space", - expectedSender: sharer.GetDisplayName(), - done: make(chan struct{}), + expectedMessage: `Hello Dr. S. Harer, + +Dr. S. Harer has invited you to join "secret space". + +Click here to view it: f/spaceid + + +--- +ownCloud - Store. Share. Work. +https://owncloud.com +`, + expectedSender: sharer.GetDisplayName(), + done: make(chan struct{}), }, events.Event{ Event: events.SpaceShared{ Executant: sharer.GetId(), @@ -108,8 +141,21 @@ var _ = Describe("Notifications", func() { Entry("Removed from Space", testChannel{ expectedReceipients: map[string]bool{sharee.GetId().GetOpaqueId(): true}, expectedSubject: "Dr. S. Harer removed you from secret space", - expectedSender: sharer.GetDisplayName(), - done: make(chan struct{}), + expectedMessage: `Hello Dr. S. Harer, + +Dr. S. Harer has removed you from "secret space". + +You might still have access through your other groups or direct membership. + +Click here to check it: f/spaceid + + +--- +ownCloud - Store. Share. Work. +https://owncloud.com +`, + expectedSender: sharer.GetDisplayName(), + done: make(chan struct{}), }, events.Event{ Event: events.SpaceUnshared{ Executant: sharer.GetId(), @@ -121,8 +167,19 @@ var _ = Describe("Notifications", func() { Entry("Space Expired", testChannel{ expectedReceipients: map[string]bool{sharee.GetId().GetOpaqueId(): true}, expectedSubject: "Membership of 'secret space' expired at 2023-04-17 16:42:00", - expectedSender: sharer.GetDisplayName(), - done: make(chan struct{}), + expectedMessage: `Hello Dr. S. Harer, + +Your membership of space secret space has expired at 2023-04-17 16:42:00 + +Even though this membership has expired you still might have access through other shares and/or space memberships + + +--- +ownCloud - Store. Share. Work. +https://owncloud.com +`, + expectedSender: sharer.GetDisplayName(), + done: make(chan struct{}), }, events.Event{ Event: events.SpaceMembershipExpired{ SpaceOwner: sharer.GetId(), @@ -139,6 +196,7 @@ var _ = Describe("Notifications", func() { type testChannel struct { expectedReceipients map[string]bool expectedSubject string + expectedMessage string expectedSender string done chan struct{} } @@ -150,8 +208,7 @@ func (tc testChannel) SendMessage(ctx context.Context, userIDs []string, msg, su Expect(tc.expectedReceipients[u]).To(Equal(true)) } - // TODO: test the message? - //Expect(msg).To(Equal(tc.expectedMessage)) + Expect(msg).To(Equal(tc.expectedMessage)) Expect(subject).To(Equal(tc.expectedSubject)) Expect(senderDisplayName).To(Equal(tc.expectedSender)) tc.done <- struct{}{} diff --git a/services/notifications/pkg/service/shares.go b/services/notifications/pkg/service/shares.go index 8e54ffb9fb..6451783bc2 100644 --- a/services/notifications/pkg/service/shares.go +++ b/services/notifications/pkg/service/shares.go @@ -42,7 +42,7 @@ func (s eventsNotifier) handleShareCreated(e events.ShareCreated) { } sharerDisplayName := owner.GetDisplayName() - msg, subj, err := s.render(email.ShareCreated, map[string]interface{}{ + subj, msg, err := s.render(email.ShareCreated, map[string]interface{}{ "ShareGrantee": shareGrantee, "ShareSharer": sharerDisplayName, "ShareFolder": resourceInfo.Name, @@ -85,7 +85,7 @@ func (s eventsNotifier) handleShareExpired(e events.ShareExpired) { return } - msg, subj, err := s.render(email.ShareExpired, map[string]interface{}{ + subj, msg, err := s.render(email.ShareExpired, map[string]interface{}{ "ShareGrantee": shareGrantee, "ShareFolder": resourceInfo.GetName(), "ExpiredAt": e.ExpiredAt.Format("2006-01-02 15:04:05"), diff --git a/services/notifications/pkg/service/spaces.go b/services/notifications/pkg/service/spaces.go index f07501e6ec..bf7d747342 100644 --- a/services/notifications/pkg/service/spaces.go +++ b/services/notifications/pkg/service/spaces.go @@ -55,7 +55,7 @@ func (s eventsNotifier) handleSpaceShared(e events.SpaceShared) { } sharerDisplayName := owner.GetDisplayName() - msg, subj, err := s.render(email.SharedSpace, map[string]interface{}{ + subj, msg, err := s.render(email.SharedSpace, map[string]interface{}{ "SpaceGrantee": spaceGrantee, "SpaceSharer": sharerDisplayName, "SpaceName": resourceInfo.GetSpace().GetName(), @@ -117,7 +117,7 @@ func (s eventsNotifier) handleSpaceUnshared(e events.SpaceUnshared) { } sharerDisplayName := owner.GetDisplayName() - msg, subj, err := s.render(email.UnsharedSpace, map[string]interface{}{ + subj, msg, err := s.render(email.UnsharedSpace, map[string]interface{}{ "SpaceGrantee": spaceGrantee, "SpaceSharer": sharerDisplayName, "SpaceName": resourceInfo.GetSpace().Name, @@ -152,7 +152,7 @@ func (s eventsNotifier) handleSpaceMembershipExpired(e events.SpaceMembershipExp return } - msg, subj, err := s.render(email.MembershipExpired, map[string]interface{}{ + subj, msg, err := s.render(email.MembershipExpired, map[string]interface{}{ "SpaceGrantee": shareGrantee, "SpaceName": e.SpaceName, "ExpiredAt": e.ExpiredAt.Format("2006-01-02 15:04:05"), diff --git a/services/userlog/pkg/config/config.go b/services/userlog/pkg/config/config.go index 41985cb770..6998f82602 100644 --- a/services/userlog/pkg/config/config.go +++ b/services/userlog/pkg/config/config.go @@ -23,7 +23,7 @@ type Config struct { MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;USERLOG_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary to access resources from other services."` RevaGateway string `yaml:"reva_gateway" env:"OCIS_REVA_GATEWAY;REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata" deprecationVersion:"3.0" removalVersion:"3.1" deprecationInfo:"REVA_GATEWAY changing name for consistency" deprecationReplacement:"OCIS_REVA_GATEWAY"` - TranslationPath string `yaml:"translation_path" env:"USERLOG_TRANSLATION_PATH" desc:"(optional) Set this to a path with custom translations to overwrite the builtin translations. See the documentation for more details."` + TranslationPath string `yaml:"translation_path" env:"OCIS_TRANSLATION_PATH,USERLOG_TRANSLATION_PATH" desc:"(optional) Set this to a path with custom translations to overwrite the builtin translations. Note that file and folder naming rules apply, see the documentation for more details."` Events Events `yaml:"events"` Persistence Persistence `yaml:"persistence"` diff --git a/services/userlog/pkg/service/conversion.go b/services/userlog/pkg/service/conversion.go index aca682c0d9..e1bce3525c 100644 --- a/services/userlog/pkg/service/conversion.go +++ b/services/userlog/pkg/service/conversion.go @@ -355,9 +355,6 @@ func composeMessage(nt NotificationTemplate, locale string, path string, vars ma } message, err := executeTemplate(messageraw, vars) - if err != nil { - return "", "", "", "", err - } return subject, subjectraw, message, messageraw, err } From c3844fbb04dce79dc39ef20f6439dbc36a17de79 Mon Sep 17 00:00:00 2001 From: Roman Perekhod <2403905@gmail.com> Date: Mon, 17 Apr 2023 17:23:45 +0200 Subject: [PATCH 3/5] Update services/notifications/README.md Co-authored-by: Martin --- services/notifications/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/notifications/README.md b/services/notifications/README.md index 06925acea5..8aed5f52c5 100644 --- a/services/notifications/README.md +++ b/services/notifications/README.md @@ -2,7 +2,7 @@ The notification service is responsible for sending emails to users informing them about events that happened. To do this it hooks into the event system and listens for certain events that the users need to be informed about. -## Email notification +## Email Notifications The `notifications` service has embedded email body templates. The email templates contain placeholders `{{ .Greeting }}`, `{{ .MessageBody }}`, `{{ .CallToAction }}` that are From 3bf85c22bf2b37eaec916ffd9ecf622ebbd2cb1b Mon Sep 17 00:00:00 2001 From: Roman Perekhod <2403905@gmail.com> Date: Mon, 17 Apr 2023 17:24:31 +0200 Subject: [PATCH 4/5] Update services/notifications/README.md Co-authored-by: Martin --- services/notifications/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/notifications/README.md b/services/notifications/README.md index 8aed5f52c5..15840736bd 100644 --- a/services/notifications/README.md +++ b/services/notifications/README.md @@ -6,7 +6,7 @@ The notification service is responsible for sending emails to users informing th The `notifications` service has embedded email body templates. The email templates contain placeholders `{{ .Greeting }}`, `{{ .MessageBody }}`, `{{ .CallToAction }}` that are -replaced with translations (See [Translations](#translations) in this readme). +replaced with translations, see the [Translations](#translations) section. These embedded templates are available for all deployment scenarios. In addition, the service supports custom templates. The custom email template takes precedence over the embedded one. If a custom email template exists, the embedded ones From 1022b5ec1e385252759d9eb40b2e0c6f4856c821 Mon Sep 17 00:00:00 2001 From: Roman Perekhod <2403905@gmail.com> Date: Mon, 17 Apr 2023 17:33:34 +0200 Subject: [PATCH 5/5] Update services/notifications/README.md Co-authored-by: Martin --- services/notifications/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/notifications/README.md b/services/notifications/README.md index 15840736bd..81b25c4b99 100644 --- a/services/notifications/README.md +++ b/services/notifications/README.md @@ -2,7 +2,7 @@ The notification service is responsible for sending emails to users informing them about events that happened. To do this it hooks into the event system and listens for certain events that the users need to be informed about. -## Email Notifications +## Email Notification Templates The `notifications` service has embedded email body templates. The email templates contain placeholders `{{ .Greeting }}`, `{{ .MessageBody }}`, `{{ .CallToAction }}` that are