diff --git a/CHANGELOG.md b/CHANGELOG.md index 4165ae9..71bdeb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,25 @@ for how releases are cut and published. ## [Unreleased] +## [0.3.0] — 2026-07-02 + +### Added +- **Three new interface languages** — Somali (`so`), Arabic (`ar`) and German + (`de`) join English and French, selectable in Settings → Profile → Language. + Each locale carries a full translation of the ~1,660 UI strings, with native + names shown in the selector (Soomaali, العربية, Deutsch). +- **Right-to-left (RTL) support** — selecting Arabic sets `dir="rtl"` on the + document (applied before first paint via an inline script, so no flash), flips + physical spacing/alignment to logical CSS utilities, mirrors directional icons, + and loads an Arabic-capable typeface (IBM Plex Sans Arabic) appended to the + font stack for per-character fallback. +- **Language roams across devices** — the chosen language is persisted to the + per-user `user_settings` preferences and re-applied on sign-in, with + localStorage remaining the offline source of truth. +- **`frontend/scripts/check-locales.mjs`** (+ `npm run check-locales`) — a parity + check that fails on missing/extra keys or `{{placeholder}}` mismatches across + locales and warns when Arabic count-keys lack the full CLDR plural forms. + ## [0.2.5] — 2026-07-01 ### Fixed diff --git a/backend/package.json b/backend/package.json index 6f5af17..019ade5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "temetro-backend", - "version": "0.2.5", + "version": "0.3.0", "private": true, "type": "module", "description": "temetro backend — Express + Postgres API with Better Auth (email/password, organizations) and org-scoped patient records.", diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 4cecfdd..078ad95 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -7,9 +7,11 @@ @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-sans); + /* Append the Arabic face so Arabic codepoints fall through per-character even + in LTR locales; Latin text still renders in Inter. See app/layout.tsx. */ + --font-sans: var(--font-sans), var(--font-arabic); --font-mono: var(--font-mono); - --font-heading: var(--font-heading); + --font-heading: var(--font-heading), var(--font-arabic); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 6b533a1..7e3e0e8 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -1,5 +1,5 @@ import type { Metadata } from "next"; -import { Geist_Mono, Inter } from "next/font/google"; +import { Geist_Mono, IBM_Plex_Sans_Arabic, Inter } from "next/font/google"; import "./globals.css"; import { cn } from "@/lib/utils"; import { ThemeProvider } from "@/components/theme-provider"; @@ -10,6 +10,27 @@ import { ToastProvider } from "@/components/ui/toast"; const inter = Inter({ subsets: ["latin"], variable: "--font-sans" }); const interHeading = Inter({ subsets: ["latin"], variable: "--font-heading" }); const geistMono = Geist_Mono({ subsets: ["latin"], variable: "--font-mono" }); +// Arabic-capable fallback: Inter has no Arabic glyphs, so we append this to the +// sans/heading stacks (see globals.css) for per-character fallback in every +// locale, and rely on it fully when dir="rtl". Not a variable font — pin weights. +const plexArabic = IBM_Plex_Sans_Arabic({ + subsets: ["arabic"], + weight: ["400", "500", "600", "700"], + variable: "--font-arabic", +}); + +// Runs before first paint: mirror lib/i18n/config.ts `dirFor` so an Arabic user +// gets dir="rtl" immediately instead of a flash of LTR. Detection order matches +// i18next-browser-languagedetector (localStorage key "i18nextLng", then the +// browser language). suppressHydrationWarning on ignores the attr diff. +const setInitialDir = ` +(function(){try{ + var l=localStorage.getItem("i18nextLng")||navigator.language||"en"; + var e=document.documentElement; + e.lang=l; + e.dir=l.indexOf("ar")===0?"rtl":"ltr"; +}catch(_){}})(); +`; export const metadata: Metadata = { title: "temetro — AI assistant for clinicians", @@ -33,17 +54,22 @@ export default function RootLayout({ inter.variable, interHeading.variable, geistMono.variable, + plexArabic.variable, "font-sans" )} > {/* suppressHydrationWarning: next-themes sets the theme class on - before hydration, and browser extensions (e.g. ColorZilla's - cz-shortcut-listen) mutate . Only ignores attribute diffs on - those elements, not their children. */} + before hydration, the dir script below sets lang/dir, and browser + extensions (e.g. ColorZilla's cz-shortcut-listen) mutate . Only + ignores attribute diffs on those elements, not their children. */} +