diff --git a/apps/journal/app/components/ClientDate.tsx b/apps/journal/app/components/ClientDate.tsx index e2fcf06..0bbc775 100644 --- a/apps/journal/app/components/ClientDate.tsx +++ b/apps/journal/app/components/ClientDate.tsx @@ -1,11 +1,16 @@ import { useState, useEffect } from "react"; +import { useLocale } from "./LocaleContext"; /** - * Renders a date only on the client to avoid SSR hydration mismatches. - * Server renders empty, client fills in with user's locale. + * Renders a date using the server-detected locale for the initial render + * (matching SSR output), then updates to the browser's native locale + * after hydration. */ export function ClientDate({ iso }: { iso: string }) { - const [formatted, setFormatted] = useState(new Date(iso).toLocaleDateString("en-US")); + const locale = useLocale(); + const [formatted, setFormatted] = useState( + new Date(iso).toLocaleDateString(locale), + ); useEffect(() => { setFormatted(new Date(iso).toLocaleDateString()); diff --git a/apps/journal/app/components/LocaleContext.tsx b/apps/journal/app/components/LocaleContext.tsx new file mode 100644 index 0000000..15902c8 --- /dev/null +++ b/apps/journal/app/components/LocaleContext.tsx @@ -0,0 +1,17 @@ +import { createContext, useContext } from "react"; + +const LocaleContext = createContext("en"); + +export function LocaleProvider({ + locale, + children, +}: { + locale: string; + children: React.ReactNode; +}) { + return {children}; +} + +export function useLocale() { + return useContext(LocaleContext); +} diff --git a/apps/journal/app/root.tsx b/apps/journal/app/root.tsx index c041ae7..be4a5f4 100644 --- a/apps/journal/app/root.tsx +++ b/apps/journal/app/root.tsx @@ -4,7 +4,9 @@ import type { LinksFunction } from "react-router"; import type { Route } from "./+types/root"; import * as Sentry from "@sentry/react"; import { useTranslation } from "react-i18next"; +import { detectLocale } from "@trails-cool/i18n"; import { getSessionUser } from "~/lib/auth.server"; +import { LocaleProvider } from "~/components/LocaleContext"; import stylesheet from "@trails-cool/ui/styles.css?url"; export const links: LinksFunction = () => [{ rel: "stylesheet", href: stylesheet }]; @@ -33,7 +35,8 @@ export function Layout({ children }: { children: React.ReactNode }) { export async function loader({ request }: Route.LoaderArgs) { const user = await getSessionUser(request); - return { user: user ? { id: user.id, username: user.username } : null }; + const locale = detectLocale(request); + return { user: user ? { id: user.id, username: user.username } : null, locale }; } function NavBar({ user }: { user: { id: string; username: string } | null }) { @@ -110,6 +113,7 @@ function NavBar({ user }: { user: { id: string; username: string } | null }) { export default function App({ loaderData }: Route.ComponentProps) { const user = loaderData?.user; + const locale = loaderData?.locale ?? "en"; useEffect(() => { if (user) { Sentry.setUser({ id: user.id, username: user.username }); @@ -119,10 +123,10 @@ export default function App({ loaderData }: Route.ComponentProps) { }, [user]); return ( - <> + - + ); } diff --git a/apps/journal/app/routes/settings.tsx b/apps/journal/app/routes/settings.tsx index fda39b5..11b75b7 100644 --- a/apps/journal/app/routes/settings.tsx +++ b/apps/journal/app/routes/settings.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback } from "react"; import { data, redirect, useFetcher } from "react-router"; import { useTranslation } from "react-i18next"; +import { ClientDate } from "~/components/ClientDate"; import { eq } from "drizzle-orm"; import type { Route } from "./+types/settings"; import { getSessionUser } from "~/lib/auth.server"; @@ -217,9 +218,8 @@ export default function Settings({ loaderData }: Route.ComponentProps) { {transportLabel(passkey.transports, t)}

- {t("settings.security.addedOn", { - date: new Date(passkey.createdAt).toLocaleDateString(), - })} + {t("settings.security.addedOn", { date: "" })} +