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:
@@ -2,6 +2,7 @@ import { create } from "zustand";
|
||||
import { apiFetch, ApiError, CAP, client } from "@/jmap/client";
|
||||
import type { Id, JmapSession } from "@/jmap/types";
|
||||
import { push } from "@/jmap/push";
|
||||
import { setServerLocale } from "@/lib/datetime";
|
||||
|
||||
export type AuthStatus = "loading" | "anonymous" | "authenticated";
|
||||
|
||||
@@ -49,6 +50,7 @@ export const useSession = create<SessionState>((set, get) => ({
|
||||
|
||||
async logout() {
|
||||
push.stop();
|
||||
setServerLocale(null);
|
||||
try {
|
||||
await apiFetch("/api/auth/logout", { method: "POST" });
|
||||
} catch {
|
||||
@@ -62,6 +64,7 @@ export const useSession = create<SessionState>((set, get) => ({
|
||||
try {
|
||||
const s = await apiFetch<JmapSession>("/api/auth/session?refresh=1");
|
||||
client.session = s;
|
||||
setServerLocale(s.ihasmail?.userLocale);
|
||||
set({ session: s });
|
||||
} catch {
|
||||
/* ignore */
|
||||
@@ -83,6 +86,7 @@ export const useSession = create<SessionState>((set, get) => ({
|
||||
|
||||
function applySession(s: JmapSession, set: (p: Partial<SessionState>) => void) {
|
||||
client.session = s;
|
||||
setServerLocale(s.ihasmail?.userLocale);
|
||||
const accountId = s.primaryAccounts[CAP.mail] ?? Object.keys(s.accounts)[0] ?? null;
|
||||
set({ status: "authenticated", session: s, accountId, error: null });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user