mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-28 07:41:27 +00:00
feat: i18n seam — typed t() + en dictionary
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>
This commit is contained in:
1 parent
a89a81d46c
commit
88a8210bf8
5 files changed
+58
-15
No files matched your search
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
+15
-14
@@ -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.icon size={13} strokeWidth={1.8} />
|
||||
{n.label}
|
||||
{t(n.labelKey)}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline" aria-label="Search (⌘K)"
|
||||
variant="outline" aria-label={t("header.search")}
|
||||
onClick={() => setPaletteOpen(true)}
|
||||
className="gap-1.5 text-text-faint"
|
||||
>
|
||||
@@ -109,27 +110,27 @@ function Shell() {
|
||||
<kbd className="hidden font-mono text-[10px] min-[620px]:inline">⌘K</kbd>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline" size="icon" aria-label="Settings"
|
||||
variant="outline" size="icon" aria-label={t("header.settings")}
|
||||
onClick={() => setSettingsOpen(true)}
|
||||
>
|
||||
<Settings size={15} strokeWidth={1.8} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline" size="icon" aria-label="Toggle theme"
|
||||
variant="outline" size="icon" aria-label={t("header.theme")}
|
||||
onClick={() => void board.setSetting("theme", board.theme === "dark" ? "light" : "dark")}
|
||||
>
|
||||
{board.theme === "dark" ? <Moon size={15} strokeWidth={1.8} /> : <Sun size={15} strokeWidth={1.8} />}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline" aria-label="Add widget"
|
||||
variant="outline" aria-label={t("header.addWidget")}
|
||||
onClick={() => { setEditing(undefined); setWdgOpen(true); }}
|
||||
>
|
||||
<LayoutGrid size={13} strokeWidth={2} />
|
||||
Widget
|
||||
{t("header.widget")}
|
||||
</Button>
|
||||
<Button onClick={() => { setEditing(undefined); setSvcOpen(true); }}>
|
||||
<Plus size={13} strokeWidth={2.2} />
|
||||
Add service
|
||||
{t("header.addService")}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -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;
|
||||
@@ -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<Key, string> = en;
|
||||
|
||||
export function t(key: Key): string {
|
||||
return dict[key] ?? key;
|
||||
}
|
||||
Reference in new issue
Block a user