Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

setup localization for twenty-emails #9806

Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
54c3677
feat: update packages version
DeepaPrasanna Jan 22, 2025
1b5ab85
feat: install vite-plugin-dynamic-import package
DeepaPrasanna Jan 22, 2025
09858d6
chore: update yarn.lock
DeepaPrasanna Jan 22, 2025
c6a7204
feat: setup lingui in twenty emails
DeepaPrasanna Jan 22, 2025
9d6cc8d
feat: update email templates for translation
DeepaPrasanna Jan 22, 2025
3a38c8b
feat: add translations
DeepaPrasanna Jan 22, 2025
8527463
feat: use dynamic import to add locales
DeepaPrasanna Jan 22, 2025
ba57e71
feat: pass workspaceId to sendEmailPasswordResetLink
DeepaPrasanna Jan 22, 2025
00ac7c9
feat: pass locale to the email templates
DeepaPrasanna Jan 22, 2025
f67d60a
feat: add await and remove {pretty:true}
DeepaPrasanna Jan 22, 2025
18ad5f6
refactor: remove dynamic imports
DeepaPrasanna Jan 27, 2025
20d075c
Merge branch 'main' into feat/setup-localization-for-twenty-emails
DeepaPrasanna Jan 29, 2025
67c7fc2
chore: downgrade @react-email/render version
DeepaPrasanna Jan 29, 2025
1f85f43
chore: update translations
DeepaPrasanna Jan 29, 2025
c927341
feat: add en locale
DeepaPrasanna Jan 29, 2025
75d0eb6
feat: pass locale to updatePassword
DeepaPrasanna Jan 29, 2025
e3689ac
feat: pass locale via x-locale header
DeepaPrasanna Jan 29, 2025
208a8c5
Merge branch 'main' into feat/setup-localization-for-twenty-emails
DeepaPrasanna Feb 1, 2025
246f784
Merge branch 'main' into feat/setup-localization-for-twenty-emails
DeepaPrasanna Feb 1, 2025
1c5018e
Fixes
FelixMalfait Feb 2, 2025
afc284c
Type fix
FelixMalfait Feb 2, 2025
36a94f1
More fixes
FelixMalfait Feb 2, 2025
ca1fad3
Improve typing
FelixMalfait Feb 2, 2025
d7a861d
Fix attempt
FelixMalfait Feb 2, 2025
c25ea73
Revert attempt to move loading of translations to the server side
FelixMalfait Feb 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
"@octokit/graphql": "^7.0.2",
"@ptc-org/nestjs-query-core": "^4.2.0",
"@ptc-org/nestjs-query-typeorm": "4.2.1-alpha.2",
"@react-email/components": "0.0.12",
"@react-email/render": "0.0.10",
"@react-email/components": "0.0.32",
"@react-email/render": "0.0.17",
"@sentry/node": "^8",
"@sentry/profiling-node": "^8",
"@sentry/react": "^8",
Expand Down
18 changes: 18 additions & 0 deletions packages/twenty-emails/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see Babel in the emails folder. I think it's best to use SWC but it seemed to use Babel. But you didn't migrate twenty-emails to SWC here did you? I'm surprised it works. Want to take a shot at removing Babel?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I didn't migrated to SWC. I chose to go creating .swcrc because the vite.config.ts already imported React from @vitejs/plugin-react-swc
I will remove babel and will check how it goes then.

"$schema": "https://json.schemastore.org/swcrc",
"jsc": {
"experimental": {
"plugins": [
[
"@lingui/swc-plugin",
{
"runtimeModules": {
"i18n": ["@lingui/core", "i18n"],
"trans": ["@lingui/react", "Trans"]
}
}
Comment on lines +8 to +13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: indentation is inconsistent with the rest of the file (extra space before opening brace)

]
]
}
}
}
12 changes: 12 additions & 0 deletions packages/twenty-emails/lingui.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from '@lingui/cli';

export default defineConfig({
sourceLocale: 'en',
locales: ['fr', 'en', 'pt', 'de', 'it', 'es', 'zh-Hans', 'zh-Hant'],
catalogs: [
{
path: '<rootDir>/src/locales/{locale}/messages',
include: ['src'],
},
],
});
14 changes: 14 additions & 0 deletions packages/twenty-emails/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@
"configurations": {
"fix": {}
}
},
"lingui:extract": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"command": "lingui extract"
}
},
"lingui:compile": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"command": "lingui compile --typescript"
}
}
}
}
23 changes: 14 additions & 9 deletions packages/twenty-emails/src/components/BaseEmail.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { PropsWithChildren } from 'react';
import { i18n } from '@lingui/core';
import { I18nProvider } from '@lingui/react';
import { Container, Html } from '@react-email/components';
import { PropsWithChildren } from 'react';

import { BaseHead } from 'src/components/BaseHead';
import { Logo } from 'src/components/Logo';

type BaseEmailProps = PropsWithChildren<{
width?: number;
locale: string;
}>;

export const BaseEmail = ({ children, width }: BaseEmailProps) => {
export const BaseEmail = ({ children, width, locale }: BaseEmailProps) => {
return (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: locale prop is now required but no default value provided - could cause runtime errors if not passed

<Html lang="en">
<BaseHead />
<Container width={width || 290}>
<Logo />
{children}
</Container>
</Html>
<I18nProvider i18n={i18n}>
<Html lang={locale}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: i18n instance needs to be initialized and configured before being used in I18nProvider

<BaseHead />
<Container width={width || 290}>
<Logo />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: width default of 290 should be defined as a constant at the top level

{children}
</Container>
</Html>
</I18nProvider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const CleanSuspendedWorkspaceEmail = ({
const helloString = userName?.length > 1 ? `Hello ${userName}` : 'Hello';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: helloString needs to be wrapped in <Trans> component for localization


return (
<BaseEmail width={333}>
<BaseEmail width={333} locale="en">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: locale="en" should not be hardcoded - should be passed as a prop from parent component

Suggested change
<BaseEmail width={333} locale="en">
<BaseEmail width={333} locale={locale}>

<Title value="Deleted Workspace 🥺" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Title text needs to be wrapped in <Trans> component for localization

<MainText>
{helloString},
Expand Down
21 changes: 15 additions & 6 deletions packages/twenty-emails/src/emails/password-reset-link.email.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { BaseEmail } from 'src/components/BaseEmail';
import { CallToAction } from 'src/components/CallToAction';
import { Link } from 'src/components/Link';
import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { loadAndActivateLocale } from 'src/utils/loadAndActivateLocale';

type PasswordResetLinkEmailProps = {
duration: string;
link: string;
locale: string;
};

export const PasswordResetLinkEmail = ({
duration,
link,
locale,
}: PasswordResetLinkEmailProps) => {
loadAndActivateLocale(locale);

return (
<BaseEmail>
<Title value="Reset your password 🗝" />
<CallToAction href={link} value="Reset" />
<BaseEmail locale={locale}>
<Title value={t`Reset your password 🗝`} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: The emoji in the title string may cause display issues in some email clients. Consider using a Unicode emoji or removing it for better compatibility.

<CallToAction href={link} value={t`Reset`} />
<MainText>
This link is only valid for the next {duration}. If link does not work,
you can use the login verification link directly:
<br />
<Trans>
This link is only valid for the next {duration}. If the link does not
work, you can use the login verification link directly:
Comment on lines +27 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: The duration variable is not wrapped in a translation component, which is why it remains in English across all locales. Consider using a number + unit format that can be properly localized.

<br />
</Trans>
<Link href={link} value={link} />
</MainText>
</BaseEmail>
Expand Down
33 changes: 22 additions & 11 deletions packages/twenty-emails/src/emails/password-update-notify.email.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,49 @@
import { format } from 'date-fns';
import { i18n } from '@lingui/core';
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';

import { BaseEmail } from 'src/components/BaseEmail';
import { CallToAction } from 'src/components/CallToAction';
import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { loadAndActivateLocale } from 'src/utils/loadAndActivateLocale';

type PasswordUpdateNotifyEmailProps = {
userName: string;
email: string;
link: string;
locale: string;
};

export const PasswordUpdateNotifyEmail = ({
userName,
email,
link,
locale,
}: PasswordUpdateNotifyEmailProps) => {
const helloString = userName?.length > 1 ? `Dear ${userName}` : 'Dear';
loadAndActivateLocale(locale);

const helloString = userName?.length > 1 ? t`Dear ${userName}` : t`Dear`;
const formattedDate = i18n.date(new Date());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: string interpolation in t macro may not work as expected - consider using Trans component instead for variable interpolation


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: i18n.date() needs date format options to ensure consistent formatting across locales

return (
<BaseEmail>
<Title value="Password updated" />
<BaseEmail locale={locale}>
<Title value={t`Password updated`} />
<MainText>
{helloString},
<br />
<br />
This is a confirmation that password for your account ({email}) was
successfully changed on {format(new Date(), 'MMMM d, yyyy')}.
<br />
<br />
If you did not initiate this change, please contact your workspace owner
immediately.
<Trans>
This is a confirmation that password for your account ({email}) was
successfully changed on {formattedDate}.
<br />
<br />
If you did not initiate this change, please contact your workspace
owner immediately.
</Trans>
<br />
</MainText>
<CallToAction value="Connect to Twenty" href={link} />
<CallToAction value={t`Connect to Twenty`} href={link} />
</BaseEmail>
);
};
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';

import { BaseEmail } from 'src/components/BaseEmail';
import { CallToAction } from 'src/components/CallToAction';
import { Footer } from 'src/components/Footer';
import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { loadAndActivateLocale } from 'src/utils/loadAndActivateLocale';

type SendEmailVerificationLinkEmailProps = {
link: string;
locale: string;
};

export const SendEmailVerificationLinkEmail = ({
link,
locale,
}: SendEmailVerificationLinkEmailProps) => {
loadAndActivateLocale(locale);

return (
<BaseEmail width={333}>
<Title value="Confirm your email address" />
<CallToAction href={link} value="Verify Email" />
<BaseEmail width={333} locale={locale}>
<Title value={t`Confirm your email address`} />
<CallToAction href={link} value={t`Verify Email`} />
<br />
<br />
<MainText>
Thanks for registering for an account on Twenty! Before we get started,
we just need to confirm that this is you. Click above to verify your
email address.
<Trans>
Thanks for registering for an account on Twenty! Before we get
started, we just need to confirm that this is you. Click above to
verify your email address.
</Trans>
</MainText>
<Footer />
</BaseEmail>
Expand Down
16 changes: 12 additions & 4 deletions packages/twenty-emails/src/emails/send-invite-link.email.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { Img } from '@react-email/components';
import { emailTheme } from 'src/common-style';

Expand All @@ -10,6 +12,7 @@ import { MainText } from 'src/components/MainText';
import { Title } from 'src/components/Title';
import { WhatIsTwenty } from 'src/components/WhatIsTwenty';
import { capitalize } from 'src/utils/capitalize';
import { loadAndActivateLocale } from 'src/utils/loadAndActivateLocale';
import { getImageAbsoluteURI } from 'twenty-shared';

type SendInviteLinkEmailProps = {
Expand All @@ -21,35 +24,40 @@ type SendInviteLinkEmailProps = {
lastName: string;
};
serverUrl: string;
locale: string;
};

export const SendInviteLinkEmail = ({
link,
workspace,
sender,
serverUrl,
locale,
}: SendInviteLinkEmailProps) => {
loadAndActivateLocale(locale);

const workspaceLogo = workspace.logo
? getImageAbsoluteURI({ imageUrl: workspace.logo, baseUrl: serverUrl })
: null;

return (
<BaseEmail width={333}>
<Title value="Join your team on Twenty" />
<BaseEmail width={333} locale={locale}>
<Title value={t`Join your team on Twenty`} />
<MainText>
{capitalize(sender.firstName)} (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: sender.firstName needs to be wrapped in t`` for translation since it's being capitalized

<Link
href={`mailto:${sender.email}`}
value={sender.email}
color={emailTheme.font.colors.blue}
/>
) has invited you to join a workspace called <b>{workspace.name}</b>
)<Trans>has invited you to join a workspace called </Trans>
<b>{workspace.name}</b>
Comment on lines +50 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: text split between Trans component and workspace name may cause grammatical issues in languages with different word orders

Suggested change
)<Trans>has invited you to join a workspace called </Trans>
<b>{workspace.name}</b>
)<Trans>has invited you to join a workspace called {workspace.name}</Trans>

<br />
</MainText>
<HighlightedContainer>
{workspaceLogo && <Img src={workspaceLogo} width={40} height={40} />}
{workspace.name && <HighlightedText value={workspace.name} />}
<CallToAction href={link} value="Accept invite" />
<CallToAction href={link} value={t`Accept invite`} />
</HighlightedContainer>
<WhatIsTwenty />
</BaseEmail>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const WarnSuspendedWorkspaceEmail = ({
const helloString = userName?.length > 1 ? `Hello ${userName}` : 'Hello';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: 'Hello' string should be localized using Trans or t macro


return (
<BaseEmail width={333}>
<BaseEmail width={333} locale="en">
<Title value="Suspended Workspace 😴" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: title value should be localized using t macro

<MainText>
{helloString},
Expand Down
71 changes: 71 additions & 0 deletions packages/twenty-emails/src/locales/de/messages.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2025-01-21 22:51+0530\n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: POT-Creation-Date is set to 2025-01-21, which is in the future. This should be the current date.

"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: de\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"


#: src/emails/send-invite-link.email.tsx:60
msgid "Accept invite"
msgstr "Einladung annehmen"

#: src/emails/send-email-verification-link.email.tsx:24
msgid "Confirm your email address"
msgstr "Bestätigen Sie Ihre E-Mail-Adresse"

#: src/emails/password-update-notify.email.tsx:46
msgid "Connect to Twenty"
msgstr "Verbinde dich mit Twenty"

#: src/emails/password-update-notify.email.tsx:26
msgid "Dear"
msgstr "Sehr geehrte/r"

#: src/emails/password-update-notify.email.tsx:26
msgid "Dear {userName}"
msgstr "Sehr geehrte/r {userName}"

#: src/emails/send-invite-link.email.tsx:53
msgid "has invited you to join a workspace called "
msgstr "hat Sie eingeladen, einem Arbeitsbereich namens "

#: src/emails/send-invite-link.email.tsx:45
msgid "Join your team on Twenty"
msgstr "Treten Sie Ihrem Team auf Twenty bei"

#: src/emails/password-update-notify.email.tsx:31
msgid "Password updated"
msgstr "Passwort aktualisiert"

#: src/emails/password-reset-link.email.tsx:26
msgid "Reset"
msgstr "Zurücksetzen"

#: src/emails/password-reset-link.email.tsx:25
msgid "Reset your password 🗝"
msgstr "Setzen Sie Ihr Passwort zurück 🗝"

#: src/emails/send-email-verification-link.email.tsx:29
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "Danke, dass Sie sich für ein Konto auf Twenty registriert haben! Bevor wir beginnen, müssen wir nur bestätigen, dass Sie es sind. Klicken Sie oben, um Ihre E-Mail-Adresse zu bestätigen."

#: src/emails/password-update-notify.email.tsx:36
msgid "This is a confirmation that password for your account ({email}) was successfully changed on {formattedDate}.<0/><1/>If you did not initiate this change, please contact your workspace owner immediately."
msgstr "Dies ist eine Bestätigung, dass das Passwort für Ihr Konto ({email}) erfolgreich am {formattedDate} geändert wurde.<0/><1/>Wenn Sie diese Änderung nicht initiiert haben, kontaktieren Sie bitte sofort den Eigentümer Ihres Arbeitsbereichs."

#: src/emails/password-reset-link.email.tsx:28
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:<0/>"
msgstr "Dieser Link ist nur für die nächsten {duration} gültig. Wenn der Link nicht funktioniert, können Sie direkt den Login-Verifizierungslink verwenden:<0/>"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: The {duration} variable is not localized and will always appear in English as noted in the PR description. Consider using a localized duration format.


#: src/emails/send-email-verification-link.email.tsx:25
msgid "Verify Email"
msgstr "E-Mail verifizieren"
1 change: 1 addition & 0 deletions packages/twenty-emails/src/locales/de/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"A7zEzZ\":[[\"daysLeft\",\"plural\",{\"one\":[\"Tag übrig\"],\"other\":[\"Tage übrig\"]}]],\"0JB+B5\":[[\"daysLeft\",\"plural\",{\"one\":[\"Keine Sorge! Erstellen oder bearbeiten Sie einfach einen Datensatz innerhalb des nächsten \",[\"remainingDays\"],\" Tages, um weiterhin Zugriff zu haben.\"],\"other\":[\"Keine Sorge! Erstellen oder bearbeiten Sie einfach einen Datensatz innerhalb der nächsten \",[\"remainingDays\"],\" Tage, um weiterhin Zugriff zu haben.\"]}]],\"4WPI3S\":[\"Einladung annehmen\"],\"RPHFhC\":[\"Bestätigen Sie Ihre E-Mail-Adresse\"],\"nvkBPN\":[\"Verbinde dich mit Twenty\"],\"791xh3\":[\"Inaktive Arbeitsbereiche 😵, die gelöscht werden sollten\"],\"JRzgV7\":[\"Sehr geehrte/r\"],\"Lm5BBI\":[\"Sehr geehrte/r \",[\"userName\"]],\"tGme7M\":[\"hat Sie eingeladen, einem Arbeitsbereich namens \"],\"uzTaYi\":[\"Hallo\"],\"r0OJlV\":[\"Hallo \",[\"userName\"]],\"vq4f+3\":[\"Inaktiver Arbeitsbereich 😴\"],\"Y4Nk3c\":[\"Es scheint eine Inaktivitätsperiode in Ihrem<0>\",[\"workspaceDisplayName\"],\"</0> Arbeitsbereich gegeben zu haben.<1/><2/>Bitte beachten Sie, dass das Konto bald deaktiviert wird und alle damit verbundenen Daten in diesem Arbeitsbereich gelöscht werden.<3/><4/>\"],\"PviVyk\":[\"Treten Sie Ihrem Team auf Twenty bei\"],\"ja42wz\":[\"Liste der <0>workspaceIds</0>, die seit mindestens <1>\",[\"minDaysSinceInactive\"],\" Tagen inaktiv sind:\"],\"ogtYkT\":[\"Passwort aktualisiert\"],\"OfhWJH\":[\"Zurücksetzen\"],\"RE5NiU\":[\"Setzen Sie Ihr Passwort zurück 🗝\"],\"7yDt8q\":[\"Danke, dass Sie sich für ein Konto auf Twenty registriert haben! Bevor wir beginnen, müssen wir nur bestätigen, dass Sie es sind. Klicken Sie oben, um Ihre E-Mail-Adresse zu bestätigen.\"],\"wSOsS+\":[\"Dies ist eine Bestätigung, dass das Passwort für Ihr Konto (\",[\"email\"],\") erfolgreich am \",[\"formattedDate\"],\" geändert wurde.<0/><1/>Wenn Sie diese Änderung nicht initiiert haben, kontaktieren Sie bitte sofort den Eigentümer Ihres Arbeitsbereichs.\"],\"RGs4CL\":[\"This link is only valid for the next \",[\"duration\"],\". If the link does not\\n work, you can use the login verification link directly:<0/>\"],\"2oA637\":[\"Dieser Link ist nur für die nächsten \",[\"duration\"],\" gültig. Wenn der Link nicht funktioniert, können Sie direkt den Login-Verifizierungslink verwenden:<0/>\"],\"wCKkSr\":[\"E-Mail verifizieren\"]}")as Messages;
Loading
Loading