Files
kandev/apps/web/components/release-notes/release-notes-dialog.tsx
T
João Salavisa f09e938673 feat(i18n): localize the last batch of live surfaces before Office (#2357)
Migrates the eleven remaining files that `i18next/no-literal-string` flagged
outside `app/office`, `app/demo` and the task-create surfaces owned by #2326:
the session prepare panel, the onboarding agents step, the host system-metrics
readout, the system-health issues dialog, the shared panel search bar, the
mermaid diagram block, the settings agent card, both release-notes surfaces,
and the shared data-table pair.

Findings drop 884 -> 826 (-58) and the guard allowlist grows 442 -> 453, file
by file rather than by directory since every one of these directories still
holds un-migrated files.

Defects repaired beyond the codemod's output:

- `data-table-pagination.tsx` rendered `{count} results` as a bare JSX
  fragment, invisible to both plural gates. It is now one interpolated key
  with `_one`/`_other`, as are the health-dialog issue count and the
  release-notes "N new releases" helper.
- Two mixed literal+expression blocks were half-converted, rendering
  identically in English while being structurally untranslatable. Both were
  rebuilt as a single message.
- The codemod re-sorted `settings.json` into `localeCompare` order and
  re-encoded every non-ASCII character to `\uXXXX` under a comment claiming it
  matched the existing files. No catalog is stored escaped and only `common`
  is in that order, so both halves were reverted; all four catalogs are
  additions-only.
- Copy in default parameters and plain helpers is invisible to the lint rule,
  so `Search…`, the six prepare-panel header labels, the setup-script step name
  and error, the metric labels, `lastUpdatedText` and `buildDescription` were
  migrated by hand.

`task:preparingEnvironment` was already taken by #2326 holding "Preparing
environment" without the ellipsis this surface renders, so the prepare panel
uses `preparingEnvironmentEllipsis` rather than silently changing its copy.

Units, wire ids, the `{workspaceId}` URL token, the `"ellipsis"` page sentinel
and every mermaid theme/CSS value stay English as protocol.
2026-08-07 11:34:35 +02:00

93 lines
3.1 KiB
TypeScript

"use client";
import ReactMarkdown from "react-markdown";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from "@kandev/ui/dialog";
import { ScrollArea } from "@kandev/ui/scroll-area";
import { Badge } from "@kandev/ui/badge";
import { Separator } from "@kandev/ui/separator";
import { IconExternalLink } from "@tabler/icons-react";
import { remarkPlugins, markdownComponents } from "@/components/shared/markdown-components";
import { getReleaseUrl } from "@/lib/release-notes";
import type { ChangelogEntry } from "@/lib/changelog";
import type { TFunction } from "i18next";
import { useTranslation } from "react-i18next";
type ReleaseNotesDialogProps = {
open: boolean;
onOpenChange: (open: boolean) => void;
entries: ChangelogEntry[];
latestVersion: string;
};
function buildDescription(t: TFunction, entries: ChangelogEntry[]): string {
if (entries.length === 1) {
return entries[0].date
? t("common:releasedOn", { date: entries[0].date })
: t("common:latestReleaseNotes");
}
return t("common:newReleases", { count: entries.length });
}
export function ReleaseNotesDialog({
open,
onOpenChange,
entries,
latestVersion,
}: ReleaseNotesDialogProps) {
const { t } = useTranslation();
const releaseUrl = getReleaseUrl(latestVersion);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[520px]">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
{t("common:whatSNew")}
<Badge variant="secondary">v{latestVersion}</Badge>
</DialogTitle>
<DialogDescription>{buildDescription(t, entries)}</DialogDescription>
</DialogHeader>
<ScrollArea className="max-h-[60vh] pr-4">
<div className="space-y-4">
{entries.map((entry, index) => (
<div key={entry.version}>
{entries.length > 1 && (
<div className="flex items-center gap-2 mb-2">
<Badge variant="outline">v{entry.version}</Badge>
{entry.date && (
<span className="text-xs text-muted-foreground">{entry.date}</span>
)}
</div>
)}
<div className="markdown-body text-sm">
<ReactMarkdown remarkPlugins={remarkPlugins} components={markdownComponents}>
{entry.notes}
</ReactMarkdown>
</div>
{index < entries.length - 1 && <Separator className="mt-4" />}
</div>
))}
</div>
</ScrollArea>
<div className="pt-2 border-t border-border">
<a
href={releaseUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
>
{t("common:viewFullReleaseOnGithub")}
<IconExternalLink className="h-3 w-3" />
</a>
</div>
</DialogContent>
</Dialog>
);
}