Add per account locale setting

Add a per user account setting for the locale so that the server can
correctly render pages with localized time formatting.
This commit is contained in:
Hornwitser 2025-06-13 21:50:22 +02:00
parent fad1bb2482
commit fb7a60db28
5 changed files with 28 additions and 0 deletions

View file

@ -9,6 +9,7 @@ export default defineNuxtConfig({
vapidPrivateKeyFile: "", vapidPrivateKeyFile: "",
public: { public: {
defaultTimezone: "Europe/Oslo", defaultTimezone: "Europe/Oslo",
defaultLocale: "en-GB",
vapidPublicKey: "", vapidPublicKey: "",
} }
}, },

View file

@ -10,6 +10,10 @@
Timezone Timezone
<input type="text" v-model="timezone" :placeholder="accountStore.defaultTimezone"> <input type="text" v-model="timezone" :placeholder="accountStore.defaultTimezone">
</label> </label>
<label>
Locale
<input type="text" v-model="locale" :placeholder="accountStore.defaultLocale">
</label>
<button type="submit"> <button type="submit">
Save Save
</button> </button>
@ -38,6 +42,7 @@ const sessionStore = useSessionStore();
const accountStore = useAccountStore(); const accountStore = useAccountStore();
const timezone = ref(accountStore.timezone ?? ""); const timezone = ref(accountStore.timezone ?? "");
const locale = ref(accountStore.locale ?? "");
async function changeSettings() { async function changeSettings() {
try { try {
@ -45,6 +50,7 @@ async function changeSettings() {
method: "patch", method: "patch",
body: { body: {
timezone: timezone.value, timezone: timezone.value,
locale: locale.value,
} }
}); });
await sessionStore.fetch(); await sessionStore.fetch();

View file

@ -24,6 +24,15 @@ export default defineEventHandler(async (event) => {
}); });
} }
} }
if (patch.locale?.length) {
const locale = DateTime.local({ locale: patch.locale }).resolvedLocaleOptions().locale;
if (locale !== patch.locale) {
throw createError({
status: 400,
message: `Invalid locale: the locale "${patch.locale}" is not supported (was treated as "${locale}")`
});
}
}
const accounts = await readAccounts(); const accounts = await readAccounts();
const sessionAccount = accounts.find(account => account.id === session.accountId); const sessionAccount = accounts.find(account => account.id === session.accountId);
@ -51,6 +60,12 @@ export default defineEventHandler(async (event) => {
else else
delete sessionAccount.timezone; delete sessionAccount.timezone;
} }
if (patch.locale !== undefined) {
if (patch.locale)
sessionAccount.locale = patch.locale;
else
delete sessionAccount.locale;
}
await writeAccounts(accounts); await writeAccounts(accounts);
// Update Schedule counts. // Update Schedule counts.

View file

@ -9,6 +9,7 @@ export interface ApiAccount {
interestedEventIds?: number[], interestedEventIds?: number[],
interestedEventSlotIds?: number[], interestedEventSlotIds?: number[],
timezone?: string, timezone?: string,
locale?: string,
} }
export const apiAccountPatchSchema = z.object({ export const apiAccountPatchSchema = z.object({
@ -16,6 +17,7 @@ export const apiAccountPatchSchema = z.object({
interestedEventIds: z.optional(z.array(z.number())), interestedEventIds: z.optional(z.array(z.number())),
interestedEventSlotIds: z.optional(z.array(z.number())), interestedEventSlotIds: z.optional(z.array(z.number())),
timezone: z.optional(z.string()), timezone: z.optional(z.string()),
locale: z.optional(z.string()),
}); });
export type ApiAccountPatch = z.infer<typeof apiAccountPatchSchema>; export type ApiAccountPatch = z.infer<typeof apiAccountPatchSchema>;

View file

@ -8,6 +8,7 @@ export const useAccountStore = defineStore("account", () => {
id: ref<number>(), id: ref<number>(),
name: ref<string>(), name: ref<string>(),
timezone: ref<string>(), timezone: ref<string>(),
locale: ref<string>(),
type: ref<ApiAccount["type"]>(), type: ref<ApiAccount["type"]>(),
interestedEventIds: ref<Set<number>>(new Set()), interestedEventIds: ref<Set<number>>(new Set()),
interestedEventSlotIds: ref<Set<number>>(new Set()), interestedEventSlotIds: ref<Set<number>>(new Set()),
@ -19,6 +20,7 @@ export const useAccountStore = defineStore("account", () => {
state.id.value = account?.id; state.id.value = account?.id;
state.name.value = account?.name; state.name.value = account?.name;
state.timezone.value = account?.timezone; state.timezone.value = account?.timezone;
state.locale.value = account?.locale;
state.type.value = account?.type; state.type.value = account?.type;
state.interestedEventIds.value = new Set(account?.interestedEventIds ?? []); state.interestedEventIds.value = new Set(account?.interestedEventIds ?? []);
state.interestedEventSlotIds.value = new Set(account?.interestedEventSlotIds ?? []); state.interestedEventSlotIds.value = new Set(account?.interestedEventSlotIds ?? []);
@ -30,6 +32,8 @@ export const useAccountStore = defineStore("account", () => {
canEditPublic: computed(() => state.type.value === "admin"), canEditPublic: computed(() => state.type.value === "admin"),
activeTimezone: computed(() => state.timezone.value || runtimeConfig.public.defaultTimezone), activeTimezone: computed(() => state.timezone.value || runtimeConfig.public.defaultTimezone),
defaultTimezone: computed(() => runtimeConfig.public.defaultTimezone), defaultTimezone: computed(() => runtimeConfig.public.defaultTimezone),
activeLocale: computed(() => state.locale.value || runtimeConfig.public.defaultLocale),
defaultLocale: computed(() => runtimeConfig.public.defaultLocale),
}; };
const actions = { const actions = {