chore: release v1.5.0 (#67)

* chore: release v1.4.0

* feat: timezone-aware locale formatting

- Add TIMEZONE_TO_REGION map for 50+ timezones worldwide
- Combine app language with timezone region (e.g., en + Europe/Berlin → en-DE)
- Fix times displaying in wrong timezone (treated as UTC instead of local)
- Add parseLocalDateTime() to handle ISO strings without UTC conversion
- Users now get regional formatting (24h time, local date format) regardless of app language
- Swedish user with en-SE locale now gets yyyy-mm-dd format and 24h time
- German user with en-DE locale gets dd.mm.yyyy format and 24h time
- Add missing i18n translation key 'lastSent'
- Update all getSystemLocale() calls to pass app language parameter

* chore: release v1.5.0

* fix: timezone-independent test for CI (use 14:00 instead of 22:00)

* fix: make timezone test independent of server timezone
This commit is contained in:
Daniel Volz
2026-01-23 21:42:57 +01:00
committed by GitHub
parent 0a4f8c5948
commit 8e2fd0a761
26 changed files with 1830 additions and 108 deletions
+6 -6
View File
@@ -10,7 +10,7 @@ import { useTranslation } from "react-i18next";
import type { Medication, Coverage, RefillEntry, StockThresholds } from "../types";
import { MedicationAvatar, Lightbox } from "../components";
import { getMedTotal, getPackageSize } from "../types";
import { formatNumber, generateICS } from "../utils";
import { formatNumber, generateICS, getExpiryClass, getSystemLocale } from "../utils";
import { getStockStatus } from "../utils/schedule";
// =============================================================================
@@ -214,8 +214,8 @@ export function MedDetailModal({
{selectedMed.expiryDate && (
<div className="med-detail-item">
<span className="med-detail-label">{t("modal.expiryDate")}</span>
<span className={`med-detail-value ${new Date(selectedMed.expiryDate) < new Date() ? "danger-text" : ""}`}>
{new Date(selectedMed.expiryDate).toLocaleDateString(i18n.language, {
<span className={`med-detail-value ${getExpiryClass(selectedMed.expiryDate, settings.expiryWarningDays)}`}>
{new Date(selectedMed.expiryDate).toLocaleDateString(getSystemLocale(i18n.language), {
day: "2-digit",
month: "short",
year: "numeric",
@@ -252,7 +252,7 @@ export function MedDetailModal({
</span>
<span className="med-schedule-time">
{t("modal.at")}{" "}
{new Date(blister.start).toLocaleTimeString(i18n.language, {
{new Date(blister.start).toLocaleTimeString(getSystemLocale(i18n.language), {
hour: "2-digit",
minute: "2-digit",
})}
@@ -304,13 +304,13 @@ export function MedDetailModal({
{refillHistory.map((entry) => (
<div key={entry.id} className="refill-history-item">
<span className="refill-date">
{new Date(entry.refillDate).toLocaleDateString(i18n.language, {
{new Date(entry.refillDate).toLocaleDateString(getSystemLocale(i18n.language), {
day: "2-digit",
month: "short",
year: "numeric",
})}
,{" "}
{new Date(entry.refillDate).toLocaleTimeString(i18n.language, {
{new Date(entry.refillDate).toLocaleTimeString(getSystemLocale(i18n.language), {
hour: "2-digit",
minute: "2-digit",
})}
+1 -1
View File
@@ -331,7 +331,7 @@ export function MobileEditModal({
{t("common.cancel")}
</button>
<button type="submit" disabled={saving || hasValidationErrors || (!formChanged && (formSaved || !!editingId))}>
{saving ? t("common.saving") : formSaved && !formChanged ? t("common.saved") : t("common.save")}
{formSaved && !formChanged ? t("common.saved") : t("common.save")}
</button>
</div>
</form>
+4 -3
View File
@@ -8,6 +8,7 @@ import { useTranslation } from "react-i18next";
import type { SharedScheduleData, ExpiredLinkData } from "../types";
import { getMedTotal } from "../types";
import { loadCollapsedDaysFromStorage } from "../utils/storage";
import { getSystemLocale } from "../utils/formatters";
import { MedicationAvatar } from "./MedicationAvatar";
export function SharedSchedule() {
@@ -281,8 +282,8 @@ export function SharedSchedule() {
usage: blister.usage,
isPast,
takenBy: med.takenBy || [],
timeStr: d.toLocaleTimeString(i18n.language, { hour: "2-digit", minute: "2-digit" }),
dateStr: d.toLocaleDateString(i18n.language, { weekday: "short", day: "2-digit", month: "short" })
timeStr: d.toLocaleTimeString(getSystemLocale(i18n.language), { hour: "2-digit", minute: "2-digit" }),
dateStr: d.toLocaleDateString(getSystemLocale(i18n.language), { weekday: "short", day: "2-digit", month: "short" })
});
}
});
@@ -418,7 +419,7 @@ export function SharedSchedule() {
<h2>{t("share.expired.title")}</h2>
<p className="expired-message">{t("share.expired.message", { takenBy: expiredData.takenBy })}</p>
<p className="expired-contact">{t("share.expired.contact", { username: expiredData.ownerUsername })}</p>
<p className="expired-date">{t("share.expired.expiredOn", { date: new Date(expiredData.expiredAt).toLocaleDateString(i18n.language) })}</p>
<p className="expired-date">{t("share.expired.expiredOn", { date: new Date(expiredData.expiredAt).toLocaleDateString(getSystemLocale(i18n.language)) })}</p>
</div>
</div>
);