Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 7 additions & 5 deletions backend/LexBoxApi/Services/EmailService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public async Task SendForgotPasswordEmail(string emailAddress)
"Login",
new { jwt, returnTo = "/resetPassword" });
ArgumentException.ThrowIfNullOrEmpty(forgotLink);
await RenderEmail(email, new ForgotPasswordEmail(user.Name, forgotLink));
await RenderEmail(email, new ForgotPasswordEmail(user.Name, forgotLink), user.LocalizationCode);
await SendEmailAsync(email);
}

Expand Down Expand Up @@ -72,14 +72,14 @@ public async Task SendVerifyAddressEmail(User user, string? newEmail = null)
"Login",
new { jwt, returnTo = $"/user?emailResult={queryParam}", email = newEmail ?? user.Email, });
ArgumentException.ThrowIfNullOrEmpty(verifyLink);
await RenderEmail(email, new VerifyAddressEmail(user.Name, verifyLink, !string.IsNullOrEmpty(newEmail)));
await RenderEmail(email, new VerifyAddressEmail(user.Name, verifyLink, !string.IsNullOrEmpty(newEmail)), user.LocalizationCode);
await SendEmailAsync(email);
}

public async Task SendPasswordChangedEmail(User user)
{
var email = StartUserEmail(user);
await RenderEmail(email, new PasswordChangedEmail(user.Name));
await RenderEmail(email, new PasswordChangedEmail(user.Name), user.LocalizationCode);
await SendEmailAsync(email);
}

Expand All @@ -88,7 +88,7 @@ public async Task SendCreateProjectRequestEmail(LexAuthUser user, CreateProjectI
var email = new MimeMessage();
email.To.Add(new MailboxAddress("Admin", _emailConfig.CreateProjectEmailDestination));
await RenderEmail(email,
new CreateProjectRequestEmail("Admin", new CreateProjectRequestUser(user.Name, user.Email), projectInput));
new CreateProjectRequestEmail("Admin", new CreateProjectRequestUser(user.Name, user.Email), projectInput), "en");
await SendEmailAsync(email);
}

Expand Down Expand Up @@ -119,14 +119,16 @@ private async Task SendEmailAsync(MimeMessage message)

private record RenderResult(string Subject, string Html);

private async Task RenderEmail<T>(MimeMessage message, T parameters) where T : EmailTemplateBase
private async Task RenderEmail<T>(MimeMessage message, T parameters, string recipientLocale) where T : EmailTemplateBase
{
using var activity = LexBoxActivitySource.Get().StartActivity();
activity?.AddTag("app.email.template", typeof(T).Name);

var httpClient = clientFactory.CreateClient();
httpClient.BaseAddress = new Uri("http://" + _emailConfig.EmailRenderHost);
parameters.BaseUrl = _emailConfig.BaseUrl;
// Uses TryAddWithoutValidation to work around: https://github.com/cibernox/precompile-intl-runtime/issues/45
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Language", $"{recipientLocale};q=1.0");
var response = await httpClient.PostAsJsonAsync("email", parameters, jsonSerializerOptions);
response.EnsureSuccessStatusCode();
var renderResult = await response.Content.ReadFromJsonAsync<RenderResult>();
Expand Down
1 change: 1 addition & 0 deletions frontend/src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ declare global {
interface Locals {
client: Client;
getUser: (() => LexAuthUser | null);
requestedLocale?: string;
}

interface Error {
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { loadI18n } from '$lib/i18n';
import { AUTH_COOKIE_NAME, getUser, isAuthn } from '$lib/user'
import { apiVersion } from '$lib/util/version';
import { redirect, type Handle, type HandleFetch, type HandleServerError, type ResolveOptions } from '@sveltejs/kit'
import { ensureErrorIsTraced, traceRequest, traceFetch } from '$lib/otel/otel.server'
import { availableLocales } from '$locales';
import { env } from '$env/dynamic/private';
import { getErrorMessage, validateFetchResponse } from './hooks.shared';
import {setViewMode} from './routes/(authenticated)/shared';
import { setViewMode } from './routes/(authenticated)/shared';
import * as setCookieParser from 'set-cookie-parser';
import { getLocaleFromAcceptLanguageHeader } from 'svelte-intl-precompile';
import { AUTHENTICATED_ROOT, UNAUTHENTICATED_ROOT } from './routes';

const PUBLIC_ROUTE_ROOTS = [
Expand All @@ -24,6 +27,10 @@ export const handle: Handle = ({ event, resolve }) => {
console.log(`HTTP request: ${event.request.method} ${event.request.url}`);
event.locals.getUser = () => getUser(event.cookies);
return traceRequest(event, async () => {
const user = event.locals.getUser();
const requestedLocale = user?.locale
?? getLocaleFromAcceptLanguageHeader(event.request.headers.get('Accept-Language'), availableLocales);
await loadI18n(requestedLocale);

const options: ResolveOptions = {
filterSerializedResponseHeaders: () => true,
Expand Down
7 changes: 2 additions & 5 deletions frontend/src/routes/+layout.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ import { APP_VERSION, apiVersion } from '$lib/util/version';

import type { LayoutServerLoadEvent } from './$types'
import { USER_LOAD_KEY } from '$lib/user';
import { availableLocales } from '$locales';
import { getLocaleFromAcceptLanguageHeader } from 'svelte-intl-precompile';
import { getRootTraceparent } from '$lib/otel/otel.server'

export async function load({ locals, depends, fetch, request }: LayoutServerLoadEvent) {
const requestLang = getLocaleFromAcceptLanguageHeader(request.headers.get('Accept-Language'), availableLocales);
export async function load({ locals, depends, fetch }: LayoutServerLoadEvent) {
const user = locals.getUser();
const traceParent = getRootTraceparent()

Expand All @@ -20,7 +17,7 @@ export async function load({ locals, depends, fetch, request }: LayoutServerLoad
}
return {
user,
locale: user?.locale ?? requestLang,
locale: user?.locale ?? locals.requestedLocale,
traceParent,
serverVersion: APP_VERSION,
apiVersion: apiVersion.value
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/routes/+layout.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { LayoutLoadEvent } from './$types';
import { browser } from '$app/environment';
import { loadI18n } from '$lib/i18n';

//setting this to false can help diagnose requests to the api as you can see them in the browser instead of sveltekit
export const ssr = true;

export async function load(event: LayoutLoadEvent) {
await loadI18n(event.data.locale);
if (browser) { // i18n is initialized on the server in hooks.server.ts
await loadI18n(event.data.locale);
}
return event.data;
}