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: "" })}
+