refactor: consolidate English translations (#6640)

This commit is contained in:
Thomas authored and GitHub committed 2026-08-21 19:00:27 +02:00
1 parent 81aa704537
commit 9e14a07e64
449 files changed
+3515 -3922

No files matched your search

@@ -5,7 +5,7 @@ import { IconBox, IconLayoutDashboard, IconPlug, IconResize } from "@tabler/icon
import { useRequiredBoard } from "@homarr/boards/context";
import { useEditMode } from "@homarr/boards/edit-mode";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import { useBoardAddActions } from "./use-board-add-actions";
import classes from "./board-empty-state.module.css";
@@ -14,7 +14,7 @@ export const BoardEmptyState = () => {
const board = useRequiredBoard();
const [isEditMode] = useEditMode();
const { addWidget, addApp, connectService, canConnectService } = useBoardAddActions();
const t = useScopedI18n("board.emptyState");
const t = useI18n("board.emptyState");
const hasBoardContent = board.items.length > 0 || board.sections.some((section) => section.kind !== "empty");
if (!isEditMode || hasBoardContent) return null;
@@ -7,7 +7,7 @@ import { IconCheck, IconChevronDown, IconChecklist } from "@tabler/icons-react";
import { useIntegrationsWithUseAccess } from "@homarr/auth/client";
import { useRequiredBoard } from "@homarr/boards/context";
import { useEditMode } from "@homarr/boards/edit-mode";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import { useSetupAnalytics } from "~/components/create/setup-analytics";
import { getBoardSetupProgress } from "./board-setup-progress";
@@ -23,7 +23,8 @@ export const BoardSetupChecklist = () => {
key: `homarr-board-setup-checklist-${board.id}`,
defaultValue: false,
});
const t = useScopedI18n("board.setupChecklist");
const t = useI18n("board.setupChecklist");
const tCommon = useI18n("common.action");
const trackSetup = useSetupAnalytics();
const progress = getBoardSetupProgress({
itemKinds: board.items.map((item) => item.kind),
@@ -74,14 +75,14 @@ export const BoardSetupChecklist = () => {
complete={progress.steps.content}
label={t("step.content.label")}
description={t("step.content.description")}
actionLabel={t("step.content.action")}
actionLabel={tCommon("add")}
onAction={addWidget}
/>
<ChecklistStep
complete={progress.steps.app}
label={t("step.app.label")}
description={t("step.app.description")}
actionLabel={t("step.app.action")}
actionLabel={tCommon("add")}
onAction={addApp}
/>
<ChecklistStep
@@ -13,6 +13,7 @@ import { ErrorBoundary } from "react-error-boundary";
import { useRequiredBoard } from "@homarr/boards/context";
import { useEditMode } from "@homarr/boards/edit-mode";
import { getWidgetName } from "@homarr/definitions";
import { useSettings } from "@homarr/settings";
import { useI18n } from "@homarr/translation/client";
import { WidgetError } from "@homarr/widgets/errors";
@@ -178,7 +179,7 @@ const LoadedBoardItemContent = ({
const { active, viewportSize, open, close, dismiss, hover, leave } = useAdvancedFocus();
const { width: viewportWidth, height: viewportHeight } = viewportSize;
const supportsAdvancedFocus = definitionSupportsAdvancedFocus(definition);
const widgetName = t(`widget.${item.kind}.name`);
const widgetName = getWidgetName(item.kind, t);
const advancedViewLabel = t("item.advancedFocus.label", { widget: widgetName });
const advancedViewId = `advanced-focus-${item.id}`;
const activeFocus = supportsAdvancedFocus && active?.itemId === item.id ? active : null;
@@ -4,9 +4,10 @@ import { IconCopy, IconDotsVertical, IconLayoutKanban, IconPencil } from "@table
import { useSession } from "@homarr/auth/client";
import { useEditMode } from "@homarr/boards/edit-mode";
import { getWidgetName } from "@homarr/definitions";
import { useModalAction } from "@homarr/modals";
import { useSettings } from "@homarr/settings";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { WidgetDefinition } from "@homarr/widgets/definition";
import type { SectionItem } from "~/app/[locale]/boards/_types";
@@ -34,7 +35,7 @@ const BoardItemMenuInner = ({ item, definition, resetErrorBoundary }: BoardItemM
const { data: session } = useSession();
const canDuplicate = item.kind !== "customApi" || (session?.user.permissions.includes("admin") ?? false);
const refResetErrorBoundaryOnNextRender = useRef(false);
const tItem = useScopedI18n("item");
const tItem = useI18n("item");
const t = useI18n();
const { openModal } = useModalAction(LazyWidgetEditModal);
const openMoveModal = useOpenItemMoveModal();
@@ -43,7 +44,7 @@ const BoardItemMenuInner = ({ item, definition, resetErrorBoundary }: BoardItemM
useItemActions();
const { integrations: integrationData, section } = useSectionContext();
const settings = useSettings();
const label = item.advancedOptions.title?.trim() || t(`widget.${item.kind}.name`);
const label = item.advancedOptions.title?.trim() || getWidgetName(item.kind, t);
// Reset error boundary on next render if item has been edited
useEffect(() => {
@@ -92,7 +93,7 @@ const BoardItemMenuInner = ({ item, definition, resetErrorBoundary }: BoardItemM
},
{
title(translate) {
return `${translate("item.edit.title")} - ${translate(`widget.${item.kind}.name`)}`;
return `${translate("item.edit.title")} - ${getWidgetName(item.kind, translate)}`;
},
},
);
@@ -6,7 +6,7 @@ import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
import { getRootSectionLane } from "@homarr/definitions";
import { useZodForm } from "@homarr/form";
import { createModal, ModalFormFooter, modalSizeForm, useModalAction } from "@homarr/modals";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { Board, ContainerSectionItem, SectionItem } from "~/app/[locale]/boards/_types";
import { getLayoutRowCount } from "../layout";
@@ -53,9 +53,9 @@ type MoveTargetCandidate = Omit<MoveTarget, "label"> & {
};
export const ItemMoveModal = createModal<InnerProps>(({ actions, innerProps }) => {
const tLandmark = useScopedI18n("board.landmark");
const tContainer = useScopedI18n("section.container");
const tMoveResize = useScopedI18n("item.moveResize");
const tLandmark = useI18n("board.landmark");
const tContainer = useI18n("section.container");
const tMoveResize = useI18n("item.moveResize");
const { board, commitSectionGrids, currentLayoutId, entry, sourceSectionId } = innerProps;
const minimumSize = useMemo(
() => getEntryMinimumSize(board, currentLayoutId, entry),
@@ -24,7 +24,6 @@ vi.mock("@homarr/translation/client", () => ({
};
return translations[key] ?? key;
},
useScopedI18n: () => (key: string) => key,
}));
vi.mock("@homarr/definitions", async () => {
@@ -28,6 +28,7 @@ import {
getBoardLaneColumnCount,
getIconUrl,
getIntegrationName,
getWidgetName,
getRootSectionLane,
widgetIntegrationSupport,
widgetKinds,
@@ -144,14 +145,24 @@ const ItemSelectModalContent = ({
widgetKinds
.filter((kind) => kind !== "customApi")
.map((kind) => {
let description: string;
if (kind === "mediaMissing") {
description = t("widget.mediaMissing.description", {
radarr: getIntegrationName("radarr"),
sonarr: getIntegrationName("sonarr"),
});
} else {
description = t(`widget.${kind}.description`);
}
return {
kind,
supportedIntegrations: (widgetIntegrationSupport[kind] ?? []).filter(
(integration) => integration !== "mock",
),
icon: widgetCatalogIcons[kind],
name: t(`widget.${kind}.name`),
description: t(`widget.${kind}.description`),
name: getWidgetName(kind, t),
description,
};
})
.sort((itemA, itemB) => {
@@ -324,7 +335,7 @@ const ItemSelectModalContent = ({
targetSectionId: effectiveSectionId ?? undefined,
advancedOptions,
});
if (!notifyCreated(updatedBoard, itemId, t(`widget.${kind}.name`))) return;
if (!notifyCreated(updatedBoard, itemId, getWidgetName(kind, t))) return;
trackSetup("widget-completed", {
entryPoint: "board",
intent: kind,
@@ -337,7 +348,7 @@ const ItemSelectModalContent = ({
settings,
},
{
title: (titleT) => `${titleT("item.edit.title")} - ${titleT(`widget.${kind}.name`)}`,
title: (titleT) => `${titleT("item.edit.title")} - ${getWidgetName(kind, titleT)}`,
},
);
};
@@ -458,7 +469,7 @@ const ItemSelectModalContent = ({
<Group gap="xs">
<IconApi size={14} />
<Text size="xs" fw={600}>
{t("customWidget.page.list.title")}
{t("common.entity.customWidgets")}
</Text>
</Group>
}
@@ -643,7 +654,7 @@ const ItemSelectModalFrame = ({
canCreateIntegration={canCreateIntegration}
initialSearch={
innerProps.initialWidgetKind
? t(`widget.${innerProps.initialWidgetKind}.name`)
? getWidgetName(innerProps.initialWidgetKind, t)
: innerProps.initialIntegrationKind
? getIntegrationName(innerProps.initialIntegrationKind)
: undefined
@@ -16,12 +16,14 @@ import { useTimeAgo } from "@homarr/common";
import { useModalAction } from "@homarr/modals";
import { showErrorNotification } from "@homarr/notifications";
import { useSettings } from "@homarr/settings";
import { getWidgetName } from "@homarr/definitions";
import { translateIfNecessary } from "@homarr/translation";
import type { TranslationFunction } from "@homarr/translation";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { WidgetDefinition, WidgetRuntimeRef } from "@homarr/widgets/definition";
import { getWidgetQueryKeys, getWidgetRuntimeQueries, supportsAdvancedFocus } from "@homarr/widgets/definition";
import { reduceWidgetOptionsWithDefinition } from "@homarr/widgets/manifest";
import { getWidgetOptionTranslationNamespace } from "@homarr/widgets/option-translation";
import type { SectionItem } from "~/app/[locale]/boards/_types";
import { useAdvancedFocus } from "../advanced-focus/context";
@@ -51,8 +53,9 @@ export const WidgetContextMenu = ({
const board = useRequiredBoard();
const { hasChangeAccess } = useBoardPermissions(board);
const { updateAndPersistBoard } = usePersistBoard(board);
const tMenu = useScopedI18n("item.menu.label");
const t = useI18n();
const tMenu = useI18n("item.menu.label");
const tCommon = useI18n("common.action");
const settings = useSettings();
const { openModal } = useModalAction(LazyWidgetEditModal);
const { updateItemOptions, updateItemAdvancedOptions, updateItemIntegrations } = useItemActions();
@@ -189,7 +192,9 @@ export const WidgetContextMenu = ({
appId: item.kind === "app" ? (item.options.appId as string | undefined) : undefined,
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{ title: (fn: any) => `${fn("item.edit.title")} - ${fn(`widget.${item.kind}.name`)}` },
{
title: (fn: any) => `${fn("item.edit.title")} - ${getWidgetName(item.kind, fn)}`,
},
);
}, [
definition,
@@ -250,8 +255,7 @@ export const WidgetContextMenu = ({
<Menu.Label>{tMenu("options")}</Menu.Label>
{toggleOptions.map(([key]) => (
<Menu.CheckboxItem key={key} checked={Boolean(options[key])} onChange={handleToggle(key)}>
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
{String(translateIfNecessary(t, ((fn: any) => fn(`widget.${item.kind}.option.${key}.label`)) as any))}
{t(`${getWidgetOptionTranslationNamespace(item.kind, key)}.label` as never)}
</Menu.CheckboxItem>
))}
</>
@@ -286,7 +290,7 @@ export const WidgetContextMenu = ({
disabled={isWidgetFetching}
>
<Group justify="space-between" wrap="nowrap" gap="sm">
{tMenu("refresh")}
{tCommon("refresh")}
<WidgetQueryStatus
queryClient={queryClient}
matchesQuery={matchesWidgetQuery}
@@ -5,7 +5,7 @@ import { Avatar, Text, Tooltip } from "@mantine/core";
import type { RouterOutputs } from "@homarr/api";
import { useEditMode } from "@homarr/boards/edit-mode";
import { getIconUrl } from "@homarr/definitions";
import { getIconUrl, getWidgetName } from "@homarr/definitions";
import { useI18n } from "@homarr/translation/client";
import { loadWidgetDefinition } from "@homarr/widgets/manifest";
@@ -34,7 +34,7 @@ const LoadedWidgetHoverOverlay = ({ item, integrations }: WidgetHoverOverlayProp
const definition = use(loadWidgetDefinition(item.kind));
const WidgetIcon = definition.icon;
const displayName = item.advancedOptions.title?.trim() || t(`widget.${item.kind}.name`);
const displayName = item.advancedOptions.title?.trim() || getWidgetName(item.kind, t);
const connectedIntegrations = (integrations ?? []).filter((integration) =>
item.integrationIds.includes(integration.id),
@@ -17,7 +17,7 @@ interface InnerProps {
export const BoardRenameModal = createModal<InnerProps>(({ actions, innerProps }) => {
const utils = clientApi.useUtils();
const t = useI18n();
const tCommon = useI18n("common");
const { mutate, isPending } = clientApi.board.renameBoard.useMutation({
onSettled() {
void utils.board.getBoardByName.invalidate({
@@ -50,20 +50,20 @@ export const BoardRenameModal = createModal<InnerProps>(({ actions, innerProps }
return (
<form onSubmit={form.onSubmit(handleSubmit)}>
<Stack>
<TextInput label={t("board.field.name.label")} {...form.getInputProps("name")} data-autofocus />
<TextInput label={tCommon("field.name")} {...form.getInputProps("name")} data-autofocus />
<Group justify="end">
<Button variant="subtle" color="gray" onClick={actions.closeModal}>
{t("common.action.cancel")}
{tCommon("action.cancel")}
</Button>
<Button type="submit" loading={isPending}>
{t("common.action.confirm")}
{tCommon("action.confirm")}
</Button>
</Group>
</Stack>
</form>
);
}).withOptions({
defaultTitle: (t) => t("board.setting.section.dangerZone.action.rename.modal.title"),
defaultTitle: (t) => t("board.setting.section.dangerZone.action.rename.label"),
});
type FormType = Omit<z.infer<typeof boardRenameSchema>, "id">;
@@ -5,7 +5,7 @@ import combineClasses from "clsx";
import { useRequiredBoard } from "@homarr/boards/context";
import { useEditMode } from "@homarr/boards/edit-mode";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { ContainerSectionItem } from "~/app/[locale]/boards/_types";
import { SectionGrid } from "./grid/section-grid";
@@ -25,8 +25,8 @@ interface Props {
export const BoardContainerSection = ({ section }: Props) => {
const board = useRequiredBoard();
const [isEditMode] = useEditMode();
const t = useScopedI18n("section.container");
const tAll = useI18n();
const t = useI18n("section.container");
const tSection = useI18n("section");
const options = section.options;
const { open: openAllInNewTabs, isLoading: areAppsLoading } = useOpenSectionApps(
section.id,
@@ -137,7 +137,7 @@ export const BoardContainerSection = ({ section }: Props) => {
radius="sm"
loading={areAppsLoading}
onClick={openAllInNewTabs}
aria-label={tAll("section.action.openAllInNewTabsFor", { name: label })}
aria-label={tSection("action.openAllInNewTabsFor", { name: label })}
>
<IconExternalLink size={16} />
</ActionIcon>
@@ -15,7 +15,7 @@ interface ModalProps {
}
export const ContainerEditModal = createModal<ModalProps>(({ actions, innerProps }) => {
const t = useI18n();
const tSection = useI18n("section");
const theme = useMantineTheme();
const form = useZodForm(containerSectionOptionsSchema.unwrap(), {
mode: "controlled",
@@ -30,28 +30,28 @@ export const ContainerEditModal = createModal<ModalProps>(({ actions, innerProps
})}
>
<Stack>
<TextInput label={t("section.container.option.title.label")} data-autofocus {...form.getInputProps("title")} />
<TextInput label={tSection("container.option.title.label")} data-autofocus {...form.getInputProps("title")} />
<Switch
label={t("section.option.showLabel.label")}
description={t("section.option.showLabel.description")}
label={tSection("option.showLabel.label")}
description={tSection("option.showLabel.description")}
{...form.getInputProps("showLabel", { type: "checkbox" })}
/>
<Switch
label={t("section.option.collapsible.label")}
description={t("section.option.collapsible.description")}
label={tSection("option.collapsible.label")}
description={tSection("option.collapsible.description")}
{...form.getInputProps("collapsible", { type: "checkbox" })}
/>
<Switch
label={t("section.option.showOpenAll.label")}
description={t("section.option.showOpenAll.description")}
label={tSection("option.showOpenAll.label")}
description={tSection("option.showOpenAll.description")}
{...form.getInputProps("showOpenAll", { type: "checkbox" })}
/>
<TextMultiSelect
label={t("section.container.option.customCssClasses.label")}
label={tSection("container.option.customCssClasses.label")}
{...form.getInputProps("customCssClasses")}
/>
<ColorInput
label={t("section.container.option.borderColor.label")}
label={tSection("container.option.borderColor.label")}
format="hex"
swatches={Object.values(theme.colors).map((color) => color[6])}
rightSection={
@@ -3,7 +3,7 @@ import { IconArrowsMove, IconLayoutKanban, IconPencil } from "@tabler/icons-reac
import { useEditMode } from "@homarr/boards/edit-mode";
import { useModalAction } from "@homarr/modals";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { ContainerSectionItem } from "~/app/[locale]/boards/_types";
import { BoardRemoveConfirmationMenuItem } from "../../remove-confirmation-menu-item";
@@ -13,8 +13,8 @@ import { useContainerActions } from "./container-actions";
import { ContainerEditModal } from "./container-edit-modal";
export const BoardContainerMenu = ({ section }: { section: ContainerSectionItem }) => {
const tContainer = useScopedI18n("section.container");
const tItem = useScopedI18n("item");
const tContainer = useI18n("section.container");
const tItem = useI18n("item");
const { openModal } = useModalAction(ContainerEditModal);
const openMoveModal = useOpenItemMoveModal();
const { updateContainer, removeContainer } = useContainerActions();
@@ -1,5 +1,5 @@
import { useEditMode } from "@homarr/boards/edit-mode";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { EmptySection } from "~/app/[locale]/boards/_types";
import { SectionGrid } from "./grid/section-grid";
@@ -16,7 +16,7 @@ export const BoardEmptySection = ({ section, columnCount, requestedRowCount, rai
const { items, innerSections } = useSectionItems(section.id);
const totalLength = items.length + innerSections.length;
const [isEditMode] = useEditMode();
const t = useScopedI18n("board.landmark");
const t = useI18n("board.landmark");
if (totalLength === 0 && !isEditMode && requestedRowCount === 0 && railPlacement !== "main") return null;
@@ -7,6 +7,7 @@ import combineClasses from "clsx";
import { useCurrentLayout } from "@homarr/boards/context";
import { useEditMode } from "@homarr/boards/edit-mode";
import { getWidgetName } from "@homarr/definitions";
import { useI18n } from "@homarr/translation/client";
import type { ContainerSectionItem, SectionItem } from "~/app/[locale]/boards/_types";
@@ -76,7 +77,7 @@ export const FixedGridItem = ({
});
const displayName =
item.type === "item"
? item.advancedOptions.title?.trim() || t(`widget.${item.kind}.name`)
? item.advancedOptions.title?.trim() || getWidgetName(item.kind, t)
: item.options.title.trim() || t("section.container.untitled");
const accessibleLabel = t("item.moveResize.entryLabel", {
name: displayName,
@@ -36,6 +36,7 @@ import { DragDropProvider, DragOverlay, useDragDropManager, useDraggable, useDro
import { IconGripHorizontal, IconGripVertical } from "@tabler/icons-react";
import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
import { getWidgetName } from "@homarr/definitions";
import { useI18n } from "@homarr/translation/client";
import { getLogicalTrackSize, LOGICAL_GRID_PITCH } from "~/components/board/layout";
@@ -743,7 +744,7 @@ export default function GridEditor({
[...items, ...innerSections].map((entry) => [
entry.id,
entry.type === "item"
? entry.advancedOptions.title?.trim() || t(`widget.${entry.kind}.name`)
? entry.advancedOptions.title?.trim() || getWidgetName(entry.kind, t)
: entry.options.title.trim() || t("section.container.untitled"),
]),
),
@@ -80,7 +80,7 @@ export const useOpenSectionApps = (sectionId: string, enabled: boolean) => {
const currentLayoutId = useCurrentLayout();
const settings = useSettings();
const { openConfirmModal } = useConfirmModal();
const t = useI18n();
const t = useI18n("section");
const appDefinition = use(loadWidgetDefinition("app"));
const appIds = useMemo(() => {
const items: Item[] = getSectionItemsForLayout(board, sectionId, currentLayoutId);
@@ -115,8 +115,8 @@ export const useOpenSectionApps = (sectionId: string, enabled: boolean) => {
if (openedWindow) continue;
openConfirmModal({
title: t("section.openAllInNewTabs.title"),
children: t("section.openAllInNewTabs.text"),
title: t("openAllInNewTabs.title"),
children: t("openAllInNewTabs.text"),
});
break;
}
@@ -11,7 +11,7 @@ import { usePersistBoard } from "@homarr/boards/updater";
import { createId } from "@homarr/common";
import { getBoardLaneColumnCount, getRootSectionLane } from "@homarr/definitions";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { ContainerSection, ItemLayout } from "~/app/[locale]/boards/_types";
import { createItemCallback } from "~/components/board/items/actions/create-item";
@@ -42,8 +42,8 @@ export const BoardSelectionProvider = ({ children }: PropsWithChildren) => {
const board = useRequiredBoard();
const currentLayoutId = useCurrentLayout();
const { updateAndPersistBoard } = usePersistBoard(board);
const t = useI18n();
const tSelection = useScopedI18n("item.selection");
const tCommon = useI18n("common");
const tSelection = useI18n("item.selection");
const persistBoard = useCallback(
(updater: (previous: typeof board) => typeof board) =>
@@ -205,7 +205,7 @@ export const BoardSelectionProvider = ({ children }: PropsWithChildren) => {
const notificationId = `batch-remove-${Date.now()}`;
showSuccessNotification({
id: notificationId,
title: t("common.success"),
title: tCommon("success"),
message: (
<Group justify="space-between" wrap="nowrap">
<Text size="sm">{tSelection("removed", { count })}</Text>
@@ -220,13 +220,13 @@ export const BoardSelectionProvider = ({ children }: PropsWithChildren) => {
notifications.hide(notificationId);
}}
>
{t("common.action.undo")}
{tCommon("action.undo")}
</Button>
</Group>
),
autoClose: 10_000,
});
}, [board.items, clearSelection, persistBoard, selectedItemIds, t, tSelection]);
}, [board.items, clearSelection, persistBoard, selectedItemIds, tCommon, tSelection]);
const moveSelectedItemsToSection = useCallback(
(targetSectionId: string) => {
@@ -20,8 +20,8 @@ import { IconCheck, IconClipboard, IconCopy, IconFolderShare, IconTrash, IconX }
import { clientApi } from "@homarr/api/client";
import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
import { useEditMode } from "@homarr/boards/edit-mode";
import { getBoardLaneColumnCount, getRootSectionLane } from "@homarr/definitions";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { getBoardLaneColumnCount, getRootSectionLane, getWidgetName } from "@homarr/definitions";
import { useI18n } from "@homarr/translation/client";
import { widgetCatalogIcons } from "@homarr/widgets/catalog";
import type { EmptySection } from "~/app/[locale]/boards/_types";
@@ -43,7 +43,7 @@ export const BoardSelectionToolbar = () => {
const currentLayoutId = useCurrentLayout();
const currentLayout = board.layouts.find((layout) => layout.id === currentLayoutId);
const t = useI18n();
const tSelection = useScopedI18n("item.selection");
const tSelection = useI18n("item.selection");
const selectedItems = board.items.filter((item) => selectedItemIds.has(item.id));
const hasAppWidgets = selectedItems.some((item) => item.kind === "app");
@@ -71,7 +71,7 @@ export const BoardSelectionToolbar = () => {
const appId = (item.options as { appId?: string })?.appId;
const app = appId ? appMap.get(appId) : undefined;
if (!displayName) {
displayName = app?.name || t("widget.app.name");
displayName = app?.name || getWidgetName("app", t);
}
if (app?.iconUrl) {
iconUrl = app.iconUrl;
@@ -79,7 +79,7 @@ export const BoardSelectionToolbar = () => {
}
if (!displayName) {
displayName = t(`widget.${item.kind}.name`);
displayName = getWidgetName(item.kind, t);
}
return { displayName, iconUrl, IconComponent, kind: item.kind };
@@ -16,7 +16,6 @@ import { BoardSelectionProvider, useBoardSelection } from "./board-selection-con
vi.mock("@homarr/translation/client", () => ({
useI18n: () => (key: string) => key,
useScopedI18n: () => (key: string) => key,
}));
vi.mock("../audio/board-sounds", () => ({