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.
This commit is contained in:
Roman Perekhod
2023-04-16 14:02:40 +02:00
parent c0be639f80
commit cd59424a16
23 changed files with 382 additions and 79 deletions
@@ -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
}
+48 -3
View File
@@ -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
}
+9
View File
@@ -0,0 +1,9 @@
[main]
host = https://www.transifex.com
[o:owncloud-org:p:owncloud:r:ocis-notifications]
file_filter = locale/<lang>/LC_MESSAGES/notifications.po
minimum_perc = 75
source_file = notifications.pot
source_lang = en
type = 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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""
@@ -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
}
@@ -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 }}
---
@@ -1 +0,0 @@
{{ .ShareSharer }} shared '{{ .ShareFolder }}' with you
@@ -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 }}
---
@@ -1 +0,0 @@
Share to '{{ .ShareFolder }}' expired at {{ .ExpiredAt }}
@@ -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 }}
---
@@ -1 +0,0 @@
Membership of '{{ .SpaceName }}' expired at {{ .ExpiredAt }}
@@ -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 }}
---
@@ -1 +0,0 @@
{{ .SpaceSharer }} invited you to join {{ .SpaceName }}
@@ -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 }}
---
@@ -1 +0,0 @@
{{ .SpaceSharer }} removed you from {{ .SpaceName }}