From 88a8210bf82542357ee4a8daaabd0617c77d6cdf Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Mon, 21 Sep 2026 19:45:46 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20i18n=20seam=20=E2=80=94=20typed=20t()?= =?UTF-8?q?=20+=20en=20dictionary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nav and header wired as proof. Full string extraction deferred until a second locale exists; CONTRIBUTING records the convention. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- CONTRIBUTING.md | 2 ++ ROADMAP.md | 2 +- apps/frontend/src/App.tsx | 29 +++++++++++++++-------------- apps/frontend/src/i18n/en.ts | 27 +++++++++++++++++++++++++++ apps/frontend/src/i18n/index.ts | 13 +++++++++++++ 5 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 apps/frontend/src/i18n/en.ts create mode 100644 apps/frontend/src/i18n/index.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 25b858c..25a48b0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,6 +31,8 @@ DASH_DEV=1 go run ./cmd/dash # API on :8080 - No auth, no YAML-as-source-of-truth, no external database. These are non-goals for v1. - Standard library before dependencies; existing helpers before new ones. +- User-visible strings go through `t("key")` from `src/i18n` — add the key to + `en.ts` rather than inlining literals in new UI. ## Before pushing diff --git a/ROADMAP.md b/ROADMAP.md index 01b22b7..8423a74 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -197,7 +197,7 @@ Beszel-style server monitoring, push-based. - [x] `README.md` hero: screenshots, `docker run` one-liner (demo GIF deferred — pngs suffice for v1.0.0) - [x] goreleaser → binaries; GHCR image on tag; semver + `CHANGELOG.md` - [x] `CONTRIBUTING.md`, issue/PR templates, code of conduct, security policy -- [ ] i18n scaffolding (en default) +- [x] i18n scaffolding (`src/i18n/` — typed `t()` + `en` dict; full string extraction deferred to first real locale) - [ ] Public repo flip + announcement assets **Exit:** a stranger can `docker run`, import their Homepage config, and have a working board in under two minutes. diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx index e3d40f0..77005f8 100644 --- a/apps/frontend/src/App.tsx +++ b/apps/frontend/src/App.tsx @@ -18,6 +18,7 @@ import { StatusPagesPage } from "@/pages/StatusPagesPage"; import { StatusPublicPage } from "@/pages/StatusPublicPage"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; +import { t, type Key } from "@/i18n"; import type { Item } from "@/types"; export default function App() { @@ -28,13 +29,13 @@ export default function App() { ); } -const NAV = [ - { href: "/", label: "Board", icon: LayoutGrid }, - { href: "/monitors", label: "Monitors", icon: Activity }, - { href: "/domains", label: "Domains", icon: Globe }, - { href: "/systems", label: "Systems", icon: Server }, - { href: "/incidents", label: "Incidents", icon: AlertTriangle }, - { href: "/status", label: "Status", icon: Megaphone }, +const NAV: { href: string; labelKey: Key; icon: typeof LayoutGrid }[] = [ + { href: "/", labelKey: "nav.board", icon: LayoutGrid }, + { href: "/monitors", labelKey: "nav.monitors", icon: Activity }, + { href: "/domains", labelKey: "nav.domains", icon: Globe }, + { href: "/systems", labelKey: "nav.systems", icon: Server }, + { href: "/incidents", labelKey: "nav.incidents", icon: AlertTriangle }, + { href: "/status", labelKey: "nav.status", icon: Megaphone }, ]; function Shell() { @@ -94,14 +95,14 @@ function Shell() { )} > - {n.label} + {t(n.labelKey)} ); })}
diff --git a/apps/frontend/src/i18n/en.ts b/apps/frontend/src/i18n/en.ts new file mode 100644 index 0000000..49029e1 --- /dev/null +++ b/apps/frontend/src/i18n/en.ts @@ -0,0 +1,27 @@ +// English source strings. Flat keys, dot-separated namespaces. +// Add a locale by copying this file — keys must match exactly. +export const en = { + "nav.board": "Board", + "nav.monitors": "Monitors", + "nav.domains": "Domains", + "nav.systems": "Systems", + "nav.incidents": "Incidents", + "nav.status": "Status", + + "header.search": "Search (⌘K)", + "header.settings": "Settings", + "header.theme": "Toggle theme", + "header.addWidget": "Add widget", + "header.addService": "Add service", + "header.widget": "Widget", + + "common.add": "Add", + "common.edit": "Edit", + "common.delete": "Delete", + "common.cancel": "Cancel", + "common.save": "Save", + "common.test": "Test", + "common.loading": "Loading…", +} as const; + +export type Key = keyof typeof en; diff --git a/apps/frontend/src/i18n/index.ts b/apps/frontend/src/i18n/index.ts new file mode 100644 index 0000000..d50a624 --- /dev/null +++ b/apps/frontend/src/i18n/index.ts @@ -0,0 +1,13 @@ +import { en, type Key } from "@/i18n/en"; + +export type { Key } from "@/i18n/en"; + +// i18n seam: dictionaries are plain objects keyed by dotted names. `t` is a +// typed lookup that falls back to the key itself, so a missing translation +// renders the key — loud in dev, never a crash. Locales beyond `en` plug in +// here later (settings key + lazy import); nothing else changes. +const dict: Record = en; + +export function t(key: Key): string { + return dict[key] ?? key; +}