diff --git a/docs/releases.md b/docs/releases.md
index 2c6a8db2..527c52dd 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -1985,10 +1985,15 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
### ntfy server v2.26.x (UNRELEASED)
+**Features:**
+
+* Web app: Add "Date format" and "Time format" settings (Settings -> Appearance), with ISO 8601, day/month/year (slash or dot) and month/day/year date options and a 12-/24-hour clock option, and base the default format on your browser/system locale rather than the selected display language. When logged in, both settings sync across devices via your account ([#1647](https://github.com/binwiederhier/ntfy/issues/1647), thanks to [@wsw70](https://github.com/wsw70) for reporting)
+
**Bug fixes + maintenance:**
* Web app: Smooth transitions and loading animation, remove flickering
* Web app: `GET /account` now reads from the primary database instead of a read replica, so the account view no longer shows stale data right after a change when replicas lag behind
+* Docs: Document the third-party HelmForge Helm chart as a Kubernetes installation option ([#1727](https://github.com/binwiederhier/ntfy/issues/1727), thanks to [@mberlofa](https://github.com/mberlofa))
### ntfy Android v1.25.x (UNRELEASED)
diff --git a/server/server_account.go b/server/server_account.go
index 34d93d6b..4b80285a 100644
--- a/server/server_account.go
+++ b/server/server_account.go
@@ -107,6 +107,12 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
if u.Prefs.Language != nil {
response.Language = *u.Prefs.Language
}
+ if u.Prefs.DateFormat != nil {
+ response.DateFormat = *u.Prefs.DateFormat
+ }
+ if u.Prefs.TimeFormat != nil {
+ response.TimeFormat = *u.Prefs.TimeFormat
+ }
if u.Prefs.Notification != nil {
response.Notification = u.Prefs.Notification
}
@@ -373,6 +379,12 @@ func (s *Server) handleAccountSettingsChange(w http.ResponseWriter, r *http.Requ
if newPrefs.Language != nil {
prefs.Language = newPrefs.Language
}
+ if newPrefs.DateFormat != nil {
+ prefs.DateFormat = newPrefs.DateFormat
+ }
+ if newPrefs.TimeFormat != nil {
+ prefs.TimeFormat = newPrefs.TimeFormat
+ }
if newPrefs.Notification != nil {
if prefs.Notification == nil {
prefs.Notification = &user.NotificationPrefs{}
diff --git a/server/server_account_test.go b/server/server_account_test.go
index 3c4f1787..cee5c57b 100644
--- a/server/server_account_test.go
+++ b/server/server_account_test.go
@@ -207,12 +207,19 @@ func TestAccount_ChangeSettings(t *testing.T) {
})
require.Equal(t, 200, rr.Code)
+ rr = request(t, s, "PATCH", "/v1/account/settings", `{"date_format": "iso8601", "time_format": "24h"}`, map[string]string{
+ "Authorization": util.BearerAuth(token.Value),
+ })
+ require.Equal(t, 200, rr.Code)
+
rr = request(t, s, "GET", "/v1/account", `{"username":"marian", "password":"marian"}`, map[string]string{
"Authorization": util.BearerAuth(token.Value),
})
require.Equal(t, 200, rr.Code)
account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
require.Equal(t, "de", account.Language)
+ require.Equal(t, "iso8601", account.DateFormat) // Merged, not overwritten by previous PATCH
+ require.Equal(t, "24h", account.TimeFormat)
require.Equal(t, util.Int(86400), account.Notification.DeleteAfter)
require.Equal(t, util.String("juntos"), account.Notification.Sound)
require.Nil(t, account.Notification.MinPriority) // Not set
diff --git a/server/types.go b/server/types.go
index 5b254c4d..5c5e65ec 100644
--- a/server/types.go
+++ b/server/types.go
@@ -312,6 +312,8 @@ type apiAccountResponse struct {
SyncTopic string `json:"sync_topic,omitempty"`
Provisioned bool `json:"provisioned,omitempty"`
Language string `json:"language,omitempty"`
+ DateFormat string `json:"date_format,omitempty"`
+ TimeFormat string `json:"time_format,omitempty"`
Notification *user.NotificationPrefs `json:"notification,omitempty"`
Subscriptions []*user.Subscription `json:"subscriptions,omitempty"`
Reservations []*apiAccountReservation `json:"reservations,omitempty"`
diff --git a/user/types.go b/user/types.go
index 344bfa5d..e416113a 100644
--- a/user/types.go
+++ b/user/types.go
@@ -97,6 +97,8 @@ type MagicLink struct {
// Prefs represents a user's configuration settings
type Prefs struct {
Language *string `json:"language,omitempty"`
+ DateFormat *string `json:"date_format,omitempty"`
+ TimeFormat *string `json:"time_format,omitempty"`
Notification *NotificationPrefs `json:"notification,omitempty"`
Subscriptions []*Subscription `json:"subscriptions,omitempty"`
}
diff --git a/web/jsconfig.json b/web/jsconfig.json
new file mode 100644
index 00000000..924531f5
--- /dev/null
+++ b/web/jsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "jsx": "react-jsx",
+ "checkJs": false
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "build"]
+}
diff --git a/web/public/static/langs/ar.json b/web/public/static/langs/ar.json
index d0c1e4b6..cb786842 100644
--- a/web/public/static/langs/ar.json
+++ b/web/public/static/langs/ar.json
@@ -361,7 +361,7 @@
"account_upgrade_dialog_tier_price_per_month": "شهر",
"prefs_appearance_theme_title": "السمة",
"subscribe_dialog_subscribe_use_another_background_info": "لن يتم استلام الاشعارات من الخوادم الخارجية عندما يكون تطبيق الويب مغلقاً",
- "prefs_appearance_theme_system": "النظام (الافتراضي)",
+ "prefs_system_default": "النظام الافتراضي",
"prefs_notifications_min_priority_low_and_higher": "أولوية منخفضة وأعلى",
"prefs_notifications_min_priority_default_and_higher": "الأولوية الافتراضية وما فوقها",
"prefs_notifications_min_priority_high_and_higher": "أولوية عالية وأعلى"
diff --git a/web/public/static/langs/bg.json b/web/public/static/langs/bg.json
index 4e81bb75..f754cfae 100644
--- a/web/public/static/langs/bg.json
+++ b/web/public/static/langs/bg.json
@@ -400,7 +400,7 @@
"prefs_notifications_web_push_title": "Известия във фонов режим",
"prefs_notifications_web_push_enabled_description": "Известията ще бъдат получавани даже и ако приложението за уеб не работи (чрез Web Push)",
"prefs_appearance_theme_title": "Цветова тема",
- "prefs_appearance_theme_system": "Системна (подразбирана)",
+ "prefs_system_default": "Системна подразбирана",
"web_push_subscription_expiring_title": "Известията временно ще бъдат спрени",
"web_push_subscription_expiring_body": "За да продължите да получавате известия, отворете ntfy",
"action_bar_unmute_notifications": "Включване звука на известията",
diff --git a/web/public/static/langs/cs.json b/web/public/static/langs/cs.json
index dde71051..4590624d 100644
--- a/web/public/static/langs/cs.json
+++ b/web/public/static/langs/cs.json
@@ -398,7 +398,7 @@
"prefs_notifications_web_push_disabled_description": "Oznámení jsou přijímána, když je webová aplikace spuštěna (přes WebSocket)",
"prefs_notifications_web_push_disabled": "Zakázáno",
"prefs_appearance_theme_title": "Motiv",
- "prefs_appearance_theme_system": "Systém (výchozí)",
+ "prefs_system_default": "Systém výchozí",
"prefs_appearance_theme_dark": "Tmavý režim",
"prefs_appearance_theme_light": "Světlý režim",
"web_push_subscription_expiring_title": "Oznámení budou pozastavena",
diff --git a/web/public/static/langs/de.json b/web/public/static/langs/de.json
index a619bedb..8fd5a5ba 100644
--- a/web/public/static/langs/de.json
+++ b/web/public/static/langs/de.json
@@ -395,7 +395,7 @@
"prefs_notifications_web_push_enabled": "Aktiviert für {{server}}",
"prefs_notifications_web_push_disabled": "Deaktiviert",
"prefs_appearance_theme_title": "Thema",
- "prefs_appearance_theme_system": "System (Standard)",
+ "prefs_system_default": "System Standard",
"prefs_appearance_theme_dark": "Nachtmodus",
"prefs_appearance_theme_light": "Tagmodus",
"error_boundary_button_reload_ntfy": "ntfy neu laden",
diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json
index e2fae504..b55568a0 100644
--- a/web/public/static/langs/en.json
+++ b/web/public/static/langs/en.json
@@ -412,9 +412,17 @@
"prefs_appearance_title": "Appearance",
"prefs_appearance_language_title": "Language",
"prefs_appearance_theme_title": "Theme",
- "prefs_appearance_theme_system": "System (default)",
+ "prefs_system_default": "System default",
"prefs_appearance_theme_dark": "Dark mode",
"prefs_appearance_theme_light": "Light mode",
+ "prefs_appearance_date_format_title": "Date format",
+ "prefs_appearance_date_format_iso8601": "ISO 8601",
+ "prefs_appearance_date_format_dmy": "Day/Month/Year",
+ "prefs_appearance_date_format_dmy_dot": "Day.Month.Year",
+ "prefs_appearance_date_format_mdy": "Month/Day/Year",
+ "prefs_appearance_time_format_title": "Time format",
+ "prefs_appearance_time_format_12h": "12-hour",
+ "prefs_appearance_time_format_24h": "24-hour",
"prefs_reservations_title": "Reserved topics",
"prefs_reservations_description": "You can reserve topic names for personal use here. Reserving a topic gives you ownership over the topic, and allows you to define access permissions for other users over the topic.",
"prefs_reservations_limit_reached": "You reached your reserved topics limit.",
diff --git a/web/public/static/langs/es.json b/web/public/static/langs/es.json
index 26d787c7..fbad65fe 100644
--- a/web/public/static/langs/es.json
+++ b/web/public/static/langs/es.json
@@ -395,7 +395,7 @@
"prefs_notifications_web_push_enabled_description": "Las notificaciones se reciben incluso cuando la aplicación web no se está ejecutando (a través de Web Push)",
"prefs_notifications_web_push_disabled": "Desactivado",
"prefs_appearance_theme_title": "Tema",
- "prefs_appearance_theme_system": "Sistema (por defecto)",
+ "prefs_system_default": "Sistema por defecto",
"error_boundary_button_reload_ntfy": "Volver a cargar ntfy",
"web_push_subscription_expiring_title": "Las notificaciones se pausarán",
"prefs_notifications_web_push_disabled_description": "Las notificaciones se reciben cuando la aplicación web se está ejecutando (a través de WebSocket)",
diff --git a/web/public/static/langs/et.json b/web/public/static/langs/et.json
index 19c0c704..3f0425b7 100644
--- a/web/public/static/langs/et.json
+++ b/web/public/static/langs/et.json
@@ -238,7 +238,7 @@
"account_basics_tier_title": "Kasutajakonto tüüp",
"account_basics_tier_description": "Sinu kasutajakonto õigused",
"account_delete_dialog_button_submit": "Kustuta kasutajakonto jäädavalt",
- "prefs_appearance_theme_system": "Süsteemi kujundus",
+ "prefs_system_default": "Süsteemi kujundus",
"prefs_appearance_theme_dark": "Tume kujundus",
"prefs_appearance_theme_light": "Hele kujundus",
"prefs_reservations_title": "Reserveeritud teemad",
diff --git a/web/public/static/langs/fi.json b/web/public/static/langs/fi.json
index 4ddf2920..4128fdb5 100644
--- a/web/public/static/langs/fi.json
+++ b/web/public/static/langs/fi.json
@@ -390,7 +390,7 @@
"alert_notification_ios_install_required_title": "iOS-asennus vaaditaan",
"publish_dialog_checkbox_markdown": "Muotoile Markdownina",
"prefs_notifications_web_push_title": "Taustailmoitukset",
- "prefs_appearance_theme_system": "Järjestelmä (oletus)",
+ "prefs_system_default": "Järjestelmä oletus",
"alert_notification_permission_denied_description": "Ota ilmoitukset uudelleen käyttöön selaimessa",
"prefs_appearance_theme_title": "Teema",
"prefs_appearance_theme_light": "Vaalea tila",
diff --git a/web/public/static/langs/fr.json b/web/public/static/langs/fr.json
index 42051387..a9850136 100644
--- a/web/public/static/langs/fr.json
+++ b/web/public/static/langs/fr.json
@@ -396,7 +396,7 @@
"prefs_notifications_web_push_enabled": "Activé pour {{server}}",
"prefs_notifications_web_push_disabled": "Désactivé",
"prefs_appearance_theme_title": "Thème",
- "prefs_appearance_theme_system": "Système (défaut)",
+ "prefs_system_default": "Système défaut",
"prefs_appearance_theme_dark": "Mode sombre",
"prefs_appearance_theme_light": "Mode clair",
"error_boundary_button_reload_ntfy": "Recharger ntfy",
diff --git a/web/public/static/langs/gl.json b/web/public/static/langs/gl.json
index d69da79c..8e5b8b57 100644
--- a/web/public/static/langs/gl.json
+++ b/web/public/static/langs/gl.json
@@ -398,7 +398,7 @@
"prefs_notifications_web_push_enabled": "Activadas para {{server}}",
"prefs_notifications_web_push_disabled": "Desactivadas",
"prefs_appearance_theme_title": "Decorado",
- "prefs_appearance_theme_system": "Sistema (por defecto)",
+ "prefs_system_default": "Sistema por defecto",
"prefs_appearance_theme_dark": "Modo escuro",
"prefs_appearance_theme_light": "Modo claro",
"error_boundary_button_reload_ntfy": "Recargar ntfy",
diff --git a/web/public/static/langs/hu.json b/web/public/static/langs/hu.json
index 8e17b691..a8a72cf6 100644
--- a/web/public/static/langs/hu.json
+++ b/web/public/static/langs/hu.json
@@ -371,7 +371,7 @@
"prefs_users_description_no_sync": "A felhasználónevek és jelszavak nem kerülnek szinkronizálásra a fiókjával.",
"prefs_users_table_cannot_delete_or_edit": "A bejelentkezett felhasználót nem lehet törölni vagy szerkeszteni",
"prefs_appearance_theme_title": "Téma",
- "prefs_appearance_theme_system": "Rendszer (alapértelmezett)",
+ "prefs_system_default": "Rendszer alapértelmezett",
"prefs_appearance_theme_dark": "Sötét mód",
"prefs_appearance_theme_light": "Világos mód",
"prefs_reservations_title": "Fenntartott témák",
diff --git a/web/public/static/langs/id.json b/web/public/static/langs/id.json
index 736270e9..c86537ac 100644
--- a/web/public/static/langs/id.json
+++ b/web/public/static/langs/id.json
@@ -399,7 +399,7 @@
"prefs_notifications_web_push_enabled": "Diaktifkan untuk {{server}}",
"prefs_notifications_web_push_disabled": "Dinonaktifkan",
"prefs_appearance_theme_dark": "Mode gelap",
- "prefs_appearance_theme_system": "Sistem (bawaan)",
+ "prefs_system_default": "Sistem bawaan",
"prefs_appearance_theme_light": "Mode terang",
"web_push_subscription_expiring_title": "Notifikasi akan dijeda",
"web_push_subscription_expiring_body": "Buka ntfy untuk terus menerima notifikasi",
diff --git a/web/public/static/langs/it.json b/web/public/static/langs/it.json
index cf2ad718..0bd24be3 100644
--- a/web/public/static/langs/it.json
+++ b/web/public/static/langs/it.json
@@ -390,7 +390,7 @@
"prefs_notifications_web_push_disabled": "Disabilitato",
"prefs_users_table_cannot_delete_or_edit": "Impossibile eliminare o modificare l'utente registrato",
"prefs_appearance_theme_title": "Tema",
- "prefs_appearance_theme_system": "Sistema (predefinito)",
+ "prefs_system_default": "Sistema predefinito",
"prefs_appearance_theme_dark": "Modalità scura",
"prefs_appearance_theme_light": "Modalità chiara",
"prefs_reservations_table_topic_header": "Argomento",
diff --git a/web/public/static/langs/ja.json b/web/public/static/langs/ja.json
index 1f672b2c..7eddfc52 100644
--- a/web/public/static/langs/ja.json
+++ b/web/public/static/langs/ja.json
@@ -400,7 +400,7 @@
"prefs_notifications_web_push_enabled": "{{server}}で有効",
"prefs_notifications_web_push_disabled": "無効",
"prefs_appearance_theme_title": "テーマ",
- "prefs_appearance_theme_system": "システム (既定)",
+ "prefs_system_default": "システム 既定",
"prefs_appearance_theme_dark": "ダークモード",
"web_push_unknown_notification_title": "不明な通知を受信しました",
"web_push_unknown_notification_body": "ウェブアプリを開いてntfyをアップデートする必要があります",
diff --git a/web/public/static/langs/nb_NO.json b/web/public/static/langs/nb_NO.json
index 2bcf6391..e901b678 100644
--- a/web/public/static/langs/nb_NO.json
+++ b/web/public/static/langs/nb_NO.json
@@ -266,7 +266,7 @@
"account_upgrade_dialog_button_cancel_subscription": "Avslutt abonnement",
"account_tokens_description": "Bruk tilgangstokener når du publiserer og abonnerer via ntfy-APIet, slik at du ikke trenger å sende innloggingsinformasjon for kontoen din. Se dokumentasjonen for å lære mer.",
"account_tokens_table_current_session": "Nåværende nettleserøkt",
- "prefs_appearance_theme_system": "System (standard)",
+ "prefs_system_default": "System standard",
"prefs_notifications_web_push_disabled_description": "Varslinger mottas når web-appen kjører (via WebSocket)",
"prefs_appearance_theme_title": "Tema",
"prefs_appearance_theme_dark": "Mørk modus",
diff --git a/web/public/static/langs/nl.json b/web/public/static/langs/nl.json
index 66997fba..ae920efa 100644
--- a/web/public/static/langs/nl.json
+++ b/web/public/static/langs/nl.json
@@ -397,7 +397,7 @@
"prefs_notifications_web_push_disabled": "Uitgezet",
"prefs_notifications_web_push_enabled_description": "Notificaties worden ontvangen, ook als de webapplicatie niet geopend is (via Web Push)",
"prefs_appearance_theme_title": "Thema",
- "prefs_appearance_theme_system": "Systeem (standaard)",
+ "prefs_system_default": "Systeem standaard",
"prefs_appearance_theme_dark": "Donkere modus",
"prefs_appearance_theme_light": "Lichte modus",
"error_boundary_button_reload_ntfy": "Herlaad ntfy",
diff --git a/web/public/static/langs/pl.json b/web/public/static/langs/pl.json
index 3f90cac4..c83c45fc 100644
--- a/web/public/static/langs/pl.json
+++ b/web/public/static/langs/pl.json
@@ -377,7 +377,7 @@
"prefs_notifications_web_push_disabled_description": "Powiadomienia są dostarczane kiedy aplikacja jest aktywna (poprzez WebSocket)",
"prefs_notifications_web_push_enabled": "Włączone dla {{server}}",
"prefs_notifications_web_push_disabled": "Wyłączone",
- "prefs_appearance_theme_system": "Systemowy (domyślny)",
+ "prefs_system_default": "Systemowy domyślny",
"prefs_appearance_theme_dark": "Tryb ciemny",
"prefs_appearance_theme_light": "Tryb jasny",
"prefs_reservations_edit_button": "Modyfikuj ustawienia dostępu dla tematu",
diff --git a/web/public/static/langs/pt.json b/web/public/static/langs/pt.json
index e7ec50aa..cc6cc639 100644
--- a/web/public/static/langs/pt.json
+++ b/web/public/static/langs/pt.json
@@ -370,7 +370,7 @@
"prefs_notifications_web_push_disabled": "Desativado",
"prefs_users_table_cannot_delete_or_edit": "Não é possível apagar ou editar o utilizador conectado",
"prefs_appearance_theme_title": "Tema",
- "prefs_appearance_theme_system": "Sistema (padrão)",
+ "prefs_system_default": "Sistema padrão",
"prefs_appearance_theme_dark": "Modo escuro",
"prefs_appearance_theme_light": "Modo claro",
"prefs_reservations_title": "Tópicos reservados",
diff --git a/web/public/static/langs/pt_BR.json b/web/public/static/langs/pt_BR.json
index e5498948..e84e4633 100644
--- a/web/public/static/langs/pt_BR.json
+++ b/web/public/static/langs/pt_BR.json
@@ -384,7 +384,7 @@
"prefs_notifications_web_push_disabled": "Desativado",
"prefs_appearance_theme_title": "Tema",
"prefs_users_table_cannot_delete_or_edit": "Não é possível apagar ou editar o usuário conectado",
- "prefs_appearance_theme_system": "Sistema (padrão)",
+ "prefs_system_default": "Sistema padrão",
"prefs_appearance_theme_dark": "Modo escuro",
"prefs_appearance_theme_light": "Modo claro",
"prefs_reservations_title": "Tópicos reservados",
diff --git a/web/public/static/langs/ro.json b/web/public/static/langs/ro.json
index 0283c759..637b9886 100644
--- a/web/public/static/langs/ro.json
+++ b/web/public/static/langs/ro.json
@@ -341,7 +341,7 @@
"prefs_appearance_title": "Aspect",
"prefs_appearance_language_title": "Limbă",
"prefs_appearance_theme_title": "Temă",
- "prefs_appearance_theme_system": "Sistem (implicit)",
+ "prefs_system_default": "Sistem implicit",
"prefs_appearance_theme_dark": "Mod întunecat",
"prefs_appearance_theme_light": "Mod luminos",
"prefs_reservations_title": "Subiecte rezervate",
diff --git a/web/public/static/langs/ru.json b/web/public/static/langs/ru.json
index 43bb432e..b520df96 100644
--- a/web/public/static/langs/ru.json
+++ b/web/public/static/langs/ru.json
@@ -401,7 +401,7 @@
"notifications_actions_failed_notification": "Неудачное действие",
"publish_dialog_checkbox_markdown": "Форматировать как Markdown",
"subscribe_dialog_subscribe_use_another_background_info": "Уведомления с других серверов не будут получены, когда веб-приложение не открыто",
- "prefs_appearance_theme_system": "Как в системе (по умолчанию)",
+ "prefs_system_default": "Как в системе по умолчанию",
"prefs_appearance_theme_dark": "Тёмная",
"prefs_appearance_theme_light": "Светлая",
"account_basics_cannot_edit_or_delete_provisioned_user": "Пользователя, созданного автоматически, нельзя изменить или удалить",
diff --git a/web/public/static/langs/sk.json b/web/public/static/langs/sk.json
index 5e670789..7b26ca81 100644
--- a/web/public/static/langs/sk.json
+++ b/web/public/static/langs/sk.json
@@ -397,7 +397,7 @@
"prefs_notifications_web_push_enabled": "Povolené pre {{server}}",
"prefs_notifications_web_push_disabled": "Zakázané",
"prefs_appearance_theme_title": "Téma",
- "prefs_appearance_theme_system": "Systémové (predvolené)",
+ "prefs_system_default": "Systémové predvolené",
"prefs_appearance_theme_dark": "Tmavý režim",
"prefs_appearance_theme_light": "Svetlý režim",
"error_boundary_button_reload_ntfy": "Obnoviť ntfy",
diff --git a/web/public/static/langs/sv.json b/web/public/static/langs/sv.json
index abcf94d6..92c7563e 100644
--- a/web/public/static/langs/sv.json
+++ b/web/public/static/langs/sv.json
@@ -395,7 +395,7 @@
"prefs_notifications_web_push_enabled": "Aktivera för {{server}}",
"prefs_notifications_web_push_disabled": "Avaktivera",
"prefs_appearance_theme_title": "Tema",
- "prefs_appearance_theme_system": "System (basinställning)",
+ "prefs_system_default": "System basinställning",
"prefs_appearance_theme_dark": "Mörkt läge",
"prefs_appearance_theme_light": "Ljust läge",
"error_boundary_button_reload_ntfy": "Ladda om ntfy",
diff --git a/web/public/static/langs/ta.json b/web/public/static/langs/ta.json
index b822107d..f66d7f7d 100644
--- a/web/public/static/langs/ta.json
+++ b/web/public/static/langs/ta.json
@@ -235,7 +235,7 @@
"prefs_appearance_title": "தோற்றம்",
"prefs_appearance_language_title": "மொழி",
"prefs_appearance_theme_title": "கருப்பொருள்",
- "prefs_appearance_theme_system": "கணினி (இயல்புநிலை)",
+ "prefs_system_default": "கணினி இயல்புநிலை",
"prefs_appearance_theme_dark": "இருண்ட முறை",
"prefs_appearance_theme_light": "ஒளி பயன்முறை",
"prefs_reservations_title": "ஒதுக்கப்பட்ட தலைப்புகள்",
diff --git a/web/public/static/langs/tr.json b/web/public/static/langs/tr.json
index 864bb7b8..1d523f4b 100644
--- a/web/public/static/langs/tr.json
+++ b/web/public/static/langs/tr.json
@@ -395,7 +395,7 @@
"prefs_notifications_web_push_enabled": "{{server}} için etkinleştirildi",
"prefs_notifications_web_push_disabled": "Devre dışı",
"prefs_appearance_theme_title": "Tema",
- "prefs_appearance_theme_system": "Sistem (öntanımlı)",
+ "prefs_system_default": "Sistem öntanımlı",
"prefs_appearance_theme_dark": "Koyu mod",
"prefs_appearance_theme_light": "Açık mod",
"error_boundary_button_reload_ntfy": "ntfy'yi yeniden yükle",
diff --git a/web/public/static/langs/uk.json b/web/public/static/langs/uk.json
index 29b4996f..71e9c6e9 100644
--- a/web/public/static/langs/uk.json
+++ b/web/public/static/langs/uk.json
@@ -398,7 +398,7 @@
"prefs_notifications_web_push_enabled": "Увімкнено для {{server}}",
"prefs_notifications_web_push_disabled": "Вимкнено",
"prefs_appearance_theme_title": "Тема оформлення",
- "prefs_appearance_theme_system": "Система (за замовчуванням)",
+ "prefs_system_default": "Система за замовчуванням",
"prefs_appearance_theme_light": "Світлий режим",
"error_boundary_button_reload_ntfy": "Перезавантажити ntfy",
"web_push_subscription_expiring_title": "Сповіщення буде призупинено",
diff --git a/web/public/static/langs/zh_Hans.json b/web/public/static/langs/zh_Hans.json
index fea7a87e..9f8d8df2 100644
--- a/web/public/static/langs/zh_Hans.json
+++ b/web/public/static/langs/zh_Hans.json
@@ -152,7 +152,7 @@
"prefs_appearance_title": "外观",
"prefs_appearance_language_title": "语言",
"prefs_appearance_theme_title": "主題",
- "prefs_appearance_theme_system": "系统 (默认)",
+ "prefs_system_default": "系统 默认",
"prefs_appearance_theme_dark": "深色模式",
"prefs_appearance_theme_light": "浅色模式",
"priority_min": "最低",
diff --git a/web/public/static/langs/zh_Hant.json b/web/public/static/langs/zh_Hant.json
index 07a92411..e94536df 100644
--- a/web/public/static/langs/zh_Hant.json
+++ b/web/public/static/langs/zh_Hant.json
@@ -229,7 +229,7 @@
"prefs_appearance_language_title": "語言",
"prefs_appearance_theme_dark": "深色模式",
"prefs_appearance_theme_light": "淺色模式",
- "prefs_appearance_theme_system": "系統 (預設)",
+ "prefs_system_default": "系統 預設",
"prefs_appearance_theme_title": "主題",
"prefs_appearance_title": "外觀",
"prefs_notifications_delete_after_never_description": "永不自動刪除通知",
diff --git a/web/src/app/AccountApi.js b/web/src/app/AccountApi.js
index 10facafd..0cc65c2c 100644
--- a/web/src/app/AccountApi.js
+++ b/web/src/app/AccountApi.js
@@ -448,6 +448,12 @@ class AccountApi {
if (account.language) {
await i18n.changeLanguage(account.language);
}
+ if (account.date_format) {
+ await prefs.setDateFormat(account.date_format);
+ }
+ if (account.time_format) {
+ await prefs.setTimeFormat(account.time_format);
+ }
if (account.notification) {
if (account.notification.sound) {
await prefs.setSound(account.notification.sound);
diff --git a/web/src/app/AccountApi.test.js b/web/src/app/AccountApi.test.js
index d8cdf4c3..9fb16c54 100644
--- a/web/src/app/AccountApi.test.js
+++ b/web/src/app/AccountApi.test.js
@@ -13,7 +13,7 @@ vi.mock("./Session", () => ({
}));
vi.mock("./SubscriptionManager", () => ({ default: { syncFromRemote: vi.fn() } }));
vi.mock("./Prefs", () => ({
- default: { setSound: vi.fn(), setDeleteAfter: vi.fn(), setMinPriority: vi.fn() },
+ default: { setSound: vi.fn(), setDeleteAfter: vi.fn(), setMinPriority: vi.fn(), setDateFormat: vi.fn(), setTimeFormat: vi.fn() },
THEME: { DARK: "dark", LIGHT: "light", SYSTEM: "system" },
}));
vi.mock("i18next", () => ({ default: { changeLanguage: vi.fn() } }));
@@ -190,6 +190,8 @@ describe("AccountApi.sync", () => {
fetchMock.mockResolvedValue(
ok({
language: "de",
+ date_format: "iso8601",
+ time_format: "24h",
notification: { sound: "ding", delete_after: 3600, min_priority: 3 },
subscriptions: [{ topic: "t" }],
reservations: [{ topic: "t" }],
@@ -199,6 +201,8 @@ describe("AccountApi.sync", () => {
await accountApi.sync();
expect(i18n.changeLanguage).toHaveBeenCalledWith("de");
+ expect(prefs.setDateFormat).toHaveBeenCalledWith("iso8601");
+ expect(prefs.setTimeFormat).toHaveBeenCalledWith("24h");
expect(prefs.setSound).toHaveBeenCalledWith("ding");
expect(prefs.setDeleteAfter).toHaveBeenCalledWith(3600);
expect(prefs.setMinPriority).toHaveBeenCalledWith(3);
diff --git a/web/src/app/Prefs.js b/web/src/app/Prefs.js
index e77fd3cb..2d2a0808 100644
--- a/web/src/app/Prefs.js
+++ b/web/src/app/Prefs.js
@@ -6,16 +6,43 @@ export const THEME = {
SYSTEM: "system",
};
+// How the date component is displayed. SYSTEM follows the browser's locale (i.e. the OS region/
+// format settings, not the chosen UI translation language); ISO8601 forces YYYY-MM-DD; DMY and MDY
+// force the day/month order regardless of locale (e.g. for an English UI that defaults to US order).
+export const DATE_FORMAT = {
+ SYSTEM: "system",
+ ISO8601: "iso8601",
+ DMY: "dmy",
+ DMY_DOT: "dmy_dot",
+ MDY: "mdy",
+};
+
+// How the time component (clock) is displayed. Orthogonal to DATE_FORMAT and to the UI language --
+// SYSTEM lets the browser locale decide, H12/H24 force a 12- or 24-hour clock. ISO 8601 dates are
+// always 24-hour, so this setting is ignored in that mode.
+export const TIME_FORMAT = {
+ SYSTEM: "system",
+ H12: "12h",
+ H24: "24h",
+};
+
// Default values the getters return when a pref is unset; also used by PrefCache (PrefCache.jsx).
export const PREF_DEFAULTS = {
sound: "ding",
minPriority: 1,
deleteAfter: 604800, // one week
theme: THEME.SYSTEM,
+ dateFormat: DATE_FORMAT.SYSTEM,
+ timeFormat: TIME_FORMAT.SYSTEM,
webPushEnabled: false,
};
-class Prefs {
+// Guards against values the current build doesn't know about (e.g. a pref synced from a newer
+// client, or a renamed enum). An unknown value would leave the Settings