Files
edgeever/apps/web/src/components/editor/EditorNoteLinkPicker.tsx
T
Dariusz Korytoandtianma-if d0f441dafa fix(web): localize untitled notes and diagrams on public share pages (#605)
* fix(web): show the localized label for untitled notes everywhere

Untitled notes are stored with the zh-CN default title, and several views
only replaced an empty title, so they showed 无标题笔记 in every locale.
The visible cases were the public share page (heading and browser tab),
the public notebook site, AI companion cards and mentions, the note link
picker, link text inserted for a note, and the title of a duplicated
note.

Add getMemoDisplayTitle() next to DEFAULT_MEMO_TITLE and use it in those
views, matching what MemoCard already does with isDefaultMemoTitle.

* fix(web): localize diagrams and content label on the public share page

The public share page passed zh-CN to read-only diagrams for every
language other than English, so Japanese and Polish visitors saw Chinese
diagram labels. Resolve the locale with resolveSupportedLocale(), which
the diagram renderer already supports for all four languages.

Also translate the note content aria-label, which was hard-coded in
English.

---------

Co-authored-by: tianma-if <yingwaizhiying@gmail.com>
2026-10-18 11:26:50 +08:00

82 lines
2.7 KiB
TypeScript

import { Link2, X } from "lucide-react";
import { getMemoDisplayTitle } from "@edgeever/shared";
import { useTranslation } from "react-i18next";
import type { MemoSummary } from "@edgeever/shared";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
export const EditorNoteLinkPicker = ({
query,
isLoading,
memos,
currentMemoId,
onQueryChange,
onClose,
onInsert,
}: {
query: string;
isLoading: boolean;
memos: MemoSummary[];
currentMemoId: string;
onQueryChange: (query: string) => void;
onClose: () => void;
onInsert: (memo: MemoSummary) => void;
}) => {
const { t } = useTranslation();
const candidates = memos.filter((candidate) => candidate.id !== currentMemoId && !candidate.isDeleted);
return (
<div
className="absolute left-3 right-3 top-14 z-30 h-[min(22rem,calc(100%-4rem))] max-w-xl rounded-lg border border-slate-200 bg-card shadow-xl sm:left-5 sm:right-auto sm:w-[28rem]"
role="dialog"
aria-label={t("noteLinkPicker.title")}
>
<Command shouldFilter={false}>
<div className="flex items-center justify-between border-b border-slate-100 pr-2">
<CommandInput
autoFocus
value={query}
placeholder={t("noteLinkPicker.searchPlaceholder")}
onValueChange={onQueryChange}
onKeyDown={(event) => {
if (event.key === "Escape") onClose();
}}
/>
<button
type="button"
className="rounded-md p-1.5 text-slate-400 hover:bg-slate-100 hover:text-slate-700"
aria-label={t("noteLinkPicker.close")}
onClick={onClose}
>
<X className="h-4 w-4" />
</button>
</div>
<CommandList>
{isLoading ? (
<div className="p-6 text-center text-sm text-slate-500">{t("noteLinkPicker.loading")}</div>
) : (
<>
<CommandEmpty>{t("noteLinkPicker.empty")}</CommandEmpty>
<CommandGroup>
{candidates.map((candidate) => (
<CommandItem key={candidate.id} value={candidate.id} onSelect={() => onInsert(candidate)}>
<Link2 className="h-4 w-4 shrink-0 text-slate-600" />
<span className="min-w-0 flex-1 truncate">{getMemoDisplayTitle(candidate.title, t("common.untitledMemo"))}</span>
<span className="max-w-40 truncate text-xs text-slate-400">{candidate.excerpt}</span>
</CommandItem>
))}
</CommandGroup>
</>
)}
</CommandList>
</Command>
</div>
);
};