diff --git a/docs/content/docs/guides/inbox-tagging.mdx b/docs/content/docs/guides/inbox-tagging.mdx index e371e5b11..e59ddd49a 100644 --- a/docs/content/docs/guides/inbox-tagging.mdx +++ b/docs/content/docs/guides/inbox-tagging.mdx @@ -140,7 +140,7 @@ Tagging reads English, German, French, Spanish, Italian and Dutch replies out of Adding a language does two things: - **It is named to the classifier**, alongside each message, so it knows what to expect. It is a hint, not a translation: nothing about the message or the questions changes, and the built-in questions stay in English. Any of 44 languages can be named: Arabic, Bengali, Bulgarian, Catalan, Chinese, Croatian, Czech, Danish, Dutch, English, Estonian, Filipino, Finnish, French, German, Greek, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Latvian, Lithuanian, Malay, Norwegian, Persian, Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovenian, Spanish, Swahili, Swedish, Tamil, Thai, Turkish, Ukrainian, Urdu and Vietnamese. -- **It adds that language to the reading done before any call**, for these 27: Arabic, Chinese, Czech, Danish, Dutch, Finnish, French, German, Greek, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Norwegian, Polish, Portuguese, Romanian, Russian, Spanish, Swedish, Thai, Turkish, Ukrainian and Vietnamese. The picker marks the others as a classifier hint only. +- **It adds that language to the reading done before any call**, for every one of them. English is always read. The reading before any call covers: diff --git a/docs/content/docs/guides/unsubscribe.mdx b/docs/content/docs/guides/unsubscribe.mdx index 217f84b14..c6b91fb73 100644 --- a/docs/content/docs/guides/unsubscribe.mdx +++ b/docs/content/docs/guides/unsubscribe.mdx @@ -37,6 +37,8 @@ The token carries 128 bits from a cryptographic random source, which is more tha A mailbox or campaign with a verified [custom tracking domain](/guides/mailboxes/#custom-tracking-domain) serves its unsubscribe link there, so a recipient reads `https://t.yourdomain.com/unsubscribe/...` rather than an address on the platform. The campaign's own domain wins over the mailbox's, and only a verified one is used. The page itself is the same page, named after you: it carries no logo, no product name and no scripts, because the email came from your mailbox and not from a platform. +The page speaks the recipient's language. It follows the language their browser asks for, in any of the [tagging languages](/guides/inbox-tagging/#tagging-languages) (German, French, Japanese, Arabic and the rest, with Traditional Chinese for Taiwan and Hong Kong), and falls back to English for a language it does not have. Right-to-left languages read right to left. Nothing about this is a setting: a German recipient reads German whatever language your workspace or campaign is in. + Without a custom domain the link stays on the instance's own API address. Either way it is the sender's infrastructure, not a third party's, and the recipient's opt-out reaches the same suppression list. Unsubscribe links travel with a [workspace export](/guides/workspace-export-import/), in the Campaigns group, so moving to another instance does not break a link someone is still holding. Keep serving the tracking domain they were minted on, or they arrive at a host that no longer answers. @@ -61,7 +63,7 @@ Two things to know. Gmail only shows its button for mail it already classes as b ## Replies that ask to stop -Warmbly reads every reply to a campaign email. One that asks to stop, in wording such as "unsubscribe", "remove me", "stop emailing me", "opt out" or "do not contact", puts the sender on the suppression list immediately. German replies are read the same way, whatever languages the workspace tags in: "bitte austragen", "keine weiteren E-Mails" or "löschen Sie meine Daten" opt the sender out too. Only the new text of the reply is read: the quoted history below it carries your own opt-out wording and is ignored. Phrases match on whole words, so "stop by our booth" does not opt anyone out. +Warmbly reads every reply to a campaign email. One that asks to stop, in wording such as "unsubscribe", "remove me", "stop emailing me", "opt out" or "do not contact", puts the sender on the suppression list immediately. Replies in every tagging language are read the same way, whatever languages the workspace tags in: "bitte austragen", "désinscrivez-moi", "denme de baja", "wypiszcie mnie", "отпишите меня" or "配信停止" opt the sender out too. The quoted history is cut in each of those languages as well, so a campaign that carries its own opt-out line in German or Japanese does not opt out everyone who answers it. Only the new text of the reply is read: the quoted history below it carries your own opt-out wording and is ignored. Phrases match on whole words, so "stop by our booth" does not opt anyone out. Only a person answering you can opt out this way: someone replying in one of your campaign threads, or one of your contacts writing to you. Everything else that lands in a mailbox is left alone, even when it says "unsubscribe": diff --git a/internal/api/handler/unsubscribe.go b/internal/api/handler/unsubscribe.go index c4ac8ffb6..e7c1fc90f 100644 --- a/internal/api/handler/unsubscribe.go +++ b/internal/api/handler/unsubscribe.go @@ -5,6 +5,8 @@ import ( "errors" "html/template" "net/http" + "sort" + "strconv" "strings" "time" @@ -42,15 +44,12 @@ func (h *Handler) UnsubscribePage(c *gin.Context) { if !ok { return } + t := unsubCopyFor(c.GetHeader("Accept-Language")) if claims.ContactID == uuid.Nil { - renderUnsubPage(c, http.StatusOK, unsubView{Title: "This was a test email", Body: "Test sends carry a link that is not tied to anyone, so there is nothing to unsubscribe."}) + renderUnsubPage(c, http.StatusOK, unsubView{Title: t.TestTitle, Body: t.TestBody}) return } - renderUnsubPage(c, http.StatusOK, unsubView{ - Title: "Unsubscribe from these emails?", - Body: "Confirm and you will not receive further emails from this sender.", - Confirm: c.Request.URL.Path, - }) + renderUnsubPage(c, http.StatusOK, unsubView{Title: t.ConfirmTitle, Body: t.ConfirmBody, Confirm: c.Request.URL.Path}) } // unsubscribeBodyLimit caps the public POST bodies. The engine-wide limit is @@ -63,6 +62,7 @@ func (h *Handler) UnsubscribeSubmit(c *gin.Context) { oneClick := strings.EqualFold(strings.TrimSpace(c.PostForm("List-Unsubscribe")), "One-Click") confirmed := c.PostForm("confirm") == "1" + t := unsubCopyFor(c.GetHeader("Accept-Language")) claims, err := h.verifyUnsubscribeToken(c.Request.Context(), c.Param("token")) if err != nil { if oneClick { @@ -76,7 +76,7 @@ func (h *Handler) UnsubscribeSubmit(c *gin.Context) { c.Status(http.StatusOK) return } - renderUnsubPage(c, unsubStatus(err), unsubInvalid(err)) + renderUnsubPage(c, unsubStatus(err), unsubInvalid(t, err)) return } if claims.ContactID == uuid.Nil { @@ -84,18 +84,14 @@ func (h *Handler) UnsubscribeSubmit(c *gin.Context) { c.Status(http.StatusOK) return } - renderUnsubPage(c, http.StatusOK, unsubView{Title: "This was a test email", Body: "Test sends carry a link that is not tied to anyone, so there is nothing to unsubscribe."}) + renderUnsubPage(c, http.StatusOK, unsubView{Title: t.TestTitle, Body: t.TestBody}) return } // A browser POST without the confirm field is not the button: show the // confirm page again rather than act on it. if !oneClick && !confirmed { - renderUnsubPage(c, http.StatusOK, unsubView{ - Title: "Unsubscribe from these emails?", - Body: "Confirm and you will not receive further emails from this sender.", - Confirm: c.Request.URL.Path, - }) + renderUnsubPage(c, http.StatusOK, unsubView{Title: t.ConfirmTitle, Body: t.ConfirmBody, Confirm: c.Request.URL.Path}) return } @@ -114,14 +110,10 @@ func (h *Handler) UnsubscribeSubmit(c *gin.Context) { return } if xerr != nil { - renderUnsubPage(c, http.StatusOK, unsubView{Title: "We couldn't process that link", Body: "The link is no longer valid. Reply to the email instead and the sender will stop."}) + renderUnsubPage(c, http.StatusOK, unsubView{Title: t.FailedTitle, Body: t.FailedBody}) return } - renderUnsubPage(c, http.StatusOK, unsubView{ - Title: "You've been unsubscribed", - Body: "You will not receive further emails from this sender.", - Resubscribe: c.Request.URL.Path + "/resubscribe", - }) + renderUnsubPage(c, http.StatusOK, unsubView{Title: t.DoneTitle, Body: t.DoneBody, Resubscribe: c.Request.URL.Path + "/resubscribe"}) } func (h *Handler) UnsubscribeUndo(c *gin.Context) { @@ -130,15 +122,16 @@ func (h *Handler) UnsubscribeUndo(c *gin.Context) { if !ok { return } + t := unsubCopyFor(c.GetHeader("Accept-Language")) if claims.ContactID == uuid.Nil { - renderUnsubPage(c, http.StatusBadRequest, unsubInvalid(unsublink.ErrInvalid)) + renderUnsubPage(c, http.StatusBadRequest, unsubInvalid(t, unsublink.ErrInvalid)) return } if xerr := h.AdvancedService.Resubscribe(c.Request.Context(), claims.OrgID, claims.ContactID); xerr != nil { - renderUnsubPage(c, http.StatusOK, unsubView{Title: "We couldn't process that link", Body: "The link is no longer valid. Reply to the email and the sender can add you back."}) + renderUnsubPage(c, http.StatusOK, unsubView{Title: t.FailedTitle, Body: t.FailedResubscribeBody}) return } - renderUnsubPage(c, http.StatusOK, unsubView{Title: "You're subscribed again", Body: "The sender can email you as before. You can unsubscribe from any later email."}) + renderUnsubPage(c, http.StatusOK, unsubView{Title: t.ResubscribedTitle, Body: t.ResubscribedBody}) } // errUnsubUnavailable is a ticket lookup that failed rather than a link that @@ -180,7 +173,7 @@ func (h *Handler) verifyUnsubscribeToken(ctx context.Context, token string) (uns func (h *Handler) unsubscribeClaims(c *gin.Context) (unsublink.Claims, bool) { claims, err := h.verifyUnsubscribeToken(c.Request.Context(), c.Param("token")) if err != nil { - renderUnsubPage(c, unsubStatus(err), unsubInvalid(err)) + renderUnsubPage(c, unsubStatus(err), unsubInvalid(unsubCopyFor(c.GetHeader("Accept-Language")), err)) return claims, false } return claims, true @@ -195,14 +188,14 @@ func unsubStatus(err error) int { return http.StatusBadRequest } -func unsubInvalid(err error) unsubView { +func unsubInvalid(t unsubCopy, err error) unsubView { switch err { case errUnsubUnavailable: - return unsubView{Title: "Try again shortly", Body: "We could not check this link just now. Open it again in a few minutes, or reply to the email and the sender will stop."} + return unsubView{Title: t.RetryTitle, Body: t.RetryBody} case unsublink.ErrExpired: - return unsubView{Title: "This link has expired", Body: "Reply to the email instead and the sender will stop."} + return unsubView{Title: t.ExpiredTitle, Body: t.ReplyBody} } - return unsubView{Title: "This unsubscribe link is invalid", Body: "Reply to the email instead and the sender will stop."} + return unsubView{Title: t.InvalidTitle, Body: t.ReplyBody} } type unsubView struct { @@ -210,22 +203,89 @@ type unsubView struct { Body string Confirm string // POST target of the confirm button, when shown Resubscribe string // POST target of the resubscribe button, when shown + + // Set by renderUnsubPage from the request's language. + Lang, ConfirmButton, ResubscribeButton string + RTL bool +} + +// unsubLanguage picks the page's language from the browser's +// Accept-Language: the most preferred one the page is written in, English +// when none is. +func unsubLanguage(acceptLanguage string) string { + type pref struct { + code string + q float64 + } + var prefs []pref + for _, part := range strings.Split(acceptLanguage, ",") { + tag, params, _ := strings.Cut(strings.TrimSpace(part), ";") + q := 1.0 + if v, ok := strings.CutPrefix(strings.TrimSpace(params), "q="); ok { + f, err := strconv.ParseFloat(v, 64) + // A qvalue is 0 to 1; NaN fails both comparisons. + if err != nil || !(f >= 0 && f <= 1) { + continue + } + q = f + } + if code := unsubLanguageCode(tag); code != "" && q > 0 { + prefs = append(prefs, pref{code, q}) + } + } + sort.SliceStable(prefs, func(i, j int) bool { return prefs[i].q > prefs[j].q }) + if len(prefs) > 0 { + return prefs[0].code + } + return "en" +} + +// unsubLanguageCode is the page language a tag asks for, or "" for one it +// has none of. Norwegian and Tagalog tags name the language written as nb and +// fil, and Taiwan, Hong Kong and Macau read Traditional Chinese. +func unsubLanguageCode(tag string) string { + parts := strings.Split(strings.ToLower(strings.ReplaceAll(tag, "_", "-")), "-") + code := parts[0] + switch code { + case "no", "nn": + code = "nb" + case "tl": + code = "fil" + case "zh": + for _, p := range parts[1:] { + if p == "hant" || p == "tw" || p == "hk" || p == "mo" { + code = "zh-Hant" + } + } + } + if _, ok := unsubCopies[code]; !ok { + return "" + } + return code +} + +func unsubCopyFor(acceptLanguage string) unsubCopy { + return unsubCopies[unsubLanguage(acceptLanguage)] } // A neutral page: the email came from the customer's mailbox, so the page // names no brand and carries no scripts or external assets. -var unsubTemplate = template.Must(template.New("unsubscribe").Parse(`
+var unsubTemplate = template.Must(template.New("unsubscribe").Parse(`{{.Body}}
-{{if .Confirm}}{{end}} -{{if .Resubscribe}}{{end}} +{{if .Confirm}}{{end}} +{{if .Resubscribe}}{{end}} `)) func renderUnsubPage(c *gin.Context, status int, v unsubView) { + v.Lang = unsubLanguage(c.GetHeader("Accept-Language")) + t := unsubCopies[v.Lang] + v.ConfirmButton, v.ResubscribeButton, v.RTL = t.ConfirmButton, t.ResubscribeButton, t.RTL + c.Header("Vary", "Accept-Language") c.Header("Cache-Control", "no-store") c.Header("X-Robots-Tag", "noindex") c.Status(status) diff --git a/internal/api/handler/unsubscribe_i18n.go b/internal/api/handler/unsubscribe_i18n.go new file mode 100644 index 000000000..9f42107da --- /dev/null +++ b/internal/api/handler/unsubscribe_i18n.go @@ -0,0 +1,931 @@ +package handler + +// unsubCopy is the recipient-facing unsubscribe page in one language. +type unsubCopy struct { + RTL bool + TestTitle, TestBody string + ConfirmTitle string + ConfirmBody string + ConfirmButton string + DoneTitle, DoneBody string + ResubscribeButton string + ResubscribedTitle string + ResubscribedBody string + FailedTitle string + FailedBody string + FailedResubscribeBody string + RetryTitle, RetryBody string + ExpiredTitle string + InvalidTitle string + ReplyBody string +} + +// unsubCopies is the page in every tagging language (models.MailLanguageNames) +// and Traditional Chinese. The tracking service's own two pages carry the same +// invalid and retry wording in tracking/src/unsubscribe_i18n.rs. +var unsubCopies = map[string]unsubCopy{ + "en": { + TestTitle: "This was a test email", + TestBody: "Test sends carry a link that is not tied to anyone, so there is nothing to unsubscribe.", + ConfirmTitle: "Unsubscribe from these emails?", + ConfirmBody: "Confirm and you will not receive further emails from this sender.", + ConfirmButton: "Unsubscribe", + DoneTitle: "You've been unsubscribed", + DoneBody: "You will not receive further emails from this sender.", + ResubscribeButton: "Unsubscribed by mistake? Resubscribe", + ResubscribedTitle: "You're subscribed again", + ResubscribedBody: "The sender can email you as before. You can unsubscribe from any later email.", + FailedTitle: "We couldn't process that link", + FailedBody: "The link is no longer valid. Reply to the email instead and the sender will stop.", + FailedResubscribeBody: "The link is no longer valid. Reply to the email and the sender can add you back.", + RetryTitle: "Try again shortly", + RetryBody: "We could not check this link just now. Open it again in a few minutes, or reply to the email and the sender will stop.", + ExpiredTitle: "This link has expired", + InvalidTitle: "This unsubscribe link is invalid", + ReplyBody: "Reply to the email instead and the sender will stop.", + }, + "ar": { + RTL: true, + TestTitle: "كانت هذه رسالة تجريبية", + TestBody: "تحتوي الرسائل التجريبية على رابط غير مرتبط بأي شخص، لذا لا يوجد اشتراك لإلغائه.", + ConfirmTitle: "هل تريد إلغاء الاشتراك في هذه الرسائل؟", + ConfirmBody: "أكِّد ذلك ولن تتلقى أي رسائل أخرى من هذا المرسِل.", + ConfirmButton: "إلغاء الاشتراك", + DoneTitle: "تم إلغاء اشتراكك", + DoneBody: "لن تتلقى أي رسائل أخرى من هذا المرسِل.", + ResubscribeButton: "هل ألغيت الاشتراك عن طريق الخطأ؟ أعد الاشتراك", + ResubscribedTitle: "تم تجديد اشتراكك", + ResubscribedBody: "يمكن للمرسِل مراسلتك كما في السابق. ويمكنك إلغاء الاشتراك من أي رسالة لاحقة.", + FailedTitle: "تعذّرت علينا معالجة هذا الرابط", + FailedBody: "لم يعد هذا الرابط صالحًا. يمكنك بدلًا من ذلك الرد على الرسالة وسيتوقف المرسِل عن مراسلتك.", + FailedResubscribeBody: "لم يعد هذا الرابط صالحًا. ردّ على الرسالة ويمكن للمرسِل إعادة إضافتك.", + RetryTitle: "حاول مرة أخرى بعد قليل", + RetryBody: "تعذّر علينا التحقق من هذا الرابط الآن. افتحه مرة أخرى بعد بضع دقائق، أو ردّ على الرسالة وسيتوقف المرسِل عن مراسلتك.", + ExpiredTitle: "انتهت صلاحية هذا الرابط", + InvalidTitle: "رابط إلغاء الاشتراك هذا غير صالح", + ReplyBody: "يمكنك بدلًا من ذلك الرد على الرسالة وسيتوقف المرسِل عن مراسلتك.", + }, + "bg": { + TestTitle: "Това беше тестов имейл", + TestBody: "Тестовите имейли съдържат връзка, която не е свързана с конкретен човек, затова няма абонамент за прекратяване.", + ConfirmTitle: "Отписване от тези имейли?", + ConfirmBody: "Потвърдете и няма да получавате повече имейли от този подател.", + ConfirmButton: "Отписване", + DoneTitle: "Отписахте се успешно", + DoneBody: "Няма да получавате повече имейли от този подател.", + ResubscribeButton: "Отписахте се по погрешка? Абонирайте се отново", + ResubscribedTitle: "Отново сте абонирани", + ResubscribedBody: "Подателят може да ви пише както преди. Можете да се отпишете от всеки следващ имейл.", + FailedTitle: "Не успяхме да обработим тази връзка", + FailedBody: "Връзката вече не е валидна. Вместо това отговорете на имейла и подателят ще спре да ви пише.", + FailedResubscribeBody: "Връзката вече не е валидна. Отговорете на имейла и подателят може да ви добави отново.", + RetryTitle: "Опитайте отново след малко", + RetryBody: "В момента не успяхме да проверим тази връзка. Отворете я отново след няколко минути или отговорете на имейла и подателят ще спре да ви пише.", + ExpiredTitle: "Валидността на тази връзка е изтекла", + InvalidTitle: "Тази връзка за отписване е невалидна", + ReplyBody: "Вместо това отговорете на имейла и подателят ще спре да ви пише.", + }, + "bn": { + TestTitle: "এটি একটি পরীক্ষামূলক ইমেইল ছিল", + TestBody: "পরীক্ষামূলক ইমেইলের লিংক কোনো নির্দিষ্ট ব্যক্তির সঙ্গে যুক্ত নয়, তাই আনসাবস্ক্রাইব করার কিছু নেই।", + ConfirmTitle: "এই ইমেইলগুলো থেকে আনসাবস্ক্রাইব করবেন?", + ConfirmBody: "নিশ্চিত করলে এই প্রেরকের কাছ থেকে আপনি আর কোনো ইমেইল পাবেন না।", + ConfirmButton: "আনসাবস্ক্রাইব করুন", + DoneTitle: "আপনার সাবস্ক্রিপশন বাতিল হয়েছে", + DoneBody: "এই প্রেরকের কাছ থেকে আপনি আর কোনো ইমেইল পাবেন না।", + ResubscribeButton: "ভুল করে আনসাবস্ক্রাইব করেছেন? আবার সাবস্ক্রাইব করুন", + ResubscribedTitle: "আপনার সাবস্ক্রিপশন আবার চালু হয়েছে", + ResubscribedBody: "প্রেরক আগের মতোই আপনাকে ইমেইল পাঠাতে পারবেন। পরবর্তী যেকোনো ইমেইল থেকে আপনি আনসাবস্ক্রাইব করতে পারবেন।", + FailedTitle: "আমরা এই লিংকটি প্রক্রিয়া করতে পারিনি", + FailedBody: "লিংকটি আর বৈধ নয়। এর পরিবর্তে ইমেইলটির উত্তর দিন, তাহলে প্রেরক আপনাকে ইমেইল পাঠানো বন্ধ করবেন।", + FailedResubscribeBody: "লিংকটি আর বৈধ নয়। ইমেইলটির উত্তর দিন, তাহলে প্রেরক আপনাকে আবার যুক্ত করতে পারবেন।", + RetryTitle: "কিছুক্ষণ পরে আবার চেষ্টা করুন", + RetryBody: "এই মুহূর্তে আমরা লিংকটি যাচাই করতে পারিনি। কয়েক মিনিট পরে এটি আবার খুলুন, অথবা ইমেইলটির উত্তর দিন, তাহলে প্রেরক আপনাকে ইমেইল পাঠানো বন্ধ করবেন।", + ExpiredTitle: "এই লিংকের মেয়াদ শেষ হয়ে গেছে", + InvalidTitle: "এই আনসাবস্ক্রাইব লিংকটি বৈধ নয়", + ReplyBody: "এর পরিবর্তে ইমেইলটির উত্তর দিন, তাহলে প্রেরক আপনাকে ইমেইল পাঠানো বন্ধ করবেন।", + }, + "ca": { + TestTitle: "Aquest era un correu de prova", + TestBody: "Els correus de prova contenen un enllaç que no està vinculat a ningú, de manera que no hi ha cap subscripció per cancel·lar.", + ConfirmTitle: "Voleu deixar de rebre aquests correus?", + ConfirmBody: "Confirmeu-ho i no rebreu més correus d'aquest remitent.", + ConfirmButton: "Cancel·la la subscripció", + DoneTitle: "S'ha cancel·lat la vostra subscripció", + DoneBody: "No rebreu més correus d'aquest remitent.", + ResubscribeButton: "Us heu donat de baixa per error? Torneu-vos a subscriure", + ResubscribedTitle: "S'ha reactivat la vostra subscripció", + ResubscribedBody: "El remitent us pot escriure com abans. Podeu cancel·lar la subscripció des de qualsevol correu posterior.", + FailedTitle: "No hem pogut processar aquest enllaç", + FailedBody: "L'enllaç ja no és vàlid. En lloc d'això, responeu al correu i el remitent deixarà d'escriure-us.", + FailedResubscribeBody: "L'enllaç ja no és vàlid. Responeu al correu i el remitent us podrà tornar a afegir.", + RetryTitle: "Torneu-ho a provar d'aquí a una estona", + RetryBody: "Ara mateix no hem pogut comprovar aquest enllaç. Torneu-lo a obrir d'aquí a uns minuts o responeu al correu i el remitent deixarà d'escriure-us.", + ExpiredTitle: "Aquest enllaç ha caducat", + InvalidTitle: "Aquest enllaç per cancel·lar la subscripció no és vàlid", + ReplyBody: "En lloc d'això, responeu al correu i el remitent deixarà d'escriure-us.", + }, + "cs": { + TestTitle: "Toto byl testovací e-mail", + TestBody: "Testovací e-maily obsahují odkaz, který není spojen s žádným příjemcem, takže není z čeho se odhlašovat.", + ConfirmTitle: "Odhlásit se z odběru těchto e-mailů?", + ConfirmBody: "Po potvrzení vám tento odesílatel už nebude posílat další e-maily.", + ConfirmButton: "Odhlásit se", + DoneTitle: "Odběr byl zrušen", + DoneBody: "Od tohoto odesílatele již nebudete dostávat další e-maily.", + ResubscribeButton: "Odhlásili jste se omylem? Přihlásit se znovu", + ResubscribedTitle: "Odběr byl obnoven", + ResubscribedBody: "Odesílatel vám může psát jako dříve. Z odběru se můžete odhlásit v kterémkoli dalším e-mailu.", + FailedTitle: "Tento odkaz se nám nepodařilo zpracovat", + FailedBody: "Odkaz již není platný. Odpovězte místo toho na e-mail a odesílatel vám přestane psát.", + FailedResubscribeBody: "Odkaz již není platný. Odpovězte na e-mail a odesílatel vás může znovu přidat.", + RetryTitle: "Zkuste to prosím za chvíli", + RetryBody: "Tento odkaz se nám teď nepodařilo ověřit. Otevřete jej znovu za několik minut, nebo odpovězte na e-mail a odesílatel vám přestane psát.", + ExpiredTitle: "Platnost tohoto odkazu vypršela", + InvalidTitle: "Tento odkaz k odhlášení je neplatný", + ReplyBody: "Odpovězte místo toho na e-mail a odesílatel vám přestane psát.", + }, + "da": { + TestTitle: "Dette var en test-e-mail", + TestBody: "Test-e-mails indeholder et link, der ikke er knyttet til nogen, så der er intet at afmelde.", + ConfirmTitle: "Vil du afmelde disse e-mails?", + ConfirmBody: "Bekræft, så modtager du ikke flere e-mails fra denne afsender.", + ConfirmButton: "Afmeld", + DoneTitle: "Du er nu afmeldt", + DoneBody: "Du modtager ikke flere e-mails fra denne afsender.", + ResubscribeButton: "Afmeldt ved en fejl? Tilmeld dig igen", + ResubscribedTitle: "Du er tilmeldt igen", + ResubscribedBody: "Afsenderen kan skrive til dig som før. Du kan afmelde dig fra enhver senere e-mail.", + FailedTitle: "Vi kunne ikke behandle linket", + FailedBody: "Linket er ikke længere gyldigt. Svar i stedet på e-mailen, så holder afsenderen op med at skrive til dig.", + FailedResubscribeBody: "Linket er ikke længere gyldigt. Svar på e-mailen, så kan afsenderen tilføje dig igen.", + RetryTitle: "Prøv igen om lidt", + RetryBody: "Vi kunne ikke kontrollere linket lige nu. Åbn det igen om et par minutter, eller svar på e-mailen, så holder afsenderen op med at skrive til dig.", + ExpiredTitle: "Linket er udløbet", + InvalidTitle: "Afmeldingslinket er ugyldigt", + ReplyBody: "Svar i stedet på e-mailen, så holder afsenderen op med at skrive til dig.", + }, + "de": { + TestTitle: "Dies war eine Test-E-Mail", + TestBody: "Test-E-Mails enthalten einen Link, der keiner Person zugeordnet ist. Es gibt daher nichts abzubestellen.", + ConfirmTitle: "Diese E-Mails abbestellen?", + ConfirmBody: "Nach Ihrer Bestätigung erhalten Sie keine weiteren E-Mails von diesem Absender.", + ConfirmButton: "Abbestellen", + DoneTitle: "Sie wurden abgemeldet", + DoneBody: "Sie erhalten keine weiteren E-Mails von diesem Absender.", + ResubscribeButton: "Versehentlich abgemeldet? Wieder anmelden", + ResubscribedTitle: "Sie sind wieder angemeldet", + ResubscribedBody: "Der Absender kann Ihnen wie bisher schreiben. Sie können sich über jede spätere E-Mail wieder abmelden.", + FailedTitle: "Dieser Link konnte nicht verarbeitet werden", + FailedBody: "Der Link ist nicht mehr gültig. Antworten Sie stattdessen auf die E-Mail, dann schreibt Ihnen der Absender nicht mehr.", + FailedResubscribeBody: "Der Link ist nicht mehr gültig. Antworten Sie auf die E-Mail, dann kann der Absender Sie wieder aufnehmen.", + RetryTitle: "Bitte versuchen Sie es gleich noch einmal", + RetryBody: "Wir konnten diesen Link gerade nicht prüfen. Öffnen Sie ihn in ein paar Minuten erneut oder antworten Sie auf die E-Mail, dann schreibt Ihnen der Absender nicht mehr.", + ExpiredTitle: "Dieser Link ist abgelaufen", + InvalidTitle: "Dieser Abmeldelink ist ungültig", + ReplyBody: "Antworten Sie stattdessen auf die E-Mail, dann schreibt Ihnen der Absender nicht mehr.", + }, + "el": { + TestTitle: "Αυτό ήταν ένα δοκιμαστικό email", + TestBody: "Τα δοκιμαστικά email περιέχουν σύνδεσμο που δεν αντιστοιχεί σε κανέναν, οπότε δεν υπάρχει εγγραφή για κατάργηση.", + ConfirmTitle: "Κατάργηση εγγραφής από αυτά τα email;", + ConfirmBody: "Επιβεβαιώστε και δεν θα λαμβάνετε πλέον email από αυτόν τον αποστολέα.", + ConfirmButton: "Κατάργηση εγγραφής", + DoneTitle: "Η εγγραφή σας καταργήθηκε", + DoneBody: "Δεν θα λαμβάνετε πλέον email από αυτόν τον αποστολέα.", + ResubscribeButton: "Καταργήσατε την εγγραφή κατά λάθος; Εγγραφείτε ξανά", + ResubscribedTitle: "Η εγγραφή σας ενεργοποιήθηκε ξανά", + ResubscribedBody: "Ο αποστολέας μπορεί να σας στέλνει email όπως πριν. Μπορείτε να καταργήσετε την εγγραφή σας από οποιοδήποτε επόμενο email.", + FailedTitle: "Δεν ήταν δυνατή η επεξεργασία αυτού του συνδέσμου", + FailedBody: "Ο σύνδεσμος δεν ισχύει πλέον. Εναλλακτικά, απαντήστε στο email και ο αποστολέας θα σταματήσει να σας γράφει.", + FailedResubscribeBody: "Ο σύνδεσμος δεν ισχύει πλέον. Απαντήστε στο email και ο αποστολέας μπορεί να σας προσθέσει ξανά.", + RetryTitle: "Δοκιμάστε ξανά σε λίγο", + RetryBody: "Δεν ήταν δυνατός ο έλεγχος αυτού του συνδέσμου αυτή τη στιγμή. Ανοίξτε τον ξανά σε λίγα λεπτά ή απαντήστε στο email και ο αποστολέας θα σταματήσει να σας γράφει.", + ExpiredTitle: "Αυτός ο σύνδεσμος έχει λήξει", + InvalidTitle: "Αυτός ο σύνδεσμος κατάργησης εγγραφής δεν είναι έγκυρος", + ReplyBody: "Εναλλακτικά, απαντήστε στο email και ο αποστολέας θα σταματήσει να σας γράφει.", + }, + "es": { + TestTitle: "Este era un correo de prueba", + TestBody: "Los envíos de prueba incluyen un enlace que no está vinculado a ninguna persona, así que no hay ninguna suscripción que cancelar.", + ConfirmTitle: "¿Desea darse de baja de estos correos?", + ConfirmBody: "Confirme y no recibirá más correos de este remitente.", + ConfirmButton: "Darme de baja", + DoneTitle: "Se ha dado de baja", + DoneBody: "No recibirá más correos de este remitente.", + ResubscribeButton: "¿Se dio de baja por error? Vuelva a suscribirse", + ResubscribedTitle: "Su suscripción se ha reactivado", + ResubscribedBody: "El remitente podrá escribirle como antes. Puede darse de baja desde cualquier correo posterior.", + FailedTitle: "No hemos podido procesar este enlace", + FailedBody: "El enlace ya no es válido. En su lugar, responda al correo y el remitente dejará de escribirle.", + FailedResubscribeBody: "El enlace ya no es válido. Responda al correo y el remitente podrá volver a incluirle.", + RetryTitle: "Vuelva a intentarlo en unos momentos", + RetryBody: "No hemos podido comprobar este enlace en este momento. Ábralo de nuevo en unos minutos o responda al correo y el remitente dejará de escribirle.", + ExpiredTitle: "Este enlace ha caducado", + InvalidTitle: "Este enlace para darse de baja no es válido", + ReplyBody: "En su lugar, responda al correo y el remitente dejará de escribirle.", + }, + "et": { + TestTitle: "See oli testkiri", + TestBody: "Testkirjades on link, mis ei ole seotud ühegi inimesega, seega pole tellimust, millest loobuda.", + ConfirmTitle: "Kas loobuda nendest kirjadest?", + ConfirmBody: "Kinnitage ja te ei saa sellelt saatjalt enam kirju.", + ConfirmButton: "Loobu tellimusest", + DoneTitle: "Olete tellimusest loobunud", + DoneBody: "Te ei saa sellelt saatjalt enam kirju.", + ResubscribeButton: "Loobusite kogemata? Tellige uuesti", + ResubscribedTitle: "Tellimus on taastatud", + ResubscribedBody: "Saatja võib teile kirjutada nagu varem. Tellimusest saate loobuda iga hilisema kirja kaudu.", + FailedTitle: "Me ei saanud seda linki töödelda", + FailedBody: "Link ei kehti enam. Vastake selle asemel kirjale ja saatja lõpetab teile kirjutamise.", + FailedResubscribeBody: "Link ei kehti enam. Vastake kirjale ja saatja saab teid uuesti lisada.", + RetryTitle: "Proovige veidi aja pärast uuesti", + RetryBody: "Me ei saanud seda linki praegu kontrollida. Avage see mõne minuti pärast uuesti või vastake kirjale ja saatja lõpetab teile kirjutamise.", + ExpiredTitle: "Selle lingi kehtivus on lõppenud", + InvalidTitle: "See tellimusest loobumise link on kehtetu", + ReplyBody: "Vastake selle asemel kirjale ja saatja lõpetab teile kirjutamise.", + }, + "fa": { + RTL: true, + TestTitle: "این یک ایمیل آزمایشی بود", + TestBody: "ایمیلهای آزمایشی پیوندی دارند که به هیچ شخصی مربوط نیست، بنابراین اشتراکی برای لغو وجود ندارد.", + ConfirmTitle: "اشتراک این ایمیلها لغو شود؟", + ConfirmBody: "با تأیید شما، دیگر ایمیلی از این فرستنده دریافت نخواهید کرد.", + ConfirmButton: "لغو اشتراک", + DoneTitle: "اشتراک شما لغو شد", + DoneBody: "دیگر ایمیلی از این فرستنده دریافت نخواهید کرد.", + ResubscribeButton: "اشتراک را اشتباهی لغو کردید؟ اشتراک دوباره", + ResubscribedTitle: "اشتراک شما دوباره فعال شد", + ResubscribedBody: "فرستنده میتواند مانند گذشته برای شما ایمیل بفرستد. میتوانید اشتراک خود را از طریق هر ایمیل بعدی لغو کنید.", + FailedTitle: "نتوانستیم این پیوند را پردازش کنیم", + FailedBody: "این پیوند دیگر معتبر نیست. بهجای آن به ایمیل پاسخ دهید تا فرستنده دیگر برای شما ایمیل نفرستد.", + FailedResubscribeBody: "این پیوند دیگر معتبر نیست. به ایمیل پاسخ دهید تا فرستنده بتواند دوباره شما را اضافه کند.", + RetryTitle: "کمی بعد دوباره امتحان کنید", + RetryBody: "در حال حاضر نتوانستیم این پیوند را بررسی کنیم. چند دقیقه دیگر دوباره آن را باز کنید، یا به ایمیل پاسخ دهید تا فرستنده دیگر برای شما ایمیل نفرستد.", + ExpiredTitle: "این پیوند منقضی شده است", + InvalidTitle: "این پیوند لغو اشتراک نامعتبر است", + ReplyBody: "بهجای آن به ایمیل پاسخ دهید تا فرستنده دیگر برای شما ایمیل نفرستد.", + }, + "fi": { + TestTitle: "Tämä oli testiviesti", + TestBody: "Testiviesteissä on linkki, jota ei ole liitetty kehenkään, joten peruttavaa tilausta ei ole.", + ConfirmTitle: "Perutaanko näiden sähköpostien tilaus?", + ConfirmBody: "Vahvista, niin et saa enää sähköposteja tältä lähettäjältä.", + ConfirmButton: "Peru tilaus", + DoneTitle: "Tilaus on peruttu", + DoneBody: "Et saa enää sähköposteja tältä lähettäjältä.", + ResubscribeButton: "Peruitko tilauksen vahingossa? Tilaa uudelleen", + ResubscribedTitle: "Tilaus on jälleen voimassa", + ResubscribedBody: "Lähettäjä voi lähettää sinulle sähköpostia kuten ennenkin. Voit perua tilauksen mistä tahansa myöhemmästä viestistä.", + FailedTitle: "Linkkiä ei voitu käsitellä", + FailedBody: "Linkki ei ole enää voimassa. Vastaa sen sijaan sähköpostiin, niin lähettäjä lopettaa viestien lähettämisen.", + FailedResubscribeBody: "Linkki ei ole enää voimassa. Vastaa sähköpostiin, niin lähettäjä voi lisätä sinut takaisin.", + RetryTitle: "Yritä hetken kuluttua uudelleen", + RetryBody: "Emme voineet tarkistaa linkkiä juuri nyt. Avaa se uudelleen muutaman minuutin kuluttua tai vastaa sähköpostiin, niin lähettäjä lopettaa viestien lähettämisen.", + ExpiredTitle: "Linkki on vanhentunut", + InvalidTitle: "Tilauksen peruutuslinkki ei ole kelvollinen", + ReplyBody: "Vastaa sen sijaan sähköpostiin, niin lähettäjä lopettaa viestien lähettämisen.", + }, + "fil": { + TestTitle: "Test email lamang ito", + TestBody: "Ang mga test email ay may link na hindi nakatali sa sinuman, kaya walang kailangang i-unsubscribe.", + ConfirmTitle: "Mag-unsubscribe sa mga email na ito?", + ConfirmBody: "Kumpirmahin po ito at hindi na kayo makatatanggap ng anumang email mula sa nagpadalang ito.", + ConfirmButton: "Mag-unsubscribe", + DoneTitle: "Na-unsubscribe na kayo", + DoneBody: "Hindi na kayo makatatanggap ng anumang email mula sa nagpadalang ito.", + ResubscribeButton: "Nagkamali ng pag-unsubscribe? Mag-subscribe muli", + ResubscribedTitle: "Naka-subscribe na kayong muli", + ResubscribedBody: "Maaari na kayong padalhan muli ng email ng nagpadala gaya ng dati. Maaari kayong mag-unsubscribe sa alinmang susunod na email.", + FailedTitle: "Hindi namin maproseso ang link na iyon", + FailedBody: "Hindi na valid ang link. Sa halip, sagutin ang email at titigil na ang nagpadala sa pag-email sa inyo.", + FailedResubscribeBody: "Hindi na valid ang link. Sagutin ang email at maaari kayong idagdag muli ng nagpadala.", + RetryTitle: "Subukang muli maya-maya", + RetryBody: "Hindi namin masuri ang link na ito sa ngayon. Buksan itong muli pagkalipas ng ilang minuto, o sagutin ang email at titigil na ang nagpadala sa pag-email sa inyo.", + ExpiredTitle: "Nag-expire na ang link na ito", + InvalidTitle: "Hindi valid ang unsubscribe link na ito", + ReplyBody: "Sa halip, sagutin ang email at titigil na ang nagpadala sa pag-email sa inyo.", + }, + "fr": { + TestTitle: "Ceci était un e-mail de test", + TestBody: "Les envois de test contiennent un lien qui n'est associé à aucun destinataire : il n'y a donc aucun abonnement à résilier.", + ConfirmTitle: "Se désabonner de ces e-mails ?", + ConfirmBody: "Confirmez et vous ne recevrez plus d'e-mails de cet expéditeur.", + ConfirmButton: "Se désabonner", + DoneTitle: "Votre désabonnement est confirmé", + DoneBody: "Vous ne recevrez plus d'e-mails de cet expéditeur.", + ResubscribeButton: "Désabonnement par erreur ? Se réabonner", + ResubscribedTitle: "Votre abonnement est rétabli", + ResubscribedBody: "L'expéditeur peut vous écrire comme avant. Vous pourrez vous désabonner depuis n'importe quel e-mail ultérieur.", + FailedTitle: "Nous n'avons pas pu traiter ce lien", + FailedBody: "Ce lien n'est plus valide. Répondez plutôt à l'e-mail et l'expéditeur cessera de vous écrire.", + FailedResubscribeBody: "Ce lien n'est plus valide. Répondez à l'e-mail et l'expéditeur pourra vous réinscrire.", + RetryTitle: "Réessayez dans quelques instants", + RetryBody: "Nous n'avons pas pu vérifier ce lien pour le moment. Ouvrez-le à nouveau dans quelques minutes, ou répondez à l'e-mail et l'expéditeur cessera de vous écrire.", + ExpiredTitle: "Ce lien a expiré", + InvalidTitle: "Ce lien de désabonnement n'est pas valide", + ReplyBody: "Répondez plutôt à l'e-mail et l'expéditeur cessera de vous écrire.", + }, + "he": { + RTL: true, + TestTitle: "זו הייתה הודעת בדיקה", + TestBody: "הודעות בדיקה כוללות קישור שאינו משויך לאף אחד, ולכן אין הרשמה לבטל.", + ConfirmTitle: "לבטל את ההרשמה להודעות האלה?", + ConfirmBody: "לאחר האישור לא יישלחו אליך הודעות נוספות מהשולח הזה.", + ConfirmButton: "ביטול ההרשמה", + DoneTitle: "ההרשמה בוטלה", + DoneBody: "לא יישלחו אליך הודעות נוספות מהשולח הזה.", + ResubscribeButton: "ההרשמה בוטלה בטעות? הרשמה מחדש", + ResubscribedTitle: "ההרשמה חודשה", + ResubscribedBody: "השולח יכול לשלוח אליך הודעות כמו קודם. אפשר לבטל את ההרשמה מכל הודעה עתידית.", + FailedTitle: "לא הצלחנו לעבד את הקישור", + FailedBody: "הקישור כבר אינו בתוקף. במקום זאת, אפשר להשיב להודעה והשולח יפסיק לשלוח אליך הודעות.", + FailedResubscribeBody: "הקישור כבר אינו בתוקף. אפשר להשיב להודעה, והשולח יוכל להוסיף אותך מחדש.", + RetryTitle: "נא לנסות שוב בעוד כמה רגעים", + RetryBody: "לא הצלחנו לבדוק את הקישור כרגע. אפשר לפתוח אותו שוב בעוד כמה דקות, או להשיב להודעה והשולח יפסיק לשלוח אליך הודעות.", + ExpiredTitle: "תוקף הקישור פג", + InvalidTitle: "קישור ביטול ההרשמה אינו תקין", + ReplyBody: "במקום זאת, אפשר להשיב להודעה והשולח יפסיק לשלוח אליך הודעות.", + }, + "hi": { + TestTitle: "यह एक परीक्षण ईमेल था", + TestBody: "परीक्षण के लिए भेजे गए ईमेल का लिंक किसी व्यक्ति से जुड़ा नहीं होता, इसलिए सदस्यता समाप्त करने के लिए कुछ भी नहीं है।", + ConfirmTitle: "इन ईमेल की सदस्यता समाप्त करें?", + ConfirmBody: "पुष्टि करें, इसके बाद आपको इस प्रेषक से और ईमेल नहीं मिलेंगे।", + ConfirmButton: "सदस्यता समाप्त करें", + DoneTitle: "आपकी सदस्यता समाप्त कर दी गई है", + DoneBody: "आपको इस प्रेषक से और ईमेल नहीं मिलेंगे।", + ResubscribeButton: "गलती से सदस्यता समाप्त हो गई? फिर से सदस्यता लें", + ResubscribedTitle: "आपकी सदस्यता फिर से शुरू हो गई है", + ResubscribedBody: "प्रेषक आपको पहले की तरह ईमेल भेज सकता है। आप बाद के किसी भी ईमेल से सदस्यता समाप्त कर सकते हैं।", + FailedTitle: "हम इस लिंक को संसाधित नहीं कर सके", + FailedBody: "यह लिंक अब मान्य नहीं है। इसके बजाय ईमेल का जवाब दें, तो प्रेषक आपको ईमेल भेजना बंद कर देगा।", + FailedResubscribeBody: "यह लिंक अब मान्य नहीं है। ईमेल का जवाब दें, तो प्रेषक आपको फिर से जोड़ सकता है।", + RetryTitle: "थोड़ी देर में फिर से प्रयास करें", + RetryBody: "हम अभी इस लिंक की जाँच नहीं कर सके। कुछ मिनट बाद इसे फिर से खोलें, या ईमेल का जवाब दें, तो प्रेषक आपको ईमेल भेजना बंद कर देगा।", + ExpiredTitle: "इस लिंक की समय-सीमा समाप्त हो गई है", + InvalidTitle: "सदस्यता समाप्त करने का यह लिंक अमान्य है", + ReplyBody: "इसके बजाय ईमेल का जवाब दें, तो प्रेषक आपको ईमेल भेजना बंद कर देगा।", + }, + "hr": { + TestTitle: "Ovo je bila probna e-poruka", + TestBody: "Probna slanja sadrže poveznicu koja nije vezana ni uz koga, pa se nemate od čega odjaviti.", + ConfirmTitle: "Odjaviti se od ovih e-poruka?", + ConfirmBody: "Potvrdite i više nećete primati e-poruke od ovog pošiljatelja.", + ConfirmButton: "Odjavi se", + DoneTitle: "Odjavljeni ste", + DoneBody: "Više nećete primati e-poruke od ovog pošiljatelja.", + ResubscribeButton: "Odjavili ste se greškom? Ponovno se prijavite", + ResubscribedTitle: "Ponovno ste prijavljeni", + ResubscribedBody: "Pošiljatelj vam može slati e-poruke kao i prije. Možete se odjaviti putem bilo koje sljedeće e-poruke.", + FailedTitle: "Nismo mogli obraditi tu poveznicu", + FailedBody: "Poveznica više nije valjana. Umjesto toga odgovorite na e-poruku i pošiljatelj vam više neće pisati.", + FailedResubscribeBody: "Poveznica više nije valjana. Odgovorite na e-poruku i pošiljatelj vas može ponovno dodati.", + RetryTitle: "Pokušajte ponovno za koji trenutak", + RetryBody: "Trenutačno nismo mogli provjeriti ovu poveznicu. Otvorite je ponovno za nekoliko minuta ili odgovorite na e-poruku i pošiljatelj vam više neće pisati.", + ExpiredTitle: "Ova je poveznica istekla", + InvalidTitle: "Ova poveznica za odjavu nije valjana", + ReplyBody: "Umjesto toga odgovorite na e-poruku i pošiljatelj vam više neće pisati.", + }, + "hu": { + TestTitle: "Ez egy teszt e-mail volt", + TestBody: "A tesztküldésekben lévő hivatkozás nem tartozik senkihez, így nincs miről leiratkozni.", + ConfirmTitle: "Leiratkozik ezekről az e-mailekről?", + ConfirmBody: "Erősítse meg, és ettől a feladótól nem kap több e-mailt.", + ConfirmButton: "Leiratkozás", + DoneTitle: "Sikeresen leiratkozott", + DoneBody: "Ettől a feladótól nem kap több e-mailt.", + ResubscribeButton: "Tévedésből iratkozott le? Újbóli feliratkozás", + ResubscribedTitle: "Újra feliratkozott", + ResubscribedBody: "A feladó ugyanúgy írhat Önnek, mint korábban. Bármelyik későbbi e-mailből leiratkozhat.", + FailedTitle: "Nem sikerült feldolgozni a hivatkozást", + FailedBody: "A hivatkozás már nem érvényes. Válaszoljon inkább az e-mailre, és a feladó nem ír Önnek többet.", + FailedResubscribeBody: "A hivatkozás már nem érvényes. Válaszoljon az e-mailre, és a feladó újra felveheti Önt.", + RetryTitle: "Próbálja újra kicsit később", + RetryBody: "Ezt a hivatkozást most nem tudtuk ellenőrizni. Nyissa meg újra néhány perc múlva, vagy válaszoljon az e-mailre, és a feladó nem ír Önnek többet.", + ExpiredTitle: "Ez a hivatkozás lejárt", + InvalidTitle: "Ez a leiratkozási hivatkozás érvénytelen", + ReplyBody: "Válaszoljon inkább az e-mailre, és a feladó nem ír Önnek többet.", + }, + "id": { + TestTitle: "Ini adalah email uji coba", + TestBody: "Email uji coba berisi tautan yang tidak terkait dengan siapa pun, jadi tidak ada langganan yang perlu dihentikan.", + ConfirmTitle: "Berhenti berlangganan email ini?", + ConfirmBody: "Konfirmasikan, dan Anda tidak akan menerima email lagi dari pengirim ini.", + ConfirmButton: "Berhenti berlangganan", + DoneTitle: "Anda telah berhenti berlangganan", + DoneBody: "Anda tidak akan menerima email lagi dari pengirim ini.", + ResubscribeButton: "Tidak sengaja berhenti berlangganan? Berlangganan lagi", + ResubscribedTitle: "Anda berlangganan kembali", + ResubscribedBody: "Pengirim dapat mengirimi Anda email seperti sebelumnya. Anda dapat berhenti berlangganan melalui email mana pun berikutnya.", + FailedTitle: "Kami tidak dapat memproses tautan tersebut", + FailedBody: "Tautan ini sudah tidak berlaku. Sebagai gantinya, balas email tersebut dan pengirim akan berhenti mengirimi Anda email.", + FailedResubscribeBody: "Tautan ini sudah tidak berlaku. Balas email tersebut dan pengirim dapat menambahkan Anda kembali.", + RetryTitle: "Coba lagi sebentar lagi", + RetryBody: "Kami tidak dapat memeriksa tautan ini saat ini. Buka kembali dalam beberapa menit, atau balas email tersebut dan pengirim akan berhenti mengirimi Anda email.", + ExpiredTitle: "Tautan ini sudah kedaluwarsa", + InvalidTitle: "Tautan berhenti berlangganan ini tidak valid", + ReplyBody: "Sebagai gantinya, balas email tersebut dan pengirim akan berhenti mengirimi Anda email.", + }, + "it": { + TestTitle: "Questa era un'email di prova", + TestBody: "Gli invii di prova contengono un link non associato a nessuno, quindi non c'è alcuna iscrizione da annullare.", + ConfirmTitle: "Annullare l'iscrizione a queste email?", + ConfirmBody: "Confermi e non riceverà più email da questo mittente.", + ConfirmButton: "Annulla iscrizione", + DoneTitle: "L'iscrizione è stata annullata", + DoneBody: "Non riceverà più email da questo mittente.", + ResubscribeButton: "Annullata per errore? Riattiva l'iscrizione", + ResubscribedTitle: "L'iscrizione è di nuovo attiva", + ResubscribedBody: "Il mittente potrà scriverle come prima. Può annullare l'iscrizione da qualsiasi email successiva.", + FailedTitle: "Non è stato possibile elaborare il link", + FailedBody: "Il link non è più valido. Risponda invece all'email e il mittente smetterà di scriverle.", + FailedResubscribeBody: "Il link non è più valido. Risponda all'email e il mittente potrà aggiungerla di nuovo.", + RetryTitle: "Riprovi tra poco", + RetryBody: "Al momento non è stato possibile verificare questo link. Lo riapra tra qualche minuto, oppure risponda all'email e il mittente smetterà di scriverle.", + ExpiredTitle: "Questo link è scaduto", + InvalidTitle: "Questo link di annullamento dell'iscrizione non è valido", + ReplyBody: "Risponda invece all'email e il mittente smetterà di scriverle.", + }, + "ja": { + TestTitle: "これはテストメールです", + TestBody: "テスト送信のリンクは特定の宛先に紐づいていないため、配信停止の対象はありません。", + ConfirmTitle: "これらのメールの配信を停止しますか?", + ConfirmBody: "確定すると、この送信者からのメールは今後届かなくなります。", + ConfirmButton: "配信を停止する", + DoneTitle: "配信を停止しました", + DoneBody: "この送信者からのメールは今後届きません。", + ResubscribeButton: "誤って停止しましたか?配信を再開する", + ResubscribedTitle: "配信を再開しました", + ResubscribedBody: "送信者はこれまでどおりメールを送信できます。今後届くメールからいつでも配信を停止できます。", + FailedTitle: "このリンクを処理できませんでした", + FailedBody: "このリンクは無効になっています。代わりにメールへ返信していただければ、送信者は送信を停止します。", + FailedResubscribeBody: "このリンクは無効になっています。メールへ返信していただければ、送信者が配信を再開できます。", + RetryTitle: "しばらくしてからもう一度お試しください", + RetryBody: "現在このリンクを確認できませんでした。数分後にもう一度開くか、メールへ返信していただければ、送信者は送信を停止します。", + ExpiredTitle: "このリンクは有効期限が切れています", + InvalidTitle: "この配信停止リンクは無効です", + ReplyBody: "代わりにメールへ返信していただければ、送信者は送信を停止します。", + }, + "ko": { + TestTitle: "테스트 이메일이었습니다", + TestBody: "테스트 발송에 포함된 링크는 특정 수신자와 연결되어 있지 않으므로 수신 거부할 대상이 없습니다.", + ConfirmTitle: "이 이메일 수신을 거부하시겠습니까?", + ConfirmBody: "확인하시면 이 발신자로부터 더 이상 이메일을 받지 않게 됩니다.", + ConfirmButton: "수신 거부", + DoneTitle: "수신 거부되었습니다", + DoneBody: "이 발신자로부터 더 이상 이메일을 받지 않게 됩니다.", + ResubscribeButton: "실수로 수신 거부하셨나요? 다시 구독하기", + ResubscribedTitle: "다시 구독되었습니다", + ResubscribedBody: "발신자가 이전처럼 이메일을 보낼 수 있습니다. 이후 받는 이메일에서 언제든지 수신을 거부하실 수 있습니다.", + FailedTitle: "링크를 처리할 수 없습니다", + FailedBody: "이 링크는 더 이상 유효하지 않습니다. 대신 이메일에 회신하시면 발신자가 발송을 중단합니다.", + FailedResubscribeBody: "이 링크는 더 이상 유효하지 않습니다. 이메일에 회신하시면 발신자가 다시 추가해 드릴 수 있습니다.", + RetryTitle: "잠시 후 다시 시도해 주세요", + RetryBody: "지금은 이 링크를 확인할 수 없습니다. 몇 분 후 다시 열어 보시거나, 이메일에 회신하시면 발신자가 발송을 중단합니다.", + ExpiredTitle: "만료된 링크입니다", + InvalidTitle: "유효하지 않은 수신 거부 링크입니다", + ReplyBody: "대신 이메일에 회신하시면 발신자가 발송을 중단합니다.", + }, + "lt": { + TestTitle: "Tai buvo bandomasis el. laiškas", + TestBody: "Bandomuosiuose laiškuose esanti nuoroda nesusieta su jokiu gavėju, todėl nėra ko atsisakyti.", + ConfirmTitle: "Atsisakyti šių el. laiškų?", + ConfirmBody: "Patvirtinkite ir daugiau nebegausite el. laiškų iš šio siuntėjo.", + ConfirmButton: "Atsisakyti prenumeratos", + DoneTitle: "Prenumeratos atsisakyta", + DoneBody: "Daugiau nebegausite el. laiškų iš šio siuntėjo.", + ResubscribeButton: "Atsisakėte per klaidą? Užsiprenumeruoti iš naujo", + ResubscribedTitle: "Prenumerata atnaujinta", + ResubscribedBody: "Siuntėjas vėl galės jums rašyti kaip anksčiau. Prenumeratos galite atsisakyti per bet kurį vėlesnį el. laišką.", + FailedTitle: "Nepavyko apdoroti šios nuorodos", + FailedBody: "Ši nuoroda nebegalioja. Vietoj to atsakykite į el. laišką ir siuntėjas jums nebesiųs laiškų.", + FailedResubscribeBody: "Ši nuoroda nebegalioja. Atsakykite į el. laišką ir siuntėjas galės jus vėl įtraukti.", + RetryTitle: "Netrukus bandykite dar kartą", + RetryBody: "Šiuo metu nepavyko patikrinti šios nuorodos. Atidarykite ją dar kartą po kelių minučių arba atsakykite į el. laišką ir siuntėjas jums nebesiųs laiškų.", + ExpiredTitle: "Šios nuorodos galiojimas baigėsi", + InvalidTitle: "Ši prenumeratos atsisakymo nuoroda negalioja", + ReplyBody: "Vietoj to atsakykite į el. laišką ir siuntėjas jums nebesiųs laiškų.", + }, + "lv": { + TestTitle: "Šis bija testa e-pasts", + TestBody: "Testa sūtījumos ir saite, kas nav piesaistīta nevienam adresātam, tāpēc nav no kā atteikties.", + ConfirmTitle: "Atteikties no šiem e-pastiem?", + ConfirmBody: "Apstipriniet, un jūs vairs nesaņemsiet e-pastus no šī sūtītāja.", + ConfirmButton: "Atteikties", + DoneTitle: "Jūsu abonements ir atcelts", + DoneBody: "Jūs vairs nesaņemsiet e-pastus no šī sūtītāja.", + ResubscribeButton: "Atteicāties kļūdas pēc? Abonēt atkārtoti", + ResubscribedTitle: "Jūsu abonements ir atjaunots", + ResubscribedBody: "Sūtītājs var jums rakstīt tāpat kā iepriekš. No abonementa varat atteikties jebkurā nākamajā e-pastā.", + FailedTitle: "Šo saiti neizdevās apstrādāt", + FailedBody: "Saite vairs nav derīga. Tā vietā atbildiet uz e-pastu, un sūtītājs jums vairs nerakstīs.", + FailedResubscribeBody: "Saite vairs nav derīga. Atbildiet uz e-pastu, un sūtītājs varēs jūs pievienot atkārtoti.", + RetryTitle: "Mēģiniet vēlreiz nedaudz vēlāk", + RetryBody: "Pašlaik šo saiti neizdevās pārbaudīt. Atveriet to vēlreiz pēc dažām minūtēm vai atbildiet uz e-pastu, un sūtītājs jums vairs nerakstīs.", + ExpiredTitle: "Šīs saites derīguma termiņš ir beidzies", + InvalidTitle: "Šī atteikšanās saite nav derīga", + ReplyBody: "Tā vietā atbildiet uz e-pastu, un sūtītājs jums vairs nerakstīs.", + }, + "ms": { + TestTitle: "Ini ialah e-mel ujian", + TestBody: "E-mel ujian mengandungi pautan yang tidak dikaitkan dengan sesiapa, jadi tiada langganan untuk dihentikan.", + ConfirmTitle: "Berhenti melanggan e-mel ini?", + ConfirmBody: "Sahkan dan anda tidak akan menerima e-mel lagi daripada pengirim ini.", + ConfirmButton: "Berhenti melanggan", + DoneTitle: "Anda telah berhenti melanggan", + DoneBody: "Anda tidak akan menerima e-mel lagi daripada pengirim ini.", + ResubscribeButton: "Berhenti melanggan secara tidak sengaja? Langgan semula", + ResubscribedTitle: "Anda telah melanggan semula", + ResubscribedBody: "Pengirim boleh menghantar e-mel kepada anda seperti sebelum ini. Anda boleh berhenti melanggan melalui mana-mana e-mel seterusnya.", + FailedTitle: "Kami tidak dapat memproses pautan itu", + FailedBody: "Pautan ini tidak lagi sah. Sebaliknya, balas e-mel tersebut dan pengirim akan berhenti menghantar e-mel kepada anda.", + FailedResubscribeBody: "Pautan ini tidak lagi sah. Balas e-mel tersebut dan pengirim boleh menambah anda semula.", + RetryTitle: "Cuba lagi sebentar nanti", + RetryBody: "Kami tidak dapat menyemak pautan ini sekarang. Buka semula dalam beberapa minit, atau balas e-mel tersebut dan pengirim akan berhenti menghantar e-mel kepada anda.", + ExpiredTitle: "Pautan ini telah tamat tempoh", + InvalidTitle: "Pautan berhenti melanggan ini tidak sah", + ReplyBody: "Sebaliknya, balas e-mel tersebut dan pengirim akan berhenti menghantar e-mel kepada anda.", + }, + "nb": { + TestTitle: "Dette var en test-e-post", + TestBody: "Testutsendelser har en lenke som ikke er knyttet til noen, så det er ingenting å melde seg av.", + ConfirmTitle: "Vil du melde deg av disse e-postene?", + ConfirmBody: "Bekreft, så får du ikke flere e-poster fra denne avsenderen.", + ConfirmButton: "Meld av", + DoneTitle: "Du er meldt av", + DoneBody: "Du får ikke flere e-poster fra denne avsenderen.", + ResubscribeButton: "Meldt av ved en feil? Meld deg på igjen", + ResubscribedTitle: "Du er påmeldt igjen", + ResubscribedBody: "Avsenderen kan sende deg e-post som før. Du kan melde deg av fra hvilken som helst senere e-post.", + FailedTitle: "Vi kunne ikke behandle lenken", + FailedBody: "Lenken er ikke lenger gyldig. Svar på e-posten i stedet, så slutter avsenderen å sende deg e-post.", + FailedResubscribeBody: "Lenken er ikke lenger gyldig. Svar på e-posten, så kan avsenderen legge deg til igjen.", + RetryTitle: "Prøv igjen om litt", + RetryBody: "Vi kunne ikke sjekke denne lenken akkurat nå. Åpne den igjen om noen minutter, eller svar på e-posten, så slutter avsenderen å sende deg e-post.", + ExpiredTitle: "Denne lenken har utløpt", + InvalidTitle: "Denne avmeldingslenken er ugyldig", + ReplyBody: "Svar på e-posten i stedet, så slutter avsenderen å sende deg e-post.", + }, + "nl": { + TestTitle: "Dit was een testmail", + TestBody: "Testverzendingen bevatten een link die aan niemand gekoppeld is, dus er is niets om u voor af te melden.", + ConfirmTitle: "Afmelden voor deze e-mails?", + ConfirmBody: "Bevestig dit en u ontvangt geen e-mails meer van deze afzender.", + ConfirmButton: "Afmelden", + DoneTitle: "U bent afgemeld", + DoneBody: "U ontvangt geen e-mails meer van deze afzender.", + ResubscribeButton: "Per ongeluk afgemeld? Opnieuw aanmelden", + ResubscribedTitle: "U bent weer aangemeld", + ResubscribedBody: "De afzender kan u weer e-mailen zoals voorheen. U kunt zich via elke volgende e-mail afmelden.", + FailedTitle: "We konden deze link niet verwerken", + FailedBody: "De link is niet meer geldig. Beantwoord in plaats daarvan de e-mail, dan mailt de afzender u niet meer.", + FailedResubscribeBody: "De link is niet meer geldig. Beantwoord de e-mail, dan kan de afzender u weer toevoegen.", + RetryTitle: "Probeer het zo opnieuw", + RetryBody: "We konden deze link nu niet controleren. Open hem over een paar minuten opnieuw, of beantwoord de e-mail, dan mailt de afzender u niet meer.", + ExpiredTitle: "Deze link is verlopen", + InvalidTitle: "Deze afmeldlink is ongeldig", + ReplyBody: "Beantwoord in plaats daarvan de e-mail, dan mailt de afzender u niet meer.", + }, + "pl": { + TestTitle: "To była wiadomość testowa", + TestBody: "Wiadomości testowe zawierają link, który nie jest przypisany do żadnego odbiorcy, więc nie ma z czego się wypisać.", + ConfirmTitle: "Wypisać się z tych wiadomości?", + ConfirmBody: "Po potwierdzeniu wiadomości od tego nadawcy przestaną przychodzić.", + ConfirmButton: "Wypisz się", + DoneTitle: "Wypisanie potwierdzone", + DoneBody: "Wiadomości od tego nadawcy nie będą już przychodzić.", + ResubscribeButton: "Wypisanie przez pomyłkę? Zapisz się ponownie", + ResubscribedTitle: "Zapis został przywrócony", + ResubscribedBody: "Nadawca może znów wysyłać wiadomości tak jak wcześniej. Z każdej kolejnej wiadomości można się wypisać.", + FailedTitle: "Nie udało się przetworzyć tego linku", + FailedBody: "Ten link jest już nieważny. Zamiast tego wystarczy odpowiedzieć na wiadomość, a nadawca przestanie pisać.", + FailedResubscribeBody: "Ten link jest już nieważny. Wystarczy odpowiedzieć na wiadomość, a nadawca będzie mógł ponownie dodać ten adres.", + RetryTitle: "Prosimy spróbować ponownie za chwilę", + RetryBody: "Nie udało się teraz sprawdzić tego linku. Prosimy otworzyć go ponownie za kilka minut lub odpowiedzieć na wiadomość, a nadawca przestanie pisać.", + ExpiredTitle: "Ten link wygasł", + InvalidTitle: "Ten link do wypisania jest nieprawidłowy", + ReplyBody: "Zamiast tego wystarczy odpowiedzieć na wiadomość, a nadawca przestanie pisać.", + }, + "pt": { + TestTitle: "Este foi um e-mail de teste", + TestBody: "Os envios de teste trazem um link que não está vinculado a ninguém, portanto não há inscrição para cancelar.", + ConfirmTitle: "Cancelar a inscrição nestes e-mails?", + ConfirmBody: "Confirme e não receberá mais e-mails deste remetente.", + ConfirmButton: "Cancelar inscrição", + DoneTitle: "Inscrição cancelada", + DoneBody: "Não receberá mais e-mails deste remetente.", + ResubscribeButton: "Cancelou por engano? Inscrever-se novamente", + ResubscribedTitle: "Inscrição reativada", + ResubscribedBody: "O remetente pode enviar e-mails como antes. É possível cancelar a inscrição em qualquer e-mail futuro.", + FailedTitle: "Não foi possível processar este link", + FailedBody: "Este link não é mais válido. Em vez disso, responda ao e-mail e o remetente deixará de enviar mensagens.", + FailedResubscribeBody: "Este link não é mais válido. Responda ao e-mail e o remetente poderá reativar a inscrição.", + RetryTitle: "Tente novamente em instantes", + RetryBody: "Não foi possível verificar este link agora. Abra-o novamente em alguns minutos ou responda ao e-mail e o remetente deixará de enviar mensagens.", + ExpiredTitle: "Este link expirou", + InvalidTitle: "Este link de cancelamento é inválido", + ReplyBody: "Em vez disso, responda ao e-mail e o remetente deixará de enviar mensagens.", + }, + "ro": { + TestTitle: "Acesta a fost un e-mail de test", + TestBody: "Trimiterile de test conțin un link care nu este asociat cu nicio persoană, așa că nu aveți de la ce să vă dezabonați.", + ConfirmTitle: "Vă dezabonați de la aceste e-mailuri?", + ConfirmBody: "Confirmați și nu veți mai primi e-mailuri de la acest expeditor.", + ConfirmButton: "Dezabonare", + DoneTitle: "V-ați dezabonat", + DoneBody: "Nu veți mai primi e-mailuri de la acest expeditor.", + ResubscribeButton: "V-ați dezabonat din greșeală? Reabonare", + ResubscribedTitle: "V-ați abonat din nou", + ResubscribedBody: "Expeditorul vă poate trimite e-mailuri ca înainte. Vă puteți dezabona din orice e-mail ulterior.", + FailedTitle: "Nu am putut procesa acest link", + FailedBody: "Linkul nu mai este valabil. Răspundeți în schimb la e-mail, iar expeditorul nu vă va mai scrie.", + FailedResubscribeBody: "Linkul nu mai este valabil. Răspundeți la e-mail, iar expeditorul vă poate adăuga din nou.", + RetryTitle: "Încercați din nou în curând", + RetryBody: "Nu am putut verifica acest link acum. Deschideți-l din nou peste câteva minute sau răspundeți la e-mail, iar expeditorul nu vă va mai scrie.", + ExpiredTitle: "Acest link a expirat", + InvalidTitle: "Acest link de dezabonare nu este valid", + ReplyBody: "Răspundeți în schimb la e-mail, iar expeditorul nu vă va mai scrie.", + }, + "ru": { + TestTitle: "Это было тестовое письмо", + TestBody: "Тестовые письма содержат ссылку, не привязанную ни к одному получателю, поэтому отписываться не от чего.", + ConfirmTitle: "Отписаться от этих писем?", + ConfirmBody: "Подтвердите, и вы больше не будете получать письма от этого отправителя.", + ConfirmButton: "Отписаться", + DoneTitle: "Вы отписались", + DoneBody: "Вы больше не будете получать письма от этого отправителя.", + ResubscribeButton: "Отписались по ошибке? Подписаться снова", + ResubscribedTitle: "Вы снова подписаны", + ResubscribedBody: "Отправитель может писать вам, как и раньше. Вы можете отписаться в любом из следующих писем.", + FailedTitle: "Не удалось обработать ссылку", + FailedBody: "Ссылка больше не действительна. Вместо этого ответьте на письмо, и отправитель перестанет вам писать.", + FailedResubscribeBody: "Ссылка больше не действительна. Ответьте на письмо, и отправитель сможет снова добавить вас.", + RetryTitle: "Попробуйте чуть позже", + RetryBody: "Сейчас не удалось проверить эту ссылку. Откройте её снова через несколько минут или ответьте на письмо, и отправитель перестанет вам писать.", + ExpiredTitle: "Срок действия ссылки истёк", + InvalidTitle: "Эта ссылка для отписки недействительна", + ReplyBody: "Вместо этого ответьте на письмо, и отправитель перестанет вам писать.", + }, + "sk": { + TestTitle: "Toto bol testovací e-mail", + TestBody: "Testovacie e-maily obsahujú odkaz, ktorý nie je priradený žiadnemu príjemcovi, preto sa nie je z čoho odhlásiť.", + ConfirmTitle: "Odhlásiť sa z odberu týchto e-mailov?", + ConfirmBody: "Po potvrdení už od tohto odosielateľa nebudete dostávať ďalšie e-maily.", + ConfirmButton: "Odhlásiť sa", + DoneTitle: "Odber bol zrušený", + DoneBody: "Od tohto odosielateľa už nebudete dostávať ďalšie e-maily.", + ResubscribeButton: "Odhlásili ste sa omylom? Prihlásiť sa znova", + ResubscribedTitle: "Odber je znova aktívny", + ResubscribedBody: "Odosielateľ vám môže písať ako predtým. Z odberu sa môžete odhlásiť v ktoromkoľvek ďalšom e-maile.", + FailedTitle: "Tento odkaz sa nepodarilo spracovať", + FailedBody: "Odkaz už nie je platný. Namiesto toho odpovedzte na e-mail a odosielateľ vám prestane písať.", + FailedResubscribeBody: "Odkaz už nie je platný. Odpovedzte na e-mail a odosielateľ vás môže znova pridať.", + RetryTitle: "Skúste to o chvíľu znova", + RetryBody: "Tento odkaz sa teraz nepodarilo overiť. Otvorte ho znova o niekoľko minút alebo odpovedzte na e-mail a odosielateľ vám prestane písať.", + ExpiredTitle: "Platnosť tohto odkazu vypršala", + InvalidTitle: "Tento odkaz na odhlásenie je neplatný", + ReplyBody: "Namiesto toho odpovedzte na e-mail a odosielateľ vám prestane písať.", + }, + "sl": { + TestTitle: "To je bilo testno e-sporočilo", + TestBody: "Testna sporočila vsebujejo povezavo, ki ni vezana na nobenega prejemnika, zato se nimate od česa odjaviti.", + ConfirmTitle: "Se želite odjaviti od teh e-sporočil?", + ConfirmBody: "Potrdite in od tega pošiljatelja ne boste več prejemali e-sporočil.", + ConfirmButton: "Odjavi me", + DoneTitle: "Odjava je bila uspešna", + DoneBody: "Od tega pošiljatelja ne boste več prejemali e-sporočil.", + ResubscribeButton: "Ste se odjavili pomotoma? Ponovno se prijavite", + ResubscribedTitle: "Ponovno ste prijavljeni", + ResubscribedBody: "Pošiljatelj vam lahko piše kot prej. Odjavite se lahko iz katerega koli poznejšega e-sporočila.", + FailedTitle: "Povezave ni bilo mogoče obdelati", + FailedBody: "Povezava ni več veljavna. Namesto tega odgovorite na e-sporočilo in pošiljatelj vam ne bo več pisal.", + FailedResubscribeBody: "Povezava ni več veljavna. Odgovorite na e-sporočilo in pošiljatelj vas lahko znova doda.", + RetryTitle: "Poskusite znova čez nekaj trenutkov", + RetryBody: "Te povezave trenutno ni bilo mogoče preveriti. Znova jo odprite čez nekaj minut ali odgovorite na e-sporočilo in pošiljatelj vam ne bo več pisal.", + ExpiredTitle: "Ta povezava je potekla", + InvalidTitle: "Ta povezava za odjavo ni veljavna", + ReplyBody: "Namesto tega odgovorite na e-sporočilo in pošiljatelj vam ne bo več pisal.", + }, + "sr": { + TestTitle: "Ово је била пробна порука", + TestBody: "Пробне поруке садрже линк који није везан ни за једног примаоца, па нема од чега да се одјавите.", + ConfirmTitle: "Желите ли да се одјавите од ових порука?", + ConfirmBody: "Потврдите и више нећете примати поруке од овог пошиљаоца.", + ConfirmButton: "Одјави ме", + DoneTitle: "Одјављени сте", + DoneBody: "Више нећете примати поруке од овог пошиљаоца.", + ResubscribeButton: "Одјавили сте се грешком? Поново се пријавите", + ResubscribedTitle: "Поново сте пријављени", + ResubscribedBody: "Пошиљалац вам може писати као и раније. Можете се одјавити из било које наредне поруке.", + FailedTitle: "Нисмо могли да обрадимо овај линк", + FailedBody: "Линк више није важећи. Уместо тога одговорите на поруку и пошиљалац ће престати да вам пише.", + FailedResubscribeBody: "Линк више није важећи. Одговорите на поруку и пошиљалац вас може поново додати.", + RetryTitle: "Покушајте поново ускоро", + RetryBody: "Тренутно нисмо могли да проверимо овај линк. Отворите га поново за неколико минута или одговорите на поруку и пошиљалац ће престати да вам пише.", + ExpiredTitle: "Овај линк је истекао", + InvalidTitle: "Овај линк за одјаву није важећи", + ReplyBody: "Уместо тога одговорите на поруку и пошиљалац ће престати да вам пише.", + }, + "sv": { + TestTitle: "Det här var ett testmejl", + TestBody: "Testutskick innehåller en länk som inte är kopplad till någon mottagare, så det finns ingen prenumeration att avsluta.", + ConfirmTitle: "Avsluta prenumerationen på de här mejlen?", + ConfirmBody: "Bekräfta så får du inga fler mejl från den här avsändaren.", + ConfirmButton: "Avsluta prenumeration", + DoneTitle: "Din prenumeration är avslutad", + DoneBody: "Du får inga fler mejl från den här avsändaren.", + ResubscribeButton: "Avslutade du av misstag? Prenumerera igen", + ResubscribedTitle: "Du prenumererar igen", + ResubscribedBody: "Avsändaren kan mejla dig som tidigare. Du kan avsluta prenumerationen från vilket senare mejl som helst.", + FailedTitle: "Vi kunde inte hantera länken", + FailedBody: "Länken är inte längre giltig. Svara på mejlet i stället så slutar avsändaren att skriva till dig.", + FailedResubscribeBody: "Länken är inte längre giltig. Svara på mejlet så kan avsändaren lägga till dig igen.", + RetryTitle: "Försök igen om en stund", + RetryBody: "Vi kunde inte kontrollera länken just nu. Öppna den igen om några minuter, eller svara på mejlet så slutar avsändaren att skriva till dig.", + ExpiredTitle: "Länken har gått ut", + InvalidTitle: "Länken för att avsluta prenumerationen är ogiltig", + ReplyBody: "Svara på mejlet i stället så slutar avsändaren att skriva till dig.", + }, + "sw": { + TestTitle: "Hii ilikuwa barua pepe ya majaribio", + TestBody: "Barua pepe za majaribio zina kiungo kisichohusishwa na mtu yeyote, kwa hiyo hakuna cha kujiondoa.", + ConfirmTitle: "Ungependa kujiondoa kwenye barua pepe hizi?", + ConfirmBody: "Thibitisha na hutapokea tena barua pepe kutoka kwa mtumaji huyu.", + ConfirmButton: "Jiondoe", + DoneTitle: "Umejiondoa", + DoneBody: "Hutapokea tena barua pepe kutoka kwa mtumaji huyu.", + ResubscribeButton: "Ulijiondoa kimakosa? Jisajili tena", + ResubscribedTitle: "Umejisajili tena", + ResubscribedBody: "Mtumaji anaweza kukutumia barua pepe kama awali. Unaweza kujiondoa kupitia barua pepe yoyote itakayofuata.", + FailedTitle: "Hatukuweza kushughulikia kiungo hicho", + FailedBody: "Kiungo hiki si halali tena. Badala yake, jibu barua pepe hiyo na mtumaji ataacha kukutumia barua.", + FailedResubscribeBody: "Kiungo hiki si halali tena. Jibu barua pepe hiyo na mtumaji anaweza kukuongeza tena.", + RetryTitle: "Jaribu tena baada ya muda mfupi", + RetryBody: "Hatukuweza kukagua kiungo hiki kwa sasa. Kifungue tena baada ya dakika chache, au jibu barua pepe hiyo na mtumaji ataacha kukutumia barua.", + ExpiredTitle: "Muda wa kiungo hiki umeisha", + InvalidTitle: "Kiungo hiki cha kujiondoa si halali", + ReplyBody: "Badala yake, jibu barua pepe hiyo na mtumaji ataacha kukutumia barua.", + }, + "ta": { + TestTitle: "இது ஒரு சோதனை மின்னஞ்சல்", + TestBody: "சோதனை மின்னஞ்சல்களில் உள்ள இணைப்பு யாருடனும் இணைக்கப்படவில்லை, எனவே குழுவிலக எதுவும் இல்லை.", + ConfirmTitle: "இந்த மின்னஞ்சல்களிலிருந்து குழுவிலக வேண்டுமா?", + ConfirmBody: "உறுதிசெய்தால், இந்த அனுப்புநரிடமிருந்து இனி உங்களுக்கு மின்னஞ்சல்கள் வராது.", + ConfirmButton: "குழுவிலகு", + DoneTitle: "நீங்கள் குழுவிலகிவிட்டீர்கள்", + DoneBody: "இந்த அனுப்புநரிடமிருந்து இனி உங்களுக்கு மின்னஞ்சல்கள் வராது.", + ResubscribeButton: "தவறுதலாகக் குழுவிலகினீர்களா? மீண்டும் குழுசேருங்கள்", + ResubscribedTitle: "நீங்கள் மீண்டும் குழுசேர்ந்துள்ளீர்கள்", + ResubscribedBody: "அனுப்புநர் முன்பு போலவே உங்களுக்கு மின்னஞ்சல் அனுப்பலாம். பின்னர் வரும் எந்த மின்னஞ்சலிலிருந்தும் நீங்கள் குழுவிலகலாம்.", + FailedTitle: "அந்த இணைப்பைச் செயலாக்க முடியவில்லை", + FailedBody: "இந்த இணைப்பு இனி செல்லுபடியாகாது. அதற்குப் பதிலாக மின்னஞ்சலுக்குப் பதிலளியுங்கள், அனுப்புநர் உங்களுக்கு அனுப்புவதை நிறுத்துவார்.", + FailedResubscribeBody: "இந்த இணைப்பு இனி செல்லுபடியாகாது. மின்னஞ்சலுக்குப் பதிலளியுங்கள், அனுப்புநர் உங்களை மீண்டும் சேர்க்கலாம்.", + RetryTitle: "சிறிது நேரத்தில் மீண்டும் முயலுங்கள்", + RetryBody: "இப்போது இந்த இணைப்பைச் சரிபார்க்க முடியவில்லை. சில நிமிடங்களில் மீண்டும் திறக்கவும், அல்லது மின்னஞ்சலுக்குப் பதிலளியுங்கள், அனுப்புநர் உங்களுக்கு அனுப்புவதை நிறுத்துவார்.", + ExpiredTitle: "இந்த இணைப்பு காலாவதியாகிவிட்டது", + InvalidTitle: "இந்தக் குழுவிலகல் இணைப்பு செல்லாது", + ReplyBody: "அதற்குப் பதிலாக மின்னஞ்சலுக்குப் பதிலளியுங்கள், அனுப்புநர் உங்களுக்கு அனுப்புவதை நிறுத்துவார்.", + }, + "th": { + TestTitle: "นี่เป็นอีเมลทดสอบ", + TestBody: "อีเมลทดสอบมีลิงก์ที่ไม่ได้ผูกกับผู้รับรายใด จึงไม่มีการรับอีเมลที่ต้องยกเลิก", + ConfirmTitle: "ต้องการยกเลิกการรับอีเมลเหล่านี้หรือไม่", + ConfirmBody: "เมื่อยืนยันแล้ว คุณจะไม่ได้รับอีเมลจากผู้ส่งรายนี้อีก", + ConfirmButton: "ยกเลิกการรับอีเมล", + DoneTitle: "ยกเลิกการรับอีเมลแล้ว", + DoneBody: "คุณจะไม่ได้รับอีเมลจากผู้ส่งรายนี้อีก", + ResubscribeButton: "ยกเลิกโดยไม่ได้ตั้งใจใช่ไหม สมัครรับอีกครั้ง", + ResubscribedTitle: "คุณสมัครรับอีเมลอีกครั้งแล้ว", + ResubscribedBody: "ผู้ส่งสามารถส่งอีเมลถึงคุณได้เหมือนเดิม คุณยกเลิกการรับได้จากอีเมลฉบับใดก็ได้ในภายหลัง", + FailedTitle: "ไม่สามารถดำเนินการกับลิงก์นี้ได้", + FailedBody: "ลิงก์นี้ใช้งานไม่ได้แล้ว โปรดตอบกลับอีเมลแทน แล้วผู้ส่งจะหยุดส่งอีเมลถึงคุณ", + FailedResubscribeBody: "ลิงก์นี้ใช้งานไม่ได้แล้ว โปรดตอบกลับอีเมล แล้วผู้ส่งจะเพิ่มคุณกลับเข้าไปได้", + RetryTitle: "โปรดลองอีกครั้งในอีกสักครู่", + RetryBody: "ขณะนี้ไม่สามารถตรวจสอบลิงก์นี้ได้ โปรดเปิดอีกครั้งในอีกไม่กี่นาที หรือตอบกลับอีเมล แล้วผู้ส่งจะหยุดส่งอีเมลถึงคุณ", + ExpiredTitle: "ลิงก์นี้หมดอายุแล้ว", + InvalidTitle: "ลิงก์ยกเลิกการรับอีเมลนี้ไม่ถูกต้อง", + ReplyBody: "โปรดตอบกลับอีเมลแทน แล้วผู้ส่งจะหยุดส่งอีเมลถึงคุณ", + }, + "tr": { + TestTitle: "Bu bir test e-postasıydı", + TestBody: "Test gönderimlerindeki bağlantı hiçbir alıcıya bağlı değildir, bu nedenle abonelikten çıkılacak bir şey yok.", + ConfirmTitle: "Bu e-postaların aboneliğinden çıkılsın mı?", + ConfirmBody: "Onayladığınızda bu göndericiden başka e-posta almayacaksınız.", + ConfirmButton: "Abonelikten çık", + DoneTitle: "Abonelikten çıktınız", + DoneBody: "Bu göndericiden başka e-posta almayacaksınız.", + ResubscribeButton: "Yanlışlıkla mı çıktınız? Yeniden abone olun", + ResubscribedTitle: "Yeniden abone oldunuz", + ResubscribedBody: "Gönderici size eskisi gibi e-posta gönderebilir. Sonraki herhangi bir e-postadan abonelikten çıkabilirsiniz.", + FailedTitle: "Bu bağlantıyı işleyemedik", + FailedBody: "Bağlantı artık geçerli değil. Bunun yerine e-postayı yanıtlayın, gönderici size yazmayı bırakacaktır.", + FailedResubscribeBody: "Bağlantı artık geçerli değil. E-postayı yanıtlayın, gönderici sizi yeniden ekleyebilir.", + RetryTitle: "Birazdan tekrar deneyin", + RetryBody: "Bu bağlantıyı şu anda kontrol edemedik. Birkaç dakika sonra yeniden açın veya e-postayı yanıtlayın, gönderici size yazmayı bırakacaktır.", + ExpiredTitle: "Bu bağlantının süresi doldu", + InvalidTitle: "Bu abonelikten çıkma bağlantısı geçersiz", + ReplyBody: "Bunun yerine e-postayı yanıtlayın, gönderici size yazmayı bırakacaktır.", + }, + "uk": { + TestTitle: "Це був тестовий лист", + TestBody: "Тестові листи містять посилання, не прив'язане до жодного одержувача, тож відписуватися немає від чого.", + ConfirmTitle: "Відписатися від цих листів?", + ConfirmBody: "Підтвердьте, і ви більше не отримуватимете листів від цього відправника.", + ConfirmButton: "Відписатися", + DoneTitle: "Ви відписалися", + DoneBody: "Ви більше не отримуватимете листів від цього відправника.", + ResubscribeButton: "Відписалися помилково? Підписатися знову", + ResubscribedTitle: "Ви знову підписані", + ResubscribedBody: "Відправник може писати вам, як і раніше. Ви можете відписатися в будь-якому з наступних листів.", + FailedTitle: "Не вдалося обробити посилання", + FailedBody: "Посилання більше не дійсне. Натомість дайте відповідь на лист, і відправник перестане вам писати.", + FailedResubscribeBody: "Посилання більше не дійсне. Дайте відповідь на лист, і відправник зможе знову вас додати.", + RetryTitle: "Спробуйте трохи згодом", + RetryBody: "Зараз не вдалося перевірити це посилання. Відкрийте його знову за кілька хвилин або дайте відповідь на лист, і відправник перестане вам писати.", + ExpiredTitle: "Термін дії посилання закінчився", + InvalidTitle: "Це посилання для відписки недійсне", + ReplyBody: "Натомість дайте відповідь на лист, і відправник перестане вам писати.", + }, + "ur": { + RTL: true, + TestTitle: "یہ ایک آزمائشی ای میل تھی", + TestBody: "آزمائشی ای میلز میں موجود لنک کسی وصول کنندہ سے منسلک نہیں ہوتا، اس لیے ان سبسکرائب کرنے کو کچھ نہیں ہے۔", + ConfirmTitle: "کیا آپ ان ای میلز سے ان سبسکرائب کرنا چاہتے ہیں؟", + ConfirmBody: "تصدیق کریں، اس کے بعد آپ کو اس بھیجنے والے کی طرف سے مزید ای میلز موصول نہیں ہوں گی۔", + ConfirmButton: "ان سبسکرائب کریں", + DoneTitle: "آپ کو ان سبسکرائب کر دیا گیا ہے", + DoneBody: "آپ کو اس بھیجنے والے کی طرف سے مزید ای میلز موصول نہیں ہوں گی۔", + ResubscribeButton: "غلطی سے ان سبسکرائب ہو گئے؟ دوبارہ سبسکرائب کریں", + ResubscribedTitle: "آپ دوبارہ سبسکرائب ہو گئے ہیں", + ResubscribedBody: "بھیجنے والا آپ کو پہلے کی طرح ای میل بھیج سکتا ہے۔ آپ آئندہ کسی بھی ای میل سے ان سبسکرائب کر سکتے ہیں۔", + FailedTitle: "ہم اس لنک پر کارروائی نہیں کر سکے", + FailedBody: "یہ لنک اب درست نہیں ہے۔ اس کے بجائے ای میل کا جواب دیں، بھیجنے والا آپ کو ای میل بھیجنا بند کر دے گا۔", + FailedResubscribeBody: "یہ لنک اب درست نہیں ہے۔ ای میل کا جواب دیں، بھیجنے والا آپ کو دوبارہ شامل کر سکتا ہے۔", + RetryTitle: "تھوڑی دیر میں دوبارہ کوشش کریں", + RetryBody: "ہم ابھی اس لنک کی جانچ نہیں کر سکے۔ چند منٹ بعد اسے دوبارہ کھولیں، یا ای میل کا جواب دیں، بھیجنے والا آپ کو ای میل بھیجنا بند کر دے گا۔", + ExpiredTitle: "اس لنک کی میعاد ختم ہو چکی ہے", + InvalidTitle: "یہ ان سبسکرائب لنک درست نہیں ہے", + ReplyBody: "اس کے بجائے ای میل کا جواب دیں، بھیجنے والا آپ کو ای میل بھیجنا بند کر دے گا۔", + }, + "vi": { + TestTitle: "Đây là email thử nghiệm", + TestBody: "Email thử nghiệm chứa liên kết không gắn với người nhận nào, vì vậy không có gì để hủy đăng ký.", + ConfirmTitle: "Hủy đăng ký nhận các email này?", + ConfirmBody: "Sau khi xác nhận, bạn sẽ không nhận thêm email nào từ người gửi này.", + ConfirmButton: "Hủy đăng ký", + DoneTitle: "Bạn đã hủy đăng ký", + DoneBody: "Bạn sẽ không nhận thêm email nào từ người gửi này.", + ResubscribeButton: "Lỡ hủy đăng ký? Đăng ký lại", + ResubscribedTitle: "Bạn đã đăng ký lại", + ResubscribedBody: "Người gửi có thể gửi email cho bạn như trước. Bạn có thể hủy đăng ký từ bất kỳ email nào sau này.", + FailedTitle: "Không thể xử lý liên kết này", + FailedBody: "Liên kết này không còn hiệu lực. Thay vào đó, hãy trả lời email và người gửi sẽ ngừng gửi cho bạn.", + FailedResubscribeBody: "Liên kết này không còn hiệu lực. Hãy trả lời email và người gửi có thể thêm lại bạn.", + RetryTitle: "Vui lòng thử lại sau ít phút", + RetryBody: "Hiện chúng tôi chưa thể kiểm tra liên kết này. Hãy mở lại sau vài phút, hoặc trả lời email và người gửi sẽ ngừng gửi cho bạn.", + ExpiredTitle: "Liên kết này đã hết hạn", + InvalidTitle: "Liên kết hủy đăng ký này không hợp lệ", + ReplyBody: "Thay vào đó, hãy trả lời email và người gửi sẽ ngừng gửi cho bạn.", + }, + "zh": { + TestTitle: "这是一封测试邮件", + TestBody: "测试邮件中的链接未与任何收件人关联,因此无需退订。", + ConfirmTitle: "要退订这些邮件吗?", + ConfirmBody: "确认后,您将不会再收到该发件人的邮件。", + ConfirmButton: "退订", + DoneTitle: "您已退订", + DoneBody: "您将不会再收到该发件人的邮件。", + ResubscribeButton: "误点了退订?重新订阅", + ResubscribedTitle: "您已重新订阅", + ResubscribedBody: "发件人可以像以前一样给您发送邮件。您可以在之后的任何一封邮件中退订。", + FailedTitle: "无法处理该链接", + FailedBody: "该链接已失效。请直接回复邮件,发件人将不再给您发送邮件。", + FailedResubscribeBody: "该链接已失效。请回复邮件,发件人可以将您重新加入。", + RetryTitle: "请稍后重试", + RetryBody: "暂时无法验证此链接。请几分钟后重新打开,或回复邮件,发件人将不再给您发送邮件。", + ExpiredTitle: "此链接已过期", + InvalidTitle: "此退订链接无效", + ReplyBody: "请直接回复邮件,发件人将不再给您发送邮件。", + }, + "zh-Hant": { + TestTitle: "這是一封測試郵件", + TestBody: "測試郵件中的連結未與任何收件人相關聯,因此無須取消訂閱。", + ConfirmTitle: "要取消訂閱這些郵件嗎?", + ConfirmBody: "確認後,您將不會再收到此寄件者的郵件。", + ConfirmButton: "取消訂閱", + DoneTitle: "您已取消訂閱", + DoneBody: "您將不會再收到此寄件者的郵件。", + ResubscribeButton: "不小心取消了?重新訂閱", + ResubscribedTitle: "您已重新訂閱", + ResubscribedBody: "寄件者可以像以前一樣寄信給您。您可以在之後的任何一封郵件中取消訂閱。", + FailedTitle: "無法處理這個連結", + FailedBody: "此連結已失效。請直接回覆郵件,寄件者就會停止寄信給您。", + FailedResubscribeBody: "此連結已失效。請回覆郵件,寄件者就能重新將您加入。", + RetryTitle: "請稍後再試", + RetryBody: "目前無法驗證此連結。請在幾分鐘後重新開啟,或回覆郵件,寄件者就會停止寄信給您。", + ExpiredTitle: "此連結已過期", + InvalidTitle: "此取消訂閱連結無效", + ReplyBody: "請直接回覆郵件,寄件者就會停止寄信給您。", + }, +} diff --git a/internal/api/handler/unsubscribe_i18n_test.go b/internal/api/handler/unsubscribe_i18n_test.go new file mode 100644 index 000000000..d3cc2b267 --- /dev/null +++ b/internal/api/handler/unsubscribe_i18n_test.go @@ -0,0 +1,137 @@ +package handler + +import ( + "html" + "net/http" + "net/http/httptest" + "os" + "reflect" + "strings" + "testing" + "time" + + "github.com/google/uuid" + + "github.com/warmbly/warmbly/internal/app/unsublink" + "github.com/warmbly/warmbly/internal/models" +) + +func TestUnsubLanguageFollowsTheBrowser(t *testing.T) { + cases := map[string]string{ + "": "en", + "*": "en", + "de-DE,de;q=0.9,en;q=0.8": "de", + "en-US,en;q=0.9,de;q=0.8": "en", + "fr;q=0.5, de;q=0.9": "de", + "xx-YY, pt-BR;q=0.7": "pt", + "de;q=0, fr;q=0.1": "fr", + "de;q=0": "en", + "de;q=abc, it": "it", + "zh-CN,zh;q=0.9": "zh", + "zh-TW": "zh-Hant", + "zh-Hant-HK": "zh-Hant", + "nn-NO": "nb", + "no": "nb", + "tl-PH": "fil", + "sr-Latn-RS": "sr", + "AR-eg": "ar", + "es_MX": "es", + "ja-JP;q=0.8, ko-KR;q=0.8": "ja", + "de;q=0.5,fr;q=NaN": "de", + "de;q=0.5,fr;q=2": "de", + "de;q=0.5,fr;q=+Inf": "de", + } + for header, want := range cases { + if got := unsubLanguage(header); got != want { + t.Errorf("unsubLanguage(%q) = %q, want %q", header, got, want) + } + } +} + +// Every tagging language has the whole page, not half of it in English. +func TestUnsubPageCoversEveryTaggingLanguage(t *testing.T) { + codes := []string{"zh-Hant"} + for code := range models.MailLanguageNames { + codes = append(codes, code) + } + for _, code := range codes { + c, ok := unsubCopies[code] + if !ok { + t.Errorf("%s: no unsubscribe page", code) + continue + } + v := reflect.ValueOf(c) + for i := range v.NumField() { + if f := v.Field(i); f.Kind() == reflect.String && strings.TrimSpace(f.String()) == "" { + t.Errorf("%s: %s is empty", code, v.Type().Field(i).Name) + } + } + if code != "en" && c.ConfirmTitle == unsubCopies["en"].ConfirmTitle { + t.Errorf("%s: the confirm page is still in English", code) + } + } +} + +func TestUnsubPageRendersTheRecipientsLanguage(t *testing.T) { + signer := unsublink.New("secret", "https://api.example.com") + h := &Handler{UnsubscribeLinks: signer} + get := func(lang string) string { + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/unsubscribe/"+signer.Token(uuid.New(), uuid.New(), uuid.New(), time.Now()), nil) + r.Header.Set("Accept-Language", lang) + newUnsubRouter(h).ServeHTTP(w, r) + if w.Code != http.StatusOK || w.Header().Get("Vary") != "Accept-Language" { + t.Fatalf("%s: %d, Vary %q", lang, w.Code, w.Header().Get("Vary")) + } + return html.UnescapeString(w.Body.String()) + } + de := get("de-DE,de;q=0.9") + for _, want := range []string{``, unsubCopies["de"].ConfirmTitle, unsubCopies["de"].ConfirmButton} { + if !strings.Contains(de, want) { + t.Errorf("German page lacks %q:\n%s", want, de) + } + } + if ar := get("ar"); !strings.Contains(ar, ``) || !strings.Contains(ar, unsubCopies["ar"].ConfirmTitle) { + t.Errorf("Arabic page is not right to left:\n%s", ar) + } + if en := get("xx"); !strings.Contains(en, ``) || !strings.Contains(en, "Unsubscribe from these emails?") { + t.Errorf("an unknown language should fall back to English:\n%s", en) + } + // A bad link is refused in the recipient's language too. + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/unsubscribe/nope", nil) + r.Header.Set("Accept-Language", "fr") + newUnsubRouter(h).ServeHTTP(w, r) + if body := html.UnescapeString(w.Body.String()); !strings.Contains(body, unsubCopies["fr"].InvalidTitle) { + t.Errorf("invalid link page not in French:\n%s", body) + } +} + +// The tracking service answers a malformed token and an unreachable backend +// itself, with the backend's wording in every language. +func TestTrackingUnsubPagesAgreeWithTheBackend(t *testing.T) { + src, err := os.ReadFile("../../../tracking/src/unsubscribe_i18n.rs") + if err != nil { + t.Fatalf("read tracking copies: %v", err) + } + rust := string(src) + for code, c := range unsubCopies { + at := strings.Index(rust, "\""+code+"\",\n") + if at < 0 { + t.Errorf("%s: no tracking copy", code) + continue + } + block := rust[at:] + if end := strings.Index(block, " ),"); end > 0 { + block = block[:end] + } + for _, s := range []string{c.InvalidTitle, c.ReplyBody, c.RetryTitle} { + if !strings.Contains(block, "\""+s+"\"") { + t.Errorf("%s: tracking copy lacks %q", code, s) + } + } + if strings.Contains(block, "rtl: true") != c.RTL { + t.Errorf("%s: tracking copy disagrees on direction", code) + } + } +} diff --git a/internal/app/replyclassify/languages.go b/internal/app/replyclassify/languages.go index 88d82450d..4038cd206 100644 --- a/internal/app/replyclassify/languages.go +++ b/internal/app/replyclassify/languages.go @@ -113,6 +113,12 @@ var nordicAttribution = quoteMarker{re: regexp.MustCompile(`(?i)(^|\s)(den\s+)?( // Thunderbird in Russian and Ukrainian: "03.03.2025 10:12, Name пишет:". var cyrillicAttribution = quoteMarker{re: regexp.MustCompile(`(?i)\d{1,2}[./]\d{1,2}[./]\d{2,4},?\s+\d{1,2}:\d{2},\s[^\n]{0,200}?\s(пишет|пише|написал(\(а\))?|написав(\(ла\))?)\s*:`), lineStart: true} +// gmailSigned is Gmail's attribution in the languages whose verb follows the +// quoted address: "We could not reach the sender's server just now. Open this link again in a few minutes, or reply to the email and the sender will stop.
"#; - pub struct UnsubscribeProxy { http: reqwest::Client, backend_url: String, @@ -67,8 +58,16 @@ impl UnsubscribeProxy { } /// Proxies one opt-out request. `suffix` is the fixed path after the token - /// ("" or "/resubscribe"); `body` is None for GET. - async fn forward(&self, token: &str, suffix: &str, body: Option<(Bytes, String)>) -> Response { + /// ("" or "/resubscribe"); `body` is None for GET. The browser's + /// Accept-Language travels on, so the backend's page is in the + /// recipient's language. + async fn forward( + &self, + token: &str, + suffix: &str, + body: Option<(Bytes, String)>, + accept_language: Option{body}
"#, + title = escape(title), + body = escape(body), + ); + ( + status, [ ( header::CONTENT_TYPE, HeaderValue::from_static("text/html; charset=utf-8"), ), (header::CACHE_CONTROL, HeaderValue::from_static("no-store")), + (header::VARY, HeaderValue::from_static("Accept-Language")), ], - UNAVAILABLE_HTML, + html, ) .into_response() } +fn escape(s: &str) -> String { + s.replace('&', "&") + .replace('<', "<") + .replace('>', ">") +} + +/// The browser's Accept-Language, when it sent one. +pub fn accept_language(headers: &HeaderMap) -> OptionReply to the email instead and the sender will stop.
"#, + lang, + copy, + copy.invalid_title, + copy.reply_body, ) - .into_response() } impl UnsubscribeProxy { - pub async fn get(&self, token: &str) -> Response { - self.forward(token, "", None).await + pub async fn get(&self, token: &str, accept_language: Option