From 5023a53980eb352c9d52bc6ead86310135d934d0 Mon Sep 17 00:00:00 2001 From: Lennart Fleischmann <67686424+lfleischmann@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:58:43 +0100 Subject: [PATCH] feat: email i18n --- backend/dto/webhook/email.go | 5 ++- .../action_resend_passcode.go | 3 +- .../credential_usage/hook_send_passcode.go | 3 +- backend/handler/passcode.go | 1 + backend/mail/locales/passcode.bn.yaml | 38 ++++++++++++++++++ backend/mail/locales/passcode.de.yaml | 39 +++++++++++++++++++ backend/mail/locales/passcode.fr.yaml | 36 +++++++++++++++++ backend/mail/locales/passcode.it.yaml | 36 +++++++++++++++++ backend/mail/locales/passcode.pt-BR.yaml | 38 ++++++++++++++++++ frontend/elements/README.md | 8 ++++ .../src/components/wrapper/Container.tsx | 10 +++-- .../elements/src/contexts/AppProvider.tsx | 8 ++++ frontend/elements/src/example.html | 2 +- frontend/frontend-sdk/README.md | 8 ++++ frontend/frontend-sdk/src/Hanko.ts | 23 +++++++++++ .../frontend-sdk/src/lib/client/HttpClient.ts | 7 ++++ .../frontend-sdk/src/lib/flow-api/Flow.ts | 4 -- .../tests/lib/client/HttpClient.spec.ts | 4 +- 18 files changed, 259 insertions(+), 14 deletions(-) create mode 100644 backend/mail/locales/passcode.bn.yaml create mode 100644 backend/mail/locales/passcode.de.yaml create mode 100644 backend/mail/locales/passcode.fr.yaml create mode 100644 backend/mail/locales/passcode.it.yaml create mode 100644 backend/mail/locales/passcode.pt-BR.yaml diff --git a/backend/dto/webhook/email.go b/backend/dto/webhook/email.go index 40d49237a..7d33e4886 100644 --- a/backend/dto/webhook/email.go +++ b/backend/dto/webhook/email.go @@ -3,10 +3,11 @@ package webhook type EmailSend struct { Subject string `json:"subject"` // subject BodyPlain string `json:"body_plain"` // used for string templates - Body string `json:"body,omitempty"` // used for html templates + Body string `json:"body,omitempty"` // used for HTML templates ToEmailAddress string `json:"to_email_address"` DeliveredByHanko bool `json:"delivered_by_hanko"` - AcceptLanguage string `json:"accept_language"` // accept_language header from http request + AcceptLanguage string `json:"accept_language"` // Deprecated. Accept-Language header from HTTP request + Language string `json:"language"` // X-Language header from HTTP request Type EmailType `json:"type"` // type of the email, currently only "passcode", but other could be added later Data interface{} `json:"data"` diff --git a/backend/flow_api/flow/credential_usage/action_resend_passcode.go b/backend/flow_api/flow/credential_usage/action_resend_passcode.go index b6e33a841..b75c31d4e 100644 --- a/backend/flow_api/flow/credential_usage/action_resend_passcode.go +++ b/backend/flow_api/flow/credential_usage/action_resend_passcode.go @@ -57,7 +57,7 @@ func (a ReSendPasscode) Execute(c flowpilot.ExecutionContext) error { sendParams := services.SendPasscodeParams{ Template: c.Stash().Get(shared.StashPathPasscodeTemplate).String(), EmailAddress: c.Stash().Get(shared.StashPathEmail).String(), - Language: deps.HttpContext.Request().Header.Get("Accept-Language"), + Language: deps.HttpContext.Request().Header.Get("X-Language"), } passcodeResult, err := deps.PasscodeService.SendPasscode(deps.Tx, sendParams) if err != nil { @@ -70,6 +70,7 @@ func (a ReSendPasscode) Execute(c flowpilot.ExecutionContext) error { ToEmailAddress: sendParams.EmailAddress, DeliveredByHanko: deps.Cfg.EmailDelivery.Enabled, AcceptLanguage: sendParams.Language, + Language: sendParams.Language, Type: webhook.EmailTypePasscode, Data: webhook.PasscodeData{ ServiceName: deps.Cfg.Service.Name, diff --git a/backend/flow_api/flow/credential_usage/hook_send_passcode.go b/backend/flow_api/flow/credential_usage/hook_send_passcode.go index 085eb25cb..f16494248 100644 --- a/backend/flow_api/flow/credential_usage/hook_send_passcode.go +++ b/backend/flow_api/flow/credential_usage/hook_send_passcode.go @@ -67,7 +67,7 @@ func (h SendPasscode) Execute(c flowpilot.HookExecutionContext) error { sendParams := services.SendPasscodeParams{ Template: c.Stash().Get(shared.StashPathPasscodeTemplate).String(), EmailAddress: c.Stash().Get(shared.StashPathEmail).String(), - Language: deps.HttpContext.Request().Header.Get("Accept-Language"), + Language: deps.HttpContext.Request().Header.Get("X-Language"), } passcodeResult, err := deps.PasscodeService.SendPasscode(deps.Tx, sendParams) @@ -91,6 +91,7 @@ func (h SendPasscode) Execute(c flowpilot.HookExecutionContext) error { ToEmailAddress: sendParams.EmailAddress, DeliveredByHanko: deps.Cfg.EmailDelivery.Enabled, AcceptLanguage: sendParams.Language, + Language: sendParams.Language, Type: webhook.EmailTypePasscode, Data: webhook.PasscodeData{ ServiceName: deps.Cfg.Service.Name, diff --git a/backend/handler/passcode.go b/backend/handler/passcode.go index ce6e0bc0d..53e8a28e3 100644 --- a/backend/handler/passcode.go +++ b/backend/handler/passcode.go @@ -206,6 +206,7 @@ func (h *PasscodeHandler) Init(c echo.Context) error { ToEmailAddress: email.Address, DeliveredByHanko: true, AcceptLanguage: lang, + Language: lang, Type: webhook.EmailTypePasscode, Data: webhook.PasscodeData{ ServiceName: h.cfg.Service.Name, diff --git a/backend/mail/locales/passcode.bn.yaml b/backend/mail/locales/passcode.bn.yaml new file mode 100644 index 000000000..6711ac284 --- /dev/null +++ b/backend/mail/locales/passcode.bn.yaml @@ -0,0 +1,38 @@ +login_text: + description: "The sign in content of the text email." + other: "আপনার পরিচয় যাচাই করতে নিচের পাসকোডটি দিন:" +ttl_text: + description: "The length how long the passcode is valid." + other: "পাসকোডটি {{ .TTL }} মিনিটের জন্য বৈধ।" +email_subject_login: + description: "" + other: "{{ .Code }} হল আপনার পাসকোড {{ .ServiceName }} এর জন্য" +subject_email_verification: + description: "" + other: "আপনার ইমেল ঠিকানা যাচাই করতে পাসকোড {{ .Code }} ব্যবহার করুন" +subject_login: + description: "" + other: "আপনার অ্যাকাউন্টে লগইন করতে পাসকোড {{ .Code }} ব্যবহার করুন" +subject_recovery: + description: "" + other: "আপনার অ্যাকাউন্ট পুনরুদ্ধার করতে পাসকোড {{ .Code }} ব্যবহার করুন" +email_verification_text: + description: "" + other: "আপনার ইমেল ঠিকানা যাচাই করতে নিচের পাসকোডটি দিন:" +recovery_text: + description: "The content of the recovery text email." + other: "লগইন স্ক্রীনে নিচের পাসকোডটি দিন:" + +subject_email_login_attempted: + description: "Subject for notification about a login attempt." + other: "প্রদত্ত ইমেল ঠিকানা চিহ্নিত হয়নি" +email_login_attempted_text: + description: "Notifies the recipient that either they or someone else attempted to log in to a specific service using an unrecognized email address." + other: "আপনি বা অন্য কেউ {{ .ServiceName }} তে সাইন ইন করার চেষ্টা করেছেন, তবে প্রদত্ত ইমেল ঠিকানা চিহ্নিত হয়নি। দয়া করে আগে একটি অ্যাকাউন্ট তৈরি করুন।" + +subject_email_registration_attempted: + description: "Subject for notification about a registration attempt." + other: "প্রদত্ত ইমেল ঠিকানা ইতিমধ্যেই ব্যবহৃত" +email_registration_attempted_text: + description: "Notifies the recipient that either they or someone else attempted to register for a specific service using an email address that is already in use." + other: "আপনি বা অন্য কেউ {{ .ServiceName }} এর জন্য একটি ইমেল নিবন্ধন করার চেষ্টা করেছেন, কিন্তু প্রদত্ত ইমেল ঠিকানা ইতিমধ্যেই নিবন্ধিত। দয়া করে তার পরিবর্তে লগইন করার চেষ্টা করুন।" diff --git a/backend/mail/locales/passcode.de.yaml b/backend/mail/locales/passcode.de.yaml new file mode 100644 index 000000000..79f8ec58f --- /dev/null +++ b/backend/mail/locales/passcode.de.yaml @@ -0,0 +1,39 @@ +login_text: + description: "The sign in content of the text email." + other: "Geben Sie den folgenden Passcode ein, um Ihre Identität zu überprüfen:" +ttl_text: + description: "The length how long the passcode is valid." + other: "Der Passcode ist {{ .TTL }} Minuten lang gültig." +email_subject_login: + description: "" + other: "{{ .Code }} ist Ihr Passcode für {{ .ServiceName }}" +subject_email_verification: + description: "" + other: "Verwenden Sie den Passcode {{ .Code }}, um Ihre E-Mail-Adresse zu bestätigen" +subject_login: + description: "" + other: "Verwenden Sie den Passcode {{ .Code }}, um sich in Ihr Konto einzuloggen" +subject_recovery: + description: "" + other: "Verwenden Sie den Passcode {{ .Code }}, um Ihr Konto wiederherzustellen" +email_verification_text: + description: "" + other: "Geben Sie den folgenden Passcode ein, um Ihre E-Mail-Adresse zu bestätigen:" +recovery_text: + description: "The content of the recovery text email." + other: "Geben Sie auf Ihrem Anmeldebildschirm den folgenden Passcode ein:" + +subject_email_login_attempted: + description: "Subject for notification about a login attempt." + other: "Die angegebene E-Mail-Adresse wird nicht erkannt." +email_login_attempted_text: + description: "Notifies the recipient that either they or someone else attempted to log in to a specific service using an unrecognized email address." + other: "Sie oder eine andere Person haben versucht, sich bei {{ .ServiceName }} anzumelden, aber die angegebene E-Mail-Adresse wird nicht erkannt. Bitte erstellen Sie zunächst ein Konto." + +subject_email_registration_attempted: + description: "Subject for notification about a registration attempt." + other: "Die angegebene E-Mail-Adresse ist bereits vergeben." +email_registration_attempted_text: + description: "Notifies the recipient that either they or someone else attempted to register for a specific service using an email address that is already in use." + other: "Sie oder eine andere Person haben versucht, eine E-Mail für {{ .ServiceName }} zu registrieren, aber die angegebene E-Mail-Adresse ist bereits registriert. Bitte versuchen Sie stattdessen, sich anzumelden." + diff --git a/backend/mail/locales/passcode.fr.yaml b/backend/mail/locales/passcode.fr.yaml new file mode 100644 index 000000000..7679e473c --- /dev/null +++ b/backend/mail/locales/passcode.fr.yaml @@ -0,0 +1,36 @@ +login_text: + description: "The sign in content of the text email." + other: "Entrez le code d'accès suivant pour vérifier votre identité :" +ttl_text: + description: "The length how long the passcode is valid." + other: "Le code d'accès est valable pendant {{ .TTL }} minutes." +email_subject_login: + description: "" + other: "{{ .Code }} est votre code d'accès pour {{ .ServiceName }}" +subject_email_verification: + description: "" + other: "Utilisez le code d'accès {{ .Code }} pour vérifier votre adresse e-mail" +subject_login: + description: "" + other: "Utilisez le code d'accès {{ .Code }} pour vous connecter à votre compte" +subject_recovery: + description: "" + other: "Utilisez le code d'accès {{ .Code }} pour récupérer votre compte" +email_verification_text: + description: "" + other: "Entrez le code d'accès suivant pour vérifier votre adresse e-mail :" +recovery_text: + description: "The content of the recovery text email." + other: "Entrez le code d'accès suivant sur votre écran de connexion :" +subject_email_login_attempted: + description: "Subject for notification about a login attempt." + other: "L'adresse e-mail fournie n'est pas reconnue" +email_login_attempted_text: + description: "Notifies the recipient that either they or someone else attempted to log in to a specific service using an unrecognized email address." + other: "Vous ou quelqu'un d'autre avez essayé de vous connecter à {{ .ServiceName }}, mais l'adresse e-mail fournie n'a pas été reconnue. Veuillez d'abord créer un compte." +subject_email_registration_attempted: + description: "Subject for notification about a registration attempt." + other: "L'adresse e-mail fournie est déjà utilisée" +email_registration_attempted_text: + description: "Notifies the recipient that either they or someone else attempted to register for a specific service using an email address that is already in use." + other: "Vous ou quelqu'un d'autre avez tenté d'enregistrer un e-mail pour {{ .ServiceName }}, mais l'adresse e-mail fournie est déjà enregistrée. Veuillez essayer de vous connecter à la place." diff --git a/backend/mail/locales/passcode.it.yaml b/backend/mail/locales/passcode.it.yaml new file mode 100644 index 000000000..36c9e9dbc --- /dev/null +++ b/backend/mail/locales/passcode.it.yaml @@ -0,0 +1,36 @@ +login_text: + description: "The sign in content of the text email." + other: "Inserisci il seguente codice di accesso per verificare la tua identità:" +ttl_text: + description: "The length how long the passcode is valid." + other: "Il codice di accesso è valido per {{ .TTL }} minuti." +email_subject_login: + description: "" + other: "{{ .Code }} è il tuo codice di accesso per {{ .ServiceName }}" +subject_email_verification: + description: "" + other: "Usa il codice di accesso {{ .Code }} per verificare il tuo indirizzo e-mail" +subject_login: + description: "" + other: "Usa il codice di accesso {{ .Code }} per accedere al tuo account" +subject_recovery: + description: "" + other: "Usa il codice di accesso {{ .Code }} per recuperare il tuo account" +email_verification_text: + description: "" + other: "Inserisci il seguente codice di accesso per verificare il tuo indirizzo e-mail:" +recovery_text: + description: "The content of the recovery text email." + other: "Inserisci il seguente codice di accesso nella tua schermata di login:" +subject_email_login_attempted: + description: "Subject for notification about a login attempt." + other: "Indirizzo e-mail fornito non riconosciuto" +email_login_attempted_text: + description: "Notifies the recipient that either they or someone else attempted to log in to a specific service using an unrecognized email address." + other: "Tu o qualcun altro avete provato ad accedere a {{ .ServiceName }}, ma l'indirizzo e-mail fornito non è stato riconosciuto. Per favore, crea prima un account." +subject_email_registration_attempted: + description: "Subject for notification about a registration attempt." + other: "Indirizzo e-mail fornito già in uso" +email_registration_attempted_text: + description: "Notifies the recipient that either they or someone else attempted to register for a specific service using an email address that is already in use." + other: "Tu o qualcun altro avete provato a registrare un'e-mail per {{ .ServiceName }}, ma l'indirizzo e-mail fornito è già registrato. Per favore, prova a fare il login invece." diff --git a/backend/mail/locales/passcode.pt-BR.yaml b/backend/mail/locales/passcode.pt-BR.yaml new file mode 100644 index 000000000..625ee5359 --- /dev/null +++ b/backend/mail/locales/passcode.pt-BR.yaml @@ -0,0 +1,38 @@ +login_text: + description: "The sign in content of the text email." + other: "Digite o seguinte código de acesso para verificar sua identidade:" +ttl_text: + description: "The length how long the passcode is valid." + other: "O código de acesso é válido por {{ .TTL }} minutos." +email_subject_login: + description: "" + other: "{{ .Code }} é o seu código de acesso para {{ .ServiceName }}" +subject_email_verification: + description: "" + other: "Use o código de acesso {{ .Code }} para verificar o seu endereço de e-mail" +subject_login: + description: "" + other: "Use o código de acesso {{ .Code }} para fazer login na sua conta" +subject_recovery: + description: "" + other: "Use o código de acesso {{ .Code }} para recuperar sua conta" +email_verification_text: + description: "" + other: "Digite o seguinte código de acesso para verificar o seu endereço de e-mail:" +recovery_text: + description: "The content of the recovery text email." + other: "Digite o seguinte código de acesso na tela de login:" + +subject_email_login_attempted: + description: "Subject for notification about a login attempt." + other: "Endereço de e-mail fornecido não reconhecido" +email_login_attempted_text: + description: "Notifies the recipient that either they or someone else attempted to log in to a specific service using an unrecognized email address." + other: "Você ou outra pessoa tentou fazer login no {{ .ServiceName }}, mas o endereço de e-mail fornecido não foi reconhecido. Por favor, crie uma conta primeiro." + +subject_email_registration_attempted: + description: "Subject for notification about a registration attempt." + other: "Endereço de e-mail fornecido já está em uso" +email_registration_attempted_text: + description: "Notifies the recipient that either they or someone else attempted to register for a specific service using an email address that is already in use." + other: "Você ou outra pessoa tentou registrar um e-mail para {{ .ServiceName }}, mas o endereço de e-mail fornecido já está registrado. Por favor, tente fazer login em vez disso." diff --git a/frontend/elements/README.md b/frontend/elements/README.md index 23eae175e..2d4f48ea9 100644 --- a/frontend/elements/README.md +++ b/frontend/elements/README.md @@ -578,6 +578,7 @@ Translations are currently available for the following languages: - "de" - German - "en" - English - "fr" - French +- "it" - Italian - "ptBR" - Brazilian Portuguese - "zh" - Simplified Chinese @@ -706,6 +707,13 @@ Markup: ``` +### Translation of outgoing Hanko emails + +If you use Hanko Elements the language supplied to the `lang` attribute of any of the components is also used to convey +to the Hanko API the language to use for outgoing emails. If you have disabled email delivery through Hanko and +configured a webhook for the `email.send` event, the value for the `lang` attribute is reflected in the JWT payload of +the token contained in the webhook request in the `language` claim. + ## Experimental Features ### Conditional Mediation / Autofill assisted Requests diff --git a/frontend/elements/src/components/wrapper/Container.tsx b/frontend/elements/src/components/wrapper/Container.tsx index 999593db5..5d86f56f8 100644 --- a/frontend/elements/src/components/wrapper/Container.tsx +++ b/frontend/elements/src/components/wrapper/Container.tsx @@ -10,12 +10,16 @@ interface Props extends h.JSX.HTMLAttributes { } const Container = forwardRef((props: Props, ref) => { - const { lang } = useContext(AppContext); + const { lang, hanko, setHanko } = useContext(AppContext); const { setLang } = useContext(TranslateContext); useEffect(() => { - setLang(lang); - }, [lang, setLang]); + setLang(lang.replace(/[-]/, "")); + setHanko((hanko) => { + hanko.setLang(lang); + return hanko; + }); + }, [hanko, lang, setHanko, setLang]); return (
diff --git a/frontend/elements/src/contexts/AppProvider.tsx b/frontend/elements/src/contexts/AppProvider.tsx index 92ea3b3a5..caecc2515 100644 --- a/frontend/elements/src/contexts/AppProvider.tsx +++ b/frontend/elements/src/contexts/AppProvider.tsx @@ -128,6 +128,7 @@ interface UIState { interface Context { hanko: Hanko; + setHanko: StateUpdater; page: h.JSX.Element; setPage: StateUpdater; init: (compName: ComponentName) => void; @@ -179,6 +180,11 @@ const AppProvider = ({ fallbackLanguage, } = globalOptions; + // Without this, the initial "lang" attribute value sometimes appears to not + // be set properly. This results in a wrong X-Language header value being sent + // to the API and hence in outgoing emails translated in the wrong language. + hanko.setLang(lang?.toString() || fallbackLanguage); + const ref = useRef(null); const storageKeyLastLogin = useMemo( @@ -201,6 +207,7 @@ const AppProvider = ({ const initComponent = useMemo(() => , []); const [page, setPage] = useState(initComponent); + const [, setHanko] = useState(hanko); const [lastLogin, setLastLogin] = useState(); const [uiState, setUIState] = useState({ email: prefilledEmail, @@ -598,6 +605,7 @@ const AppProvider = ({ setSucceededAction, uiState, hanko, + setHanko, lang: lang?.toString() || fallbackLanguage, prefilledEmail, prefilledUsername, diff --git a/frontend/elements/src/example.html b/frontend/elements/src/example.html index f150fb94a..82be327b6 100644 --- a/frontend/elements/src/example.html +++ b/frontend/elements/src/example.html @@ -135,7 +135,7 @@ - + diff --git a/frontend/frontend-sdk/README.md b/frontend/frontend-sdk/README.md index d2016b811..4776b659e 100644 --- a/frontend/frontend-sdk/README.md +++ b/frontend/frontend-sdk/README.md @@ -211,6 +211,14 @@ hanko.onUserDeleted(() => { Please Take a look into the [docs](https://teamhanko.github.io/hanko/jsdoc/hanko-frontend-sdk/) for more details. +### Translation of outgoing emails + +If you use the main `Hanko` client provided by the Frontend SDK, you can use the `lang` parameter in the options when +instantiating the client to configure the language that is used to convey to the Hanko API the +language to use for outgoing emails. If you have disabled email delivery through Hanko and configured a webhook for the +`email.send` event, the value for the `lang` parameter is reflected in the JWT payload of the token contained in the +webhook request in the "Language" claim. + ## Bugs Found a bug? Please report on our [GitHub](https://github.com/teamhanko/hanko/issues) page. diff --git a/frontend/frontend-sdk/src/Hanko.ts b/frontend/frontend-sdk/src/Hanko.ts index 5b04a0b24..10b08a86d 100644 --- a/frontend/frontend-sdk/src/Hanko.ts +++ b/frontend/frontend-sdk/src/Hanko.ts @@ -19,6 +19,12 @@ import { SessionClient } from "./lib/client/SessionClient"; * @property {string=} cookieDomain - The domain where the cookie set from the SDK is available. Defaults to the domain of the page where the cookie was created. * @property {string=} cookieSameSite - Specify whether/when cookies are sent with cross-site requests. Defaults to "lax". * @property {string=} localStorageKey - The prefix / name of the local storage keys. Defaults to "hanko" + * @property {string=} lang - Used to convey the preferred language to the API, e.g. for translating outgoing emails. + * It is transmitted to the API in a custom header (X-Language). + * Should match one of the supported languages ("bn", "de", "en", "fr", "it, "pt-BR", "zh") + * if email delivery by Hanko is enabled. If email delivery by Hanko is disabled and the + * relying party configures a webhook for the "email.send" event, then the set language is + * reflected in the payload of the token contained in the webhook request. */ export interface HankoOptions { timeout?: number; @@ -26,6 +32,7 @@ export interface HankoOptions { cookieDomain?: string; cookieSameSite?: CookieSameSite; localStorageKey?: string; + lang?: string; } /** @@ -70,6 +77,9 @@ class Hanko extends Listener { if (options?.cookieSameSite !== undefined) { opts.cookieSameSite = options.cookieSameSite; } + if (options?.lang !== undefined) { + opts.lang = options.lang; + } this.api = api; /** @@ -118,6 +128,18 @@ class Hanko extends Listener { */ this.flow = new Flow(api, opts); } + + /** + * Sets the preferred language on the underlying sub-clients. The clients' + * base HttpClient uses this language to transmit an X-Language header to the + * API which is then used to e.g. translate outgoing emails. + * + * @public + * @param lang {string} - The preferred language to convey to the API. + */ + setLang(lang: string) { + this.flow.client.lang = lang; + } } // eslint-disable-next-line require-jsdoc @@ -127,6 +149,7 @@ export interface InternalOptions { cookieDomain?: string; cookieSameSite?: CookieSameSite; localStorageKey: string; + lang?: string; } export { Hanko }; diff --git a/frontend/frontend-sdk/src/lib/client/HttpClient.ts b/frontend/frontend-sdk/src/lib/client/HttpClient.ts index 7cdac012d..d8690a902 100644 --- a/frontend/frontend-sdk/src/lib/client/HttpClient.ts +++ b/frontend/frontend-sdk/src/lib/client/HttpClient.ts @@ -115,12 +115,15 @@ class Response { * @property {string} cookieName - The name of the session cookie set from the SDK. * @property {string=} cookieDomain - The domain where cookie set from the SDK is available. Defaults to the domain of the page where the cookie was created. * @property {string} localStorageKey - The prefix / name of the local storage keys. + * @property {string} lang - The language used by the client(s) to convey to the Hanko API the language to use - + * e.g. for translating outgoing emails - in a custom header (X-Language). */ export interface HttpClientOptions { timeout: number; cookieName: string; cookieDomain?: string; localStorageKey: string; + lang?: string; } /** @@ -143,6 +146,7 @@ class HttpClient { sessionState: SessionState; dispatcher: Dispatcher; cookie: Cookie; + lang: string; // eslint-disable-next-line require-jsdoc constructor(api: string, options: HttpClientOptions) { @@ -151,6 +155,7 @@ class HttpClient { this.sessionState = new SessionState({ ...options }); this.dispatcher = new Dispatcher({ ...options }); this.cookie = new Cookie({ ...options }); + this.lang = options.lang; } // eslint-disable-next-line require-jsdoc @@ -159,11 +164,13 @@ class HttpClient { const url = this.api + path; const timeout = this.timeout; const bearerToken = this.cookie.getAuthCookie(); + const lang = this.lang; return new Promise(function (resolve, reject) { xhr.open(options.method, url, true); xhr.setRequestHeader("Accept", "application/json"); xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader("X-Language", lang); if (bearerToken) { xhr.setRequestHeader("Authorization", `Bearer ${bearerToken}`); diff --git a/frontend/frontend-sdk/src/lib/flow-api/Flow.ts b/frontend/frontend-sdk/src/lib/flow-api/Flow.ts index 813afe638..7fca051dd 100644 --- a/frontend/frontend-sdk/src/lib/flow-api/Flow.ts +++ b/frontend/frontend-sdk/src/lib/flow-api/Flow.ts @@ -4,17 +4,13 @@ import { Action } from "./types/action"; import { FetchNextState, FlowPath, Handlers } from "./types/state-handling"; import { HankoError } from "../Errors"; -type MaybePromise = T | Promise; - type ExtendedHandlers = Handlers & { onError?: (e: unknown) => any }; -type GetInitState = (flow: Flow) => MaybePromise | null>; // eslint-disable-next-line require-jsdoc class Flow extends Client { public async init( initPath: FlowPath, handlers: ExtendedHandlers, - // getInitState: GetInitState = () => this.fetchNextState(initPath), ): Promise { const fetchNextState: FetchNextState = async (href: string, body?: any) => { try { diff --git a/frontend/frontend-sdk/tests/lib/client/HttpClient.spec.ts b/frontend/frontend-sdk/tests/lib/client/HttpClient.spec.ts index ec8c00472..b4d13c642 100644 --- a/frontend/frontend-sdk/tests/lib/client/HttpClient.spec.ts +++ b/frontend/frontend-sdk/tests/lib/client/HttpClient.spec.ts @@ -49,7 +49,7 @@ describe("httpClient._fetch()", () => { "Content-Type", "application/json", ); - expect(xhr.setRequestHeader).toHaveBeenCalledTimes(2); + expect(xhr.setRequestHeader).toHaveBeenCalledTimes(3); expect(xhr.open).toHaveBeenNthCalledWith( 1, "GET", @@ -73,7 +73,7 @@ describe("httpClient._fetch()", () => { "Authorization", `Bearer ${jwt}`, ); - expect(xhr.setRequestHeader).toHaveBeenCalledTimes(3); + expect(xhr.setRequestHeader).toHaveBeenCalledTimes(4); }); it("should handle onerror", async () => {