diff --git a/src/logic/logic_email_templates.erl b/src/logic/logic_email_templates.erl index ba7665e..ac972db 100644 --- a/src/logic/logic_email_templates.erl +++ b/src/logic/logic_email_templates.erl @@ -2,6 +2,7 @@ -export([render/2, render_reminder/3, render_waitlist_promoted/2]). %% Kind: verify | invite | reset +%% Non-ASCII binaries MUST use /utf8 (OTP truncates codepoints to 8 bits otherwise). -spec render(verify | invite | reset, binary()) -> {Subject :: binary(), Plain :: binary(), Html :: binary()}. render(Kind, Url) -> @@ -12,28 +13,28 @@ render(Kind, Url) -> -spec render_reminder(binary(), binary(), binary()) -> {Subject :: binary(), Plain :: binary(), Html :: binary()}. render_reminder(Url, Title, WhenText) -> - Subject = <<"CalenTIQ: напоминание о записи">>, - Heading = <<"Напоминание о записи">>, + Subject = <<"CalenTIQ: напоминание о записи"/utf8>>, + Heading = <<"Напоминание о записи"/utf8>>, Lead = iolist_to_binary([ - <<"Скоро: «">>, Title, <<"» — ">>, WhenText, <<" (UTC).">> + <<"Скоро: «"/utf8>>, Title, <<"» — "/utf8>>, WhenText, <<" (UTC).">> ]), - Cta = <<"Открыть запись">>, - Foot = <<"Вы получили письмо, потому что записаны на это событие в CalenTIQ.">>, - Pre = <<"Напоминание о записи в CalenTIQ">>, + Cta = <<"Открыть запись"/utf8>>, + Foot = <<"Вы получили письмо, потому что записаны на это событие в CalenTIQ."/utf8>>, + Pre = <<"Напоминание о записи в CalenTIQ"/utf8>>, assemble(Subject, Heading, Lead, Url, Cta, Foot, Pre). -spec render_waitlist_promoted(binary(), binary()) -> {Subject :: binary(), Plain :: binary(), Html :: binary()}. render_waitlist_promoted(Url, Title) -> - Subject = <<"CalenTIQ: место в листе ожидания">>, - Heading = <<"Для вас освободилось место">>, + Subject = <<"CalenTIQ: место в листе ожидания"/utf8>>, + Heading = <<"Для вас освободилось место"/utf8>>, Lead = iolist_to_binary([ - <<"Вы были в листе ожидания на «">>, Title, - <<"». Мы записали вас — откройте событие в CalenTIQ.">> + <<"Вы были в листе ожидания на «"/utf8>>, Title, + <<"». Мы записали вас — откройте событие в CalenTIQ."/utf8>> ]), - Cta = <<"Открыть событие">>, - Foot = <<"Если запись вам не нужна, отмените её в приложении.">>, - Pre = <<"Место из листа ожидания CalenTIQ">>, + Cta = <<"Открыть событие"/utf8>>, + Foot = <<"Если запись вам не нужна, отмените её в приложении."/utf8>>, + Pre = <<"Место из листа ожидания CalenTIQ"/utf8>>, assemble(Subject, Heading, Lead, Url, Cta, Foot, Pre). assemble(Subject, Heading, Lead, Url, Cta, Foot, Pre) -> @@ -49,26 +50,26 @@ assemble(Subject, Heading, Lead, Url, Cta, Foot, Pre) -> {Subject, Plain, Html}. copy(verify) -> - {<<"CalenTIQ: подтвердите email">>, - <<"Подтвердите email">>, - <<"Один календарь. Три режима для вашего времени. Нажмите кнопку, чтобы активировать аккаунт.">>, - <<"Подтвердить email">>, - <<"Если вы не регистрировались в CalenTIQ, просто проигнорируйте письмо.">>, - <<"Подтвердите email в CalenTIQ">>}; + {<<"CalenTIQ: подтвердите email"/utf8>>, + <<"Подтвердите email"/utf8>>, + <<"Один календарь. Три режима для вашего времени. Нажмите кнопку, чтобы активировать аккаунт."/utf8>>, + <<"Подтвердить email"/utf8>>, + <<"Если вы не регистрировались в CalenTIQ, просто проигнорируйте письмо."/utf8>>, + <<"Подтвердите email в CalenTIQ"/utf8>>}; copy(invite) -> - {<<"CalenTIQ: приглашение специалиста">>, - <<"Вас пригласили как специалиста">>, - <<"Откройте приглашение, чтобы принять или отклонить его в CalenTIQ.">>, - <<"Открыть приглашение">>, - <<"Письмо отправлено, потому что владелец календаря указал этот адрес.">>, - <<"Приглашение специалиста в CalenTIQ">>}; + {<<"CalenTIQ: приглашение специалиста"/utf8>>, + <<"Вас пригласили как специалиста"/utf8>>, + <<"Откройте приглашение, чтобы принять или отклонить его в CalenTIQ."/utf8>>, + <<"Открыть приглашение"/utf8>>, + <<"Письмо отправлено, потому что владелец календаря указал этот адрес."/utf8>>, + <<"Приглашение специалиста в CalenTIQ"/utf8>>}; copy(reset) -> - {<<"CalenTIQ: сброс пароля">>, - <<"Сброс пароля">>, - <<"Если это были вы — задайте новый пароль по кнопке. Ссылка одноразовая.">>, - <<"Сменить пароль">>, - <<"Если вы не запрашивали сброс, проигнорируйте письмо — пароль не изменится.">>, - <<"Сброс пароля CalenTIQ">>}. + {<<"CalenTIQ: сброс пароля"/utf8>>, + <<"Сброс пароля"/utf8>>, + <<"Если это были вы — задайте новый пароль по кнопке. Ссылка одноразовая."/utf8>>, + <<"Сменить пароль"/utf8>>, + <<"Если вы не запрашивали сброс, проигнорируйте письмо — пароль не изменится."/utf8>>, + <<"Сброс пароля CalenTIQ"/utf8>>}. layout() -> case read_priv() of diff --git a/src/logic/logic_notification.erl b/src/logic/logic_notification.erl index 442a1e4..0200683 100755 --- a/src/logic/logic_notification.erl +++ b/src/logic/logic_notification.erl @@ -49,14 +49,14 @@ notify_specialist_invite(UserId, Invite) -> %% In-app / WS: напоминание о записи (Back#70) notify_event_reminder(UserId, #{title := Title, when_text := WhenText} = Data) -> - NTitle = <<"Напоминание о записи">>, - Body = iolist_to_binary([<<"Скоро: «">>, Title, <<"» — ">>, WhenText, <<" (UTC)">>]), + NTitle = <<"Напоминание о записи"/utf8>>, + Body = iolist_to_binary([<<"Скоро: «"/utf8>>, Title, <<"» — "/utf8>>, WhenText, <<" (UTC)">>]), _ = core_notification:create(UserId, event_reminder, NTitle, Body), broadcast_to_user(UserId, event_reminder, Data#{user_id => UserId}). notify_waitlist_promoted(UserId, #{title := Title} = Data) -> - NTitle = <<"Место из листа ожидания">>, - Body = iolist_to_binary([<<"Вас записали на «">>, Title, <<"».">>]), + NTitle = <<"Место из листа ожидания"/utf8>>, + Body = iolist_to_binary([<<"Вас записали на «"/utf8>>, Title, <<"»."/utf8>>]), _ = core_notification:create(UserId, waitlist_promoted, NTitle, Body), broadcast_to_user(UserId, waitlist_promoted, Data#{user_id => UserId}). diff --git a/src/logic/logic_specialist_invite.erl b/src/logic/logic_specialist_invite.erl index ba4df31..0e49094 100755 --- a/src/logic/logic_specialist_invite.erl +++ b/src/logic/logic_specialist_invite.erl @@ -187,8 +187,8 @@ do_create(OwnerId, CalendarId, UserId, Email, Opts) -> notify_invite(#specialist_invite{invitee_user_id = UserId, calendar_id = CalId, token = Token} = Inv) when UserId =/= <<>> -> - Title = <<"Приглашение стать специалистом">>, - Body = <<"Вас пригласили в календарь ", CalId/binary>>, + Title = <<"Приглашение стать специалистом"/utf8>>, + Body = <<"Вас пригласили в календарь "/utf8, CalId/binary>>, _ = core_notification:create(UserId, specialist_invite, Title, Body), logic_notification:notify_specialist_invite(UserId, Inv), maybe_email(Inv, Token); diff --git a/test/unit/logic_email_tests.erl b/test/unit/logic_email_tests.erl index 585ac87..193e106 100644 --- a/test/unit/logic_email_tests.erl +++ b/test/unit/logic_email_tests.erl @@ -91,9 +91,13 @@ test_deliver_error() -> test_template_fill() -> {Sub, Plain, Html} = logic_email_templates:render(verify, <<"https://x/verify?token=z">>), ?assert(binary:match(Sub, <<"CalenTIQ">>) =/= nomatch), + %% OTP truncates Cyrillic in <<"...">> without /utf8 — guard real UTF-8 text + ?assert(binary:match(Sub, <<"подтвердите"/utf8>>) =/= nomatch), + ?assert(binary:match(Plain, <<"Подтвердите"/utf8>>) =/= nomatch), ?assert(binary:match(Plain, <<"https://x/verify?token=z">>) =/= nomatch), ?assert(binary:match(Html, <<"{{">>) =:= nomatch), - ?assert(binary:match(Html, <<"https://x/verify?token=z">>) =/= nomatch). + ?assert(binary:match(Html, <<"https://x/verify?token=z">>) =/= nomatch), + ?assert(binary:match(Html, <<"Подтвердите"/utf8>>) =/= nomatch). test_reminder_deliver() -> Self = self(),