diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1297703..5c89e67 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,11 @@ jobs: id: meta run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + # QEMU lets the amd64 runner emulate arm64 so the images below build for + # both platforms (Intel + Apple Silicon self-hosters). + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Buildx uses: docker/setup-buildx-action@v3 @@ -49,6 +54,7 @@ jobs: with: context: ./backend push: true + platforms: linux/amd64,linux/arm64 tags: | ${{ env.REGISTRY_NAMESPACE }}/temetro-backend:${{ steps.meta.outputs.version }} ${{ env.REGISTRY_NAMESPACE }}/temetro-backend:latest @@ -58,6 +64,7 @@ jobs: with: context: ./frontend push: true + platforms: linux/amd64,linux/arm64 tags: | ${{ env.REGISTRY_NAMESPACE }}/temetro-frontend:${{ steps.meta.outputs.version }} ${{ env.REGISTRY_NAMESPACE }}/temetro-frontend:latest diff --git a/CLAUDE.md b/CLAUDE.md index 0f4b56f..0c8fca0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,6 +80,11 @@ accurate (e.g. a new backend route needs an `content/docs/api/*.mdx` entry; a UI in the matching guide; status changes belong in the roadmap). Commit docs changes inside that repo, separately from this one. +**Every release must also get a dated entry in the docs changelog** +(`content/docs/changelog.mdx`, newest first) — not just the monorepo `CHANGELOG.md`. When you cut a +version (see "Always release after pushing"), add a matching, user-facing section to that page in the +same session so `../temetro/docs` never falls behind the shipped version. + ## Running the stack From `backend/`: ensure a `.env` exists (`cp .env.example .env`, then set `BETTER_AUTH_SECRET` via diff --git a/frontend/components/settings/settings-preferences.tsx b/frontend/components/settings/settings-preferences.tsx index 6aeb553..e61fa61 100644 --- a/frontend/components/settings/settings-preferences.tsx +++ b/frontend/components/settings/settings-preferences.tsx @@ -6,7 +6,15 @@ import { useTranslation } from "react-i18next"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; +import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { Input } from "@/components/ui/input"; +import { + Select, + SelectItem, + SelectPopup, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { DeleteAccountDialog } from "@/components/settings/delete-account-dialog"; import { CopyField, @@ -68,6 +76,10 @@ export function ProfilePanel() { const [baselineName, setBaselineName] = useState(""); const [saving, setSaving] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false); + // A language picked in the Select but not yet applied — its presence opens the + // confirmation dialog. The Select stays bound to `activeLang`, so cancelling + // (clearing this) automatically reverts the shown selection. + const [pendingLang, setPendingLang] = useState(null); useEffect(() => { let cancelled = false; @@ -222,21 +234,23 @@ export function ProfilePanel() { > {t("settings.profile.language.label")} -
- {supportedLanguages.map((lng) => ( - - ))} -
+
@@ -316,6 +330,27 @@ export function ProfilePanel() { + { + if (pendingLang) void i18n.changeLanguage(pendingLang); + }} + onOpenChange={(open) => { + if (!open) setPendingLang(null); + }} + open={pendingLang !== null} + title={t("settings.profile.language.confirmTitle")} + variant="default" + /> + {dirty ? (
diff --git a/frontend/lib/i18n/locales/en/translation.json b/frontend/lib/i18n/locales/en/translation.json index 5183cd1..1547836 100644 --- a/frontend/lib/i18n/locales/en/translation.json +++ b/frontend/lib/i18n/locales/en/translation.json @@ -1689,7 +1689,11 @@ "description": "The language temetro's interface is shown in on this device.", "label": "Display language", "en": "English", - "fr": "Français" + "fr": "Français", + "confirmTitle": "Change display language?", + "confirmBody": "Switch the interface to {{language}}? The app will reload its text in the new language.", + "confirmCta": "Change language", + "cancel": "Cancel" }, "patientNotifications": "Patient notifications", "patientNotificationsDescription": "Emails sent to patients about their records, results, and pending approvals", diff --git a/frontend/lib/i18n/locales/fr/translation.json b/frontend/lib/i18n/locales/fr/translation.json index 0e8cf1d..e4902f6 100644 --- a/frontend/lib/i18n/locales/fr/translation.json +++ b/frontend/lib/i18n/locales/fr/translation.json @@ -1689,7 +1689,11 @@ "description": "La langue dans laquelle l'interface de temetro s'affiche sur cet appareil.", "label": "Langue d'affichage", "en": "English", - "fr": "Français" + "fr": "Français", + "confirmTitle": "Changer la langue d'affichage ?", + "confirmBody": "Basculer l'interface en {{language}} ? L'application rechargera ses textes dans la nouvelle langue.", + "confirmCta": "Changer la langue", + "cancel": "Annuler" }, "patientNotifications": "Notifications patients", "patientNotificationsDescription": "E-mails envoyés aux patients à propos de leurs dossiers, résultats et approbations en attente",