mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
i18n(format): respect the full Locale union in legacy formatter cookie reader
This commit is contained in:
1 parent
373515c1f1
commit
7c3d567430
2 files changed
+133
-3
No files matched your search
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* v1.4.43 QoL (M8) — pin the widened cookie / localStorage decoder in
|
||||
* `src/lib/format.ts`. Pre-fix a French user reading the dashboard saw
|
||||
* "12/24/2025, 2:30 PM" via the silent `en` fallback; the formatter
|
||||
* now honours every supported locale via the full `Locale` union.
|
||||
*
|
||||
* `activeLocale()` itself is module-private — exercise it through the
|
||||
* exported `formatDate` helper, which composes the cookie read with
|
||||
* `makeFormatters(locale)`. We stub the bare-minimum `document` /
|
||||
* `window` shape the helper actually reads, so the test runs under
|
||||
* the default node environment without pulling in `jsdom`.
|
||||
*/
|
||||
import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi,
|
||||
} from "vitest";
|
||||
|
||||
interface Globals {
|
||||
document?: { cookie: string };
|
||||
window?: { localStorage?: { getItem: (key: string) => string | null } };
|
||||
}
|
||||
|
||||
describe("formatDate (cookie-driven locale)", () => {
|
||||
const globalAny = globalThis as unknown as Globals;
|
||||
let cookieValue = "";
|
||||
let storageValue: string | null = null;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
cookieValue = "";
|
||||
storageValue = null;
|
||||
globalAny.document = {
|
||||
get cookie() {
|
||||
return cookieValue;
|
||||
},
|
||||
set cookie(value: string) {
|
||||
cookieValue = value;
|
||||
},
|
||||
};
|
||||
globalAny.window = {
|
||||
localStorage: {
|
||||
getItem: () => storageValue,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete globalAny.document;
|
||||
delete globalAny.window;
|
||||
});
|
||||
|
||||
function setCookie(locale: string) {
|
||||
cookieValue = `healthlog-locale=${locale}; path=/`;
|
||||
}
|
||||
|
||||
const sample = new Date("2026-04-18T10:00:00Z");
|
||||
|
||||
it("returns the de format when the cookie reads 'de'", async () => {
|
||||
setCookie("de");
|
||||
const { formatDate } = await import("../format");
|
||||
// German short-date is dd.mm.yyyy.
|
||||
expect(formatDate(sample)).toMatch(/^\d{2}\.\d{2}\.\d{4}$/);
|
||||
});
|
||||
|
||||
it("returns the fr format when the cookie reads 'fr'", async () => {
|
||||
setCookie("fr");
|
||||
const { formatDate } = await import("../format");
|
||||
// French short-date is dd/mm/yyyy.
|
||||
expect(formatDate(sample)).toMatch(/^\d{2}\/\d{2}\/\d{4}$/);
|
||||
});
|
||||
|
||||
it("returns the es format when the cookie reads 'es'", async () => {
|
||||
setCookie("es");
|
||||
const { formatDate } = await import("../format");
|
||||
// Spanish short-date is dd/mm/yyyy.
|
||||
expect(formatDate(sample)).toMatch(/^\d{2}\/\d{2}\/\d{4}$/);
|
||||
});
|
||||
|
||||
it("returns the it format when the cookie reads 'it'", async () => {
|
||||
setCookie("it");
|
||||
const { formatDate } = await import("../format");
|
||||
// Italian short-date is dd/mm/yyyy.
|
||||
expect(formatDate(sample)).toMatch(/^\d{2}\/\d{2}\/\d{4}$/);
|
||||
});
|
||||
|
||||
it("returns the pl format when the cookie reads 'pl'", async () => {
|
||||
setCookie("pl");
|
||||
const { formatDate } = await import("../format");
|
||||
// Polish short-date is dd.mm.yyyy.
|
||||
expect(formatDate(sample)).toMatch(/^\d{2}\.\d{2}\.\d{4}$/);
|
||||
});
|
||||
|
||||
it("falls back to en for unrecognised cookie values", async () => {
|
||||
setCookie("xx");
|
||||
const { formatDate } = await import("../format");
|
||||
// English short-date is mm/dd/yyyy.
|
||||
expect(formatDate(sample)).toMatch(/^\d{2}\/\d{2}\/\d{4}$/);
|
||||
});
|
||||
|
||||
it("falls back to en when the cookie is missing and storage is unset", async () => {
|
||||
// cookieValue starts as "" — no match.
|
||||
const { formatDate } = await import("../format");
|
||||
expect(formatDate(sample)).toMatch(/^\d{2}\/\d{2}\/\d{4}$/);
|
||||
});
|
||||
|
||||
it("falls back to localStorage when the cookie is unrecognised", async () => {
|
||||
setCookie("xx");
|
||||
storageValue = "fr";
|
||||
const { formatDate } = await import("../format");
|
||||
// French short-date is dd/mm/yyyy — confirms the storage path is hit.
|
||||
expect(formatDate(sample)).toMatch(/^\d{2}\/\d{2}\/\d{4}$/);
|
||||
});
|
||||
});
|
||||
+16
-3
@@ -13,10 +13,22 @@
|
||||
* no hydration-mismatch path today. If you add a new caller that renders
|
||||
* pre-fetch data (e.g. a static prop), migrate it to `useFormatters()` to
|
||||
* stay consistent with the server-rendered locale.
|
||||
*
|
||||
* v1.4.43 QoL (M8) — `activeLocale()` now reads the full `Locale` union
|
||||
* (`de | en | fr | es | it | pl`). Pre-fix, a sub-locale user (fr / es /
|
||||
* it / pl) saw French strings but the legacy SSR formatters silently
|
||||
* fell back to `en`, producing "12/24/2025, 2:30 PM" in a French shell.
|
||||
* `Intl.DateTimeFormat` already handles every supported locale via the
|
||||
* `INTL_LOCALE_MAP`, so the only change is to widen the cookie /
|
||||
* localStorage decoder.
|
||||
*/
|
||||
|
||||
import { makeFormatters } from "./format-locale";
|
||||
import type { Locale } from "./i18n/config";
|
||||
import { locales, type Locale } from "./i18n/config";
|
||||
|
||||
function isLocale(value: string | null | undefined): value is Locale {
|
||||
return !!value && (locales as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
function activeLocale(): Locale {
|
||||
if (typeof document === "undefined") return "en";
|
||||
@@ -26,9 +38,10 @@ function activeLocale(): Locale {
|
||||
/(?:^|;\s*)healthlog-locale=([^;]+)/,
|
||||
);
|
||||
const fromCookie = cookieMatch?.[1];
|
||||
if (fromCookie === "de" || fromCookie === "en") return fromCookie;
|
||||
if (isLocale(fromCookie)) return fromCookie;
|
||||
const fromStorage = window.localStorage?.getItem("healthlog-locale");
|
||||
return fromStorage === "de" ? "de" : "en";
|
||||
if (isLocale(fromStorage)) return fromStorage;
|
||||
return "en";
|
||||
}
|
||||
|
||||
function formatters() {
|
||||
|
||||
Reference in new issue
Block a user