feat: locale-aware date format in email list with preset picker #331

This commit is contained in:
Linus Rath
2026-05-25 16:17:32 +02:00
parent 537707d9ed
commit 534894c38d
21 changed files with 342 additions and 163 deletions
+1 -26
View File
@@ -4,17 +4,14 @@ import { useTranslations } from 'next-intl';
import { useCalendarStore, CalendarViewMode } from '@/stores/calendar-store';
import { useSettingsStore } from '@/stores/settings-store';
import { usePolicyStore } from '@/stores/policy-store';
import { SettingsSection, SettingItem, Select, RadioGroup, ToggleSwitch } from './settings-section';
import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section';
export function CalendarSettings() {
const t = useTranslations('calendar.settings');
const tViews = useTranslations('calendar.views');
const tDays = useTranslations('calendar.days');
const { viewMode, setViewMode } = useCalendarStore();
const {
timeFormat,
firstDayOfWeek,
showTimeInMonthView,
showWeekNumbers,
enableCalendarTasks,
@@ -40,28 +37,6 @@ export function CalendarSettings() {
/>
</SettingItem>
<SettingItem label={t('week_starts_on')}>
<Select
value={firstDayOfWeek.toString()}
onChange={(value) => updateSetting('firstDayOfWeek', parseInt(value) as 0 | 1)}
options={[
{ value: '1', label: tDays('monday') },
{ value: '0', label: tDays('sunday') },
]}
/>
</SettingItem>
<SettingItem label={t('time_format')}>
<RadioGroup
value={timeFormat}
onChange={(value) => updateSetting('timeFormat', value as '12h' | '24h')}
options={[
{ value: '12h', label: t('time_format_12h') },
{ value: '24h', label: t('time_format_24h') },
]}
/>
</SettingItem>
<SettingItem
label={t('show_time_in_month_view')}
description={t('show_time_in_month_view_desc')}
+86 -3
View File
@@ -1,17 +1,100 @@
"use client";
import { useMemo } from 'react';
import { useTranslations } from 'next-intl';
import { LanguageSwitcher } from '@/components/ui/language-switcher';
import { SettingsSection, SettingItem } from './settings-section';
import { useLocaleStore } from '@/stores/locale-store';
import { useSettingsStore } from '@/stores/settings-store';
import type { DateFormat, TimeFormat, FirstDayOfWeek } from '@/stores/settings-store';
import { formatDate } from '@/lib/utils';
import { SettingsSection, SettingItem, Select, RadioGroup } from './settings-section';
export function LanguageSettings() {
const t = useTranslations('settings.appearance');
const t = useTranslations('settings.language_region');
const tDays = useTranslations('calendar.days');
const { dateFormat, timeFormat, firstDayOfWeek, updateSetting } = useSettingsStore();
// Subscribe to locale changes so the preview re-renders on language switch
// (formatDate reads it via getState() and would otherwise stay stale).
const locale = useLocaleStore((s) => s.locale);
const preview = useMemo(() => {
// Build sample timestamps for each bucket so users see what their pick
// will look like in practice. Use offsets relative to "now" so the
// bucketing is stable even though the wall-clock keeps moving.
void locale; void dateFormat; void timeFormat;
const now = new Date();
const today = new Date(now);
today.setHours(15, 31, 0, 0);
const thisWeek = new Date(now);
thisWeek.setDate(now.getDate() - 2);
thisWeek.setHours(15, 31, 0, 0);
const older = new Date(now);
older.setMonth(now.getMonth() - 2);
older.setHours(15, 31, 0, 0);
return {
today: formatDate(today),
thisWeek: formatDate(thisWeek),
older: formatDate(older),
};
}, [locale, dateFormat, timeFormat]);
return (
<SettingsSection title={t('language.label')} description={t('language.description')}>
<SettingsSection title={t('title')} description={t('description')}>
<SettingItem label={t('language.label')} description={t('language.description')}>
<LanguageSwitcher />
</SettingItem>
<SettingItem label={t('date_format.label')} description={t('date_format.description')}>
<div className="flex flex-col items-end gap-2">
<Select
value={dateFormat}
onChange={(value) => updateSetting('dateFormat', value as DateFormat)}
options={[
{ value: 'smart', label: t('date_format.smart') },
{ value: 'relative', label: t('date_format.relative') },
{ value: 'full', label: t('date_format.full') },
]}
/>
<div className="text-xs text-muted-foreground text-right space-y-0.5 font-mono">
<div>
<span className="opacity-70">{t('date_format.preview_today')} </span>
<span className="text-foreground/90">{preview.today}</span>
</div>
<div>
<span className="opacity-70">{t('date_format.preview_this_week')} </span>
<span className="text-foreground/90">{preview.thisWeek}</span>
</div>
<div>
<span className="opacity-70">{t('date_format.preview_older')} </span>
<span className="text-foreground/90">{preview.older}</span>
</div>
</div>
</div>
</SettingItem>
<SettingItem label={t('time_format.label')} description={t('time_format.description')}>
<RadioGroup
value={timeFormat}
onChange={(value) => updateSetting('timeFormat', value as TimeFormat)}
options={[
{ value: '12h', label: t('time_format.12h') },
{ value: '24h', label: t('time_format.24h') },
]}
/>
</SettingItem>
<SettingItem label={t('first_day.label')} description={t('first_day.description')}>
<Select
value={firstDayOfWeek.toString()}
onChange={(value) => updateSetting('firstDayOfWeek', parseInt(value) as FirstDayOfWeek)}
options={[
{ value: '1', label: tDays('monday') },
{ value: '0', label: tDays('sunday') },
]}
/>
</SettingItem>
</SettingsSection>
);
}
+76 -12
View File
@@ -3,6 +3,8 @@ import { twMerge } from "tailwind-merge";
import { Mailbox, UNIFIED_MAILBOX_IDS } from "./jmap/types";
import type { UnifiedMailboxRole } from "./jmap/types";
import { debug } from "./debug";
import { useLocaleStore } from "@/stores/locale-store";
import { useSettingsStore } from "@/stores/settings-store";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@@ -40,24 +42,86 @@ export function generateUUID(): string {
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}
/**
* Formats a received-at date for the email list. The output style is
* controlled by the `dateFormat` user setting:
*
* - `smart` (default) — locale-aware, age-bucketed:
* today → time only ("15:31" or "3:31 PM")
* last 7 days → short weekday+time ("Fr 15:31", "Fri 3:31 PM")
* older → full locale date ("28.04.2026", "04/28/2026")
* - `relative` — legacy en-US relative format ("1h ago", "2d ago").
* - `full` — always the full locale date+time.
*
* Both the locale (from the language picker) and 12h/24h preference are
* read via `getState()` so this stays SSR-safe.
*/
export function formatDate(date: Date | string): string {
const d = typeof date === "string" ? new Date(date) : date;
const now = new Date();
const diff = now.getTime() - d.getTime();
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
const localeRaw = useLocaleStore.getState().locale;
const locale = localeRaw && localeRaw.length > 0 ? localeRaw : "en";
// `en` alone resolves to en-US in Intl; everything else uses the language
// subtag as-is and lets the runtime pick a sensible default region.
const intlLocale = locale === "en" ? "en-US" : locale;
const { dateFormat, timeFormat } = useSettingsStore.getState();
const hour12 = timeFormat === "12h";
if (minutes < 1) return "Just now";
if (minutes < 60) return `${minutes}m ago`;
if (hours < 24) return `${hours}h ago`;
if (days < 7) return `${days}d ago`;
if (dateFormat === "relative") {
const diff = now.getTime() - d.getTime();
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
if (minutes < 1) return "Just now";
if (minutes < 60) return `${minutes}m ago`;
if (hours < 24) return `${hours}h ago`;
if (days < 7) return `${days}d ago`;
return d.toLocaleDateString(intlLocale, {
month: "short",
day: "numeric",
year: d.getFullYear() !== now.getFullYear() ? "numeric" : undefined,
});
}
return d.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: d.getFullYear() !== now.getFullYear() ? "numeric" : undefined,
if (dateFormat === "full") {
return d.toLocaleString(intlLocale, {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
hour12,
});
}
// 'smart' (default)
const timeStr = d.toLocaleTimeString(intlLocale, {
hour: "2-digit",
minute: "2-digit",
hour12,
});
const isSameDay =
d.getFullYear() === now.getFullYear() &&
d.getMonth() === now.getMonth() &&
d.getDate() === now.getDate();
if (isSameDay) return timeStr;
const daysAgo = Math.floor((now.getTime() - d.getTime()) / 86400000);
if (daysAgo < 7) {
// German Intl outputs "Fr." with a trailing dot for `weekday: 'short'`;
// strip it so the result reads cleanly next to the time.
const weekday = d
.toLocaleDateString(intlLocale, { weekday: "short" })
.replace(/\.$/, "");
return `${weekday} ${timeStr}`;
}
return d.toLocaleDateString(intlLocale, {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
}
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "Pokračovat v úpravách",
"tabs": {
"appearance": "Vzhled",
"language": "Jazyk a region",
"language": "Jazyk, region a čas",
"email": "Chování e-mailu",
"composer": "Psaní zpráv",
"privacy": "Soukromí a bezpečnost",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "Jazyk a region",
"description": "Nakonfigurujte jazykové a místní předvolby",
"title": "Jazyk, region a čas",
"description": "Jazyk, formát data, formát času a další regionální předvolby",
"language": {
"label": "Jazyk",
"description": "Vyberte preferovaný jazyk",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "Formát data",
"description": "Jak se mají zobrazovat data",
"regional": "Místní",
"iso": "ISO 8601",
"custom": "Vlastní"
"description": "Jak se zobrazují data v seznamu e-mailů",
"smart": "Chytrý (regionál)",
"relative": "Relativní (před 1 h, před 2 d)",
"full": "Vždy úplné datum",
"preview_today": "Dnes:",
"preview_this_week": "Tento týden:",
"preview_older": "Starší:"
},
"time_format": {
"label": "Formát času",
+10 -7
View File
@@ -755,7 +755,7 @@
"search_no_results": "Ingen match i indstillinger",
"tabs": {
"appearance": "Udseende",
"language": "Sprog & region",
"language": "Sprog, region & tid",
"email": "E-mail-adfærd",
"composer": "Komponist",
"privacy": "Privatliv & sikkerhed",
@@ -935,8 +935,8 @@
}
},
"language_region": {
"title": "Sprog & region",
"description": "Konfigurér sprog og regionale præferencer",
"title": "Sprog, region & tid",
"description": "Sprog, datoformat, tidsformat og andre regionale indstillinger",
"language": {
"label": "Sprog",
"description": "Vælg dit foretrukne sprog",
@@ -945,10 +945,13 @@
},
"date_format": {
"label": "Datoformat",
"description": "Hvordan datoer skal vises",
"regional": "Regionalt",
"iso": "ISO 8601",
"custom": "Brugerdefineret"
"description": "Sådan vises datoer på e-mail-listen",
"smart": "Smart (regionalt)",
"relative": "Relativ (for 1 t siden, for 2 d siden)",
"full": "Altid fuld dato",
"preview_today": "I dag:",
"preview_this_week": "Denne uge:",
"preview_older": "Ældre:"
},
"time_format": {
"label": "Tidsformat",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "Weiter bearbeiten",
"tabs": {
"appearance": "Darstellung",
"language": "Sprache & Region",
"language": "Sprache, Region & Zeit",
"email": "E-Mail-Verhalten",
"composer": "Editor",
"privacy": "Datenschutz & Sicherheit",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "Sprache & Region",
"description": "Konfigurieren Sie Sprach- und Regionaleinstellungen",
"title": "Sprache, Region & Zeit",
"description": "Sprache, Datums- und Zeitformat sowie weitere regionale Einstellungen",
"language": {
"label": "Sprache",
"description": "Wählen Sie Ihre bevorzugte Sprache",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "Datumsformat",
"description": "Wie Daten angezeigt werden sollen",
"regional": "Regional",
"iso": "ISO 8601",
"custom": "Benutzerdefiniert"
"description": "Wie Daten in der E-Mail-Liste angezeigt werden",
"smart": "Intelligent (gebietsschemaabhängig)",
"relative": "Relativ (vor 1 Std., vor 2 Tagen)",
"full": "Immer vollständiges Datum",
"preview_today": "Heute:",
"preview_this_week": "Diese Woche:",
"preview_older": "Älter:"
},
"time_format": {
"label": "Zeitformat",
+10 -7
View File
@@ -755,7 +755,7 @@
"search_no_results": "No matching settings",
"tabs": {
"appearance": "Appearance",
"language": "Language & Region",
"language": "Language, Region & Time",
"email": "Email Behavior",
"composer": "Composer",
"privacy": "Privacy & Security",
@@ -935,8 +935,8 @@
}
},
"language_region": {
"title": "Language & Region",
"description": "Configure language and regional preferences",
"title": "Language, Region & Time",
"description": "Language, date format, time format, and other regional preferences",
"language": {
"label": "Language",
"description": "Choose your preferred language",
@@ -945,10 +945,13 @@
},
"date_format": {
"label": "Date Format",
"description": "How dates should be displayed",
"regional": "Regional",
"iso": "ISO 8601",
"custom": "Custom"
"description": "How dates are shown in the email list",
"smart": "Smart (locale-aware)",
"relative": "Relative (1h ago, 2d ago)",
"full": "Always full date",
"preview_today": "Today:",
"preview_this_week": "This week:",
"preview_older": "Older:"
},
"time_format": {
"label": "Time Format",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "Seguir editando",
"tabs": {
"appearance": "Apariencia",
"language": "Idioma y Región",
"language": "Idioma, Región y Hora",
"email": "Comportamiento del Correo",
"composer": "Editor",
"privacy": "Privacidad y Seguridad",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "Idioma y Región",
"description": "Configure las preferencias de idioma y región",
"title": "Idioma, Región y Hora",
"description": "Idioma, formato de fecha, formato de hora y otras preferencias regionales",
"language": {
"label": "Idioma",
"description": "Elija su idioma preferido",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "Formato de Fecha",
"description": "Cómo se deben mostrar las fechas",
"regional": "Regional",
"iso": "ISO 8601",
"custom": "Personalizado"
"description": "Cómo se muestran las fechas en la lista de correos",
"smart": "Inteligente (según región)",
"relative": "Relativo (hace 1 h, hace 2 d)",
"full": "Fecha completa siempre",
"preview_today": "Hoy:",
"preview_this_week": "Esta semana:",
"preview_older": "Más antiguo:"
},
"time_format": {
"label": "Formato de Hora",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "Continuer l'édition",
"tabs": {
"appearance": "Apparence",
"language": "Langue et région",
"language": "Langue, région et heure",
"email": "Comportement email",
"composer": "Compositeur",
"privacy": "Confidentialité et sécurité",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "Langue et région",
"description": "Configurez vos préférences linguistiques et régionales",
"title": "Langue, région et heure",
"description": "Langue, format de date, format d'heure et autres préférences régionales",
"language": {
"label": "Langue",
"description": "Choisissez votre langue préférée",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "Format de date",
"description": "Comment les dates doivent être affichées",
"regional": "Régional",
"iso": "ISO 8601",
"custom": "Personnalisé"
"description": "Comment les dates apparaissent dans la liste des e-mails",
"smart": "Intelligent (adapté à la région)",
"relative": "Relatif (il y a 1 h, il y a 2 j)",
"full": "Toujours la date complète",
"preview_today": "Aujourd'hui :",
"preview_this_week": "Cette semaine :",
"preview_older": "Plus ancien :"
},
"time_format": {
"label": "Format d'heure",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "Continua a modificare",
"tabs": {
"appearance": "Aspetto",
"language": "Lingua e regione",
"language": "Lingua, regione e ora",
"email": "Comportamento email",
"composer": "Editor",
"privacy": "Privacy e sicurezza",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "Lingua e regione",
"description": "Configura le preferenze di lingua e regionali",
"title": "Lingua, regione e ora",
"description": "Lingua, formato data, formato ora e altre preferenze regionali",
"language": {
"label": "Lingua",
"description": "Scegli la tua lingua preferita",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "Formato data",
"description": "Come devono essere visualizzate le date",
"regional": "Regionale",
"iso": "ISO 8601",
"custom": "Personalizzato"
"description": "Come vengono visualizzate le date nell'elenco delle e-mail",
"smart": "Intelligente (in base alla regione)",
"relative": "Relativo (1 h fa, 2 g fa)",
"full": "Sempre data completa",
"preview_today": "Oggi:",
"preview_this_week": "Questa settimana:",
"preview_older": "Più vecchio:"
},
"time_format": {
"label": "Formato ora",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "編集を続ける",
"tabs": {
"appearance": "外観",
"language": "言語地域",
"language": "言語地域、時刻",
"email": "メール動作",
"composer": "作成",
"privacy": "プライバシーとセキュリティ",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "言語地域",
"description": "言語地域設定を構成",
"title": "言語地域、時刻",
"description": "言語、日付形式、時刻形式、その他の地域設定",
"language": {
"label": "言語",
"description": "お好みの言語を選択",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "日付形式",
"description": "日付の表示形式",
"regional": "地域設定",
"iso": "ISO 8601",
"custom": "カスタム"
"description": "メール一覧での日付の表示方法",
"smart": "スマート(地域に合わせる)",
"relative": "相対表示(1時間前、2日前)",
"full": "常に完全な日付",
"preview_today": "今日:",
"preview_this_week": "今週:",
"preview_older": "それ以前:"
},
"time_format": {
"label": "時刻形式",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "계속 수정하기",
"tabs": {
"appearance": "화면 설정",
"language": "언어 지역",
"language": "언어, 지역 및 시간",
"email": "메일 동작",
"composer": "메일 쓰기",
"privacy": "개인정보 및 보안",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "언어 지역",
"description": "언어와 지역 형식을 설정해 주세요",
"title": "언어, 지역 및 시간",
"description": "언어, 날짜 형식, 시간 형식 및 기타 지역 환경설정",
"language": {
"label": "언어",
"description": "사용할 언어를 선택해 주세요",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "날짜 형식",
"description": "날짜가 표시되는 방식을 설정해요",
"regional": "지역 설정",
"iso": "ISO 8601",
"custom": "사용자 지정"
"description": "이메일 목록에 날짜가 표시되는 방식",
"smart": "스마트 (지역에 맞춤)",
"relative": "상대 시간 (1시간 전, 2일 전)",
"full": "항상 전체 날짜",
"preview_today": "오늘:",
"preview_this_week": "이번 주:",
"preview_older": "이전:"
},
"time_format": {
"label": "시간 형식",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "Turpināt rediģēšanu",
"tabs": {
"appearance": "Izskats",
"language": "Valoda un reģions",
"language": "Valoda, reģions un laiks",
"email": "Pasta darbība",
"composer": "Redaktors",
"privacy": "Privātums un drošība",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "Valoda un reģions",
"description": "Iestatiet valodas un reģionālās preferences",
"title": "Valoda, reģions un laiks",
"description": "Valoda, datuma formāts, laika formāts un citas reģionālās preferences",
"language": {
"label": "Valoda",
"description": "Izvēlieties vēlamo valodu",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "Datuma formāts",
"description": "Kā attēlot datumus",
"regional": "Reģionālais",
"iso": "ISO 8601",
"custom": "Pielāgots"
"description": "Kā datumi tiek attēloti e-pasta sarakstā",
"smart": "Gudrs (atbilstoši reģionam)",
"relative": "Relatīvs (pirms 1 st., pirms 2 d.)",
"full": "Vienmēr pilns datums",
"preview_today": "Šodien:",
"preview_this_week": "Šajā nedēļā:",
"preview_older": "Vecāks:"
},
"time_format": {
"label": "Laika formāts",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "Doorgaan met bewerken",
"tabs": {
"appearance": "Uiterlijk",
"language": "Taal & Regio",
"language": "Taal, Regio & Tijd",
"email": "E-mailgedrag",
"composer": "Opstellen",
"privacy": "Privacy & Beveiliging",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "Taal & Regio",
"description": "Configureer taal- en regiovoorkeuren",
"title": "Taal, Regio & Tijd",
"description": "Taal, datumnotatie, tijdnotatie en andere regionale voorkeuren",
"language": {
"label": "Taal",
"description": "Kies je voorkeurstaal",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "Datumnotatie",
"description": "Hoe datums moeten worden weergegeven",
"regional": "Regionaal",
"iso": "ISO 8601",
"custom": "Aangepast"
"description": "Hoe datums worden weergegeven in de e-maillijst",
"smart": "Slim (regionaal)",
"relative": "Relatief (1 u geleden, 2 d geleden)",
"full": "Altijd volledige datum",
"preview_today": "Vandaag:",
"preview_this_week": "Deze week:",
"preview_older": "Ouder:"
},
"time_format": {
"label": "Tijdnotatie",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "Kontynuuj edycję",
"tabs": {
"appearance": "Wygląd",
"language": "Język i region",
"language": "Język, region i czas",
"email": "Zachowanie poczty e-mail",
"composer": "Redagowanie",
"privacy": "Prywatność i bezpieczeństwo",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "Język i region",
"description": "Skonfiguruj preferencje językowe i regionalne",
"title": "Język, region i czas",
"description": "Język, format daty, format godziny i inne preferencje regionalne",
"language": {
"label": "Język",
"description": "Wybierz preferowany język",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "Format daty",
"description": "Jak mają być wyświetlane daty",
"regional": "Regionalny",
"iso": "ISO 8601",
"custom": "Niestandardowy"
"description": "Jak daty są wyświetlane na liście e-maili",
"smart": "Inteligentny (regionalny)",
"relative": "Względny (1 g temu, 2 d temu)",
"full": "Zawsze pełna data",
"preview_today": "Dzisiaj:",
"preview_this_week": "W tym tygodniu:",
"preview_older": "Starsze:"
},
"time_format": {
"label": "Format czasu",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "Continuar editando",
"tabs": {
"appearance": "Aparência",
"language": "Idioma e Região",
"language": "Idioma, Região e Hora",
"email": "Comportamento de E-mail",
"composer": "Editor",
"privacy": "Privacidade e Segurança",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "Idioma e Região",
"description": "Configure preferências de idioma e região",
"title": "Idioma, Região e Hora",
"description": "Idioma, formato de data, formato de hora e outras preferências regionais",
"language": {
"label": "Idioma",
"description": "Escolha seu idioma preferido",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "Formato de Data",
"description": "Como as datas devem ser exibidas",
"regional": "Regional",
"iso": "ISO 8601",
"custom": "Personalizado"
"description": "Como as datas são mostradas na lista de e-mails",
"smart": "Inteligente (regional)",
"relative": "Relativo (há 1 h, há 2 d)",
"full": "Sempre data completa",
"preview_today": "Hoje:",
"preview_this_week": "Esta semana:",
"preview_older": "Mais antigo:"
},
"time_format": {
"label": "Formato de Hora",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "Продолжить редактирование",
"tabs": {
"appearance": "Внешний вид",
"language": "Язык и регион",
"language": "Язык, регион и время",
"email": "Поведение почты",
"composer": "Редактор",
"privacy": "Конфиденциальность и безопасность",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "Язык и регион",
"description": "Настройте языковые и региональные предпочтения",
"title": "Язык, регион и время",
"description": "Язык, формат даты, формат времени и другие региональные настройки",
"language": {
"label": "Язык",
"description": "Выберите предпочтительный язык",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "Формат даты",
"description": "Как отображать даты",
"regional": "Региональный",
"iso": "ISO 8601",
"custom": "Пользовательский"
"description": "Как отображаются даты в списке писем",
"smart": "Умный (по региону)",
"relative": "Относительный (1 ч назад, 2 д назад)",
"full": "Всегда полная дата",
"preview_today": "Сегодня:",
"preview_this_week": "На этой неделе:",
"preview_older": "Старее:"
},
"time_format": {
"label": "Формат времени",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "Düzenlemeye devam et",
"tabs": {
"appearance": "Görünüm",
"language": "Dil ve Bölge",
"language": "Dil, Bölge ve Saat",
"email": "E-posta Davranışı",
"composer": "Yazıcı",
"privacy": "Gizlilik ve Güvenlik",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "Dil ve Bölge",
"description": "Dil ve bölgesel tercihleri yapılandırın",
"title": "Dil, Bölge ve Saat",
"description": "Dil, tarih biçimi, saat biçimi ve diğer bölgesel tercihler",
"language": {
"label": "Dil",
"description": "Tercih ettiğiniz dili seçin",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "Tarih Biçimi",
"description": "Tarihlerin nasıl görüntüleneceği",
"regional": "Bölgesel",
"iso": "ISO 8601",
"custom": "Özel"
"description": "Tarihlerin e-posta listesinde nasıl gösterileceği",
"smart": "Akıllı (bölgeye duyarlı)",
"relative": "Göreceli (1 sa önce, 2 g önce)",
"full": "Her zaman tam tarih",
"preview_today": "Bugün:",
"preview_this_week": "Bu hafta:",
"preview_older": "Daha eski:"
},
"time_format": {
"label": "Saat Biçimi",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "Продовжуйте редагувати",
"tabs": {
"appearance": "Зовнішній вигляд",
"language": "Мова та регіон",
"language": "Мова, регіон і час",
"email": "Поведінка електронної пошти",
"composer": "Композитор",
"privacy": "Конфіденційність і безпека",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "Мова та регіон",
"description": "Налаштуйте мовні та регіональні параметри",
"title": "Мова, регіон і час",
"description": "Мова, формат дати, формат часу та інші регіональні налаштування",
"language": {
"label": "Мова",
"description": "Виберіть бажану мову",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "Формат дати",
"description": "Як мають відображатися дати",
"regional": "Регіональний",
"iso": "ISO 8601",
"custom": "Довільний"
"description": "Як відображаються дати у списку листів",
"smart": "Розумний (за регіоном)",
"relative": "Відносний (1 год тому, 2 дн тому)",
"full": "Завжди повна дата",
"preview_today": "Сьогодні:",
"preview_this_week": "Цього тижня:",
"preview_older": "Старіше:"
},
"time_format": {
"label": "Формат часу",
+10 -7
View File
@@ -752,7 +752,7 @@
"keep_editing": "继续编辑",
"tabs": {
"appearance": "外观",
"language": "语言地区",
"language": "语言地区与时间",
"email": "邮件行为",
"composer": "邮件撰写",
"privacy": "隐私与安全",
@@ -932,8 +932,8 @@
}
},
"language_region": {
"title": "语言地区",
"description": "配置语言和区域首选项",
"title": "语言地区与时间",
"description": "语言、日期格式、时间格式以及其他区域设置",
"language": {
"label": "语言",
"description": "选择您的首选语言",
@@ -942,10 +942,13 @@
},
"date_format": {
"label": "日期格式",
"description": "日期应如何显示",
"regional": "区域格式",
"iso": "ISO 8601",
"custom": "自定义"
"description": "电子邮件列表中日期的显示方式",
"smart": "智能(按地区)",
"relative": "相对时间(1 小时前、2 天前)",
"full": "始终完整日期",
"preview_today": "今天:",
"preview_this_week": "本周:",
"preview_older": "更早:"
},
"time_format": {
"label": "时间格式",
+9 -3
View File
@@ -31,7 +31,7 @@ export type ListDensity = Density;
export type DeleteAction = 'trash' | 'trash-and-read' | 'permanent';
export type ReplyMode = 'reply' | 'replyAll';
export type SignaturePosition = 'above_quote' | 'below_quote';
export type DateFormat = 'regional' | 'iso' | 'custom';
export type DateFormat = 'smart' | 'relative' | 'full';
export type TimeFormat = '12h' | '24h';
export type FirstDayOfWeek = 0 | 1; // 0 = Sunday, 1 = Monday
export type ExternalContentPolicy = 'ask' | 'block' | 'allow';
@@ -300,7 +300,7 @@ const DEFAULT_SETTINGS = {
animationsEnabled: true,
// Language & Region
dateFormat: 'regional' as DateFormat,
dateFormat: 'smart' as DateFormat,
timeFormat: '24h' as TimeFormat,
firstDayOfWeek: 1 as FirstDayOfWeek, // Monday
@@ -760,7 +760,7 @@ export const useSettingsStore = create<SettingsState>()(
}),
{
name: 'settings-storage',
version: 3,
version: 4,
migrate: (persisted, version) => {
const state = persisted as Record<string, unknown>;
if (version < 2 && state.listDensity) {
@@ -771,6 +771,12 @@ export const useSettingsStore = create<SettingsState>()(
state.protocolOpenMode = state.protocolMailtoOpenMode;
}
delete state.protocolMailtoOpenMode;
// v4: `dateFormat` was repurposed from 'regional'|'iso'|'custom' to
// 'smart'|'relative'|'full'. The old setting was never read anywhere,
// so every persisted value maps to the new default.
if (version < 4) {
state.dateFormat = 'smart';
}
return state as unknown as SettingsState;
},
onRehydrateStorage: () => {