Configurable date and time formats, defaulting to the Stalwart locale
Every user-visible date now goes through web/src/lib/datetime.ts, driven by three settings (Settings > General > Locale): - Language & region: automatic, or any of the 618 locales CLDR has data for, each named in its own language and script (web/src/lib/locales.ts, generated by probing Intl over the subtag space). - Date format: automatic (locale order), 22.11.2025, 22/11/2025, 11/22/2025, or ISO 8601 2025-11-22. - Time format: automatic (locale), 24-hour, or 12-hour. Automatic takes the locale Stalwart has for the account, read best-effort at login via x:Account/get (urn:stalwart:jmap) and passed to the client in the session; servers without the capability, or that deny sysAccountGet to a regular user, fall back to the browser locale. POSIX forms are normalised (de_DE.UTF-8 -> de-DE) and script modifiers kept (sr_RS@latin -> sr-Latn-RS, uz_UZ@cyrillic -> uz-Cyrl-UZ), while dialect/variant/currency modifiers are dropped and a script the locale already implies is not appended. Numerals follow the locale (22.11.2025 renders as Arabic-Indic digits under ar-EG); ISO 8601 is the exception and pins date and clock to Latin digits so one line never mixes digit systems. Rewired: message list and headers, quoted reply headers, calendar (titles, weekday and hour gutters, mini calendar, agenda, popovers, invite cards, free/busy), contacts, files, sessions. No raw toLocale*String date calls are left in web/src. Native <input type="datetime-local"> pickers always follow the browser locale and cannot be restyled by a page, so the out-of-office fields echo the entered instant in the chosen format underneath. Also: month-grid day labels no longer wrap when they hold a date, and the mock server serves x:Account/get (MOCK_LOCALE, default en_US). Closes #1
This commit is contained in:
+8
-3
@@ -11,6 +11,7 @@ import {
|
||||
expandTemplate,
|
||||
fetchUpstreamSession,
|
||||
forgetUpstreamSession,
|
||||
getAccountLocale,
|
||||
getUpstreamSession,
|
||||
localizeSession,
|
||||
} from "./upstream.js";
|
||||
@@ -170,7 +171,8 @@ export function createApp(): Hono<Env> {
|
||||
ip,
|
||||
});
|
||||
setSessionCookie(c, cookie, session.remember);
|
||||
return c.json(localizeSession(upstream, sessionExtras(session)));
|
||||
const locale = await getAccountLocale(session.id, session.authorization, upstream);
|
||||
return c.json(localizeSession(upstream, sessionExtras(session, locale)));
|
||||
} catch (err) {
|
||||
return upstreamFailure(c, err);
|
||||
}
|
||||
@@ -180,7 +182,8 @@ export function createApp(): Hono<Env> {
|
||||
const session = c.get("session");
|
||||
try {
|
||||
const upstream = await getUpstreamSession(session.id, session.authorization, c.req.query("refresh") === "1");
|
||||
return c.json(localizeSession(upstream, sessionExtras(session)));
|
||||
const locale = await getAccountLocale(session.id, session.authorization, upstream);
|
||||
return c.json(localizeSession(upstream, sessionExtras(session, locale)));
|
||||
} catch (err) {
|
||||
if (err instanceof UpstreamError && err.status === 401) {
|
||||
sessions.destroy(session.id);
|
||||
@@ -350,7 +353,7 @@ export function createApp(): Hono<Env> {
|
||||
return app;
|
||||
}
|
||||
|
||||
function sessionExtras(session: LiveSession) {
|
||||
function sessionExtras(session: LiveSession, userLocale: string | null = null) {
|
||||
return {
|
||||
ihasmail: {
|
||||
appName: config.appName,
|
||||
@@ -359,6 +362,8 @@ function sessionExtras(session: LiveSession) {
|
||||
sessionId: session.id,
|
||||
loginName: session.username,
|
||||
remember: session.remember,
|
||||
/** Locale configured for the account in Stalwart's directory, if readable. */
|
||||
userLocale,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user