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

+17 -3
View File
@@ -1,7 +1,11 @@
import type { WidgetKind } from "@homarr/definitions";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { WidgetOptionOfType, WidgetOptionType } from "../options";
import {
getWidgetOptionDescriptionTranslationNamespace,
getWidgetOptionTranslationNamespace,
} from "../option-translation";
export interface CommonWidgetInputProps<TKey extends WidgetOptionType> {
kind: WidgetKind;
@@ -25,7 +29,17 @@ type UseWidgetInputTranslationReturnType = (key: "label" | "description" | "plac
* is defined for the option. The method does sadly not reconize issues with those definitions. So it does not yell at you when you somewhere show the label without having it defined in the translations.
*/
export const useWidgetInputTranslation = (kind: WidgetKind, property: string): UseWidgetInputTranslationReturnType => {
return useScopedI18n(
`widget.${kind}.option.${property}` as never, // Because the type is complex and not recognized by typescript, we need to cast it to never to make it work.
const inputNamespace = getWidgetOptionTranslationNamespace(kind, property);
const descriptionNamespace = getWidgetOptionDescriptionTranslationNamespace(kind, property);
const tInput = useI18n(
inputNamespace as never, // Because the type is complex and not recognized by typescript, we need to cast it to never to make it work.
) as unknown as UseWidgetInputTranslationReturnType;
const tDescription = useI18n(
(descriptionNamespace ?? inputNamespace) as never,
) as unknown as UseWidgetInputTranslationReturnType;
return (key) => {
if (key === "description" && descriptionNamespace) return tDescription("description");
return tInput(key);
};
};
@@ -5,16 +5,16 @@ import { Loader, Select } from "@mantine/core";
import { useDebouncedValue } from "@mantine/hooks";
import { clientApi } from "@homarr/api/client";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { CommonWidgetInputProps } from "./common";
import { useWidgetInputTranslation } from "./common";
import { useFormContext } from "./form";
export const WidgetAnchorNoteInput = ({ property, kind, options }: CommonWidgetInputProps<"anchorNote">) => {
const t = useI18n();
const tCommon = useI18n("common");
const tWidget = useWidgetInputTranslation(kind, property);
const tAnchor = useScopedI18n("widget.anchorNote");
const tAnchor = useI18n("widget.anchorNote");
const form = useFormContext();
const integrationId = form.values.integrationIds[0];
@@ -46,7 +46,7 @@ export const WidgetAnchorNoteInput = ({ property, kind, options }: CommonWidgetI
<Select
label={tWidget("label")}
description={options.withDescription ? tWidget("description") : undefined}
placeholder={integrationId ? t("common.select.placeholder") : tAnchor("integrationRequired")}
placeholder={integrationId ? tCommon("select.placeholder") : tAnchor("integrationRequired")}
nothingFoundMessage={integrationId ? tAnchor("noNotes") : tAnchor("integrationRequired")}
searchable
searchValue={search}
@@ -24,7 +24,8 @@ const renderHereLink = (children: React.ReactNode) => (
);
export const WidgetAppInput = ({ property, kind }: CommonWidgetInputProps<"app">) => {
const t = useI18n();
const tCommon = useI18n("common");
const tApp = useI18n("widget.common.app");
const tInput = useWidgetInputTranslation(kind, property);
const form = useFormContext();
const { data: apps, isPending, refetch } = clientApi.app.selectable.useQuery();
@@ -44,7 +45,7 @@ export const WidgetAppInput = ({ property, kind }: CommonWidgetInputProps<"app">
label={tInput("label")}
searchable
leftSection={<MemoizedLeftSection isPending={isPending} currentApp={currentApp} />}
nothingFoundMessage={t("widget.common.app.noData")}
nothingFoundMessage={tApp("noData")}
renderOption={renderSelectOption}
data={
apps?.map((app) => ({
@@ -56,8 +57,8 @@ export const WidgetAppInput = ({ property, kind }: CommonWidgetInputProps<"app">
inputWrapperOrder={["label", "input", "description", "error"]}
description={
<Text component="span" size="xs">
{t.rich("widget.common.app.description", {
here: renderHereLink,
{tApp.rich("description", {
here: () => renderHereLink(tCommon("here")),
})}
</Text>
}
@@ -79,7 +80,7 @@ export const WidgetAppInput = ({ property, kind }: CommonWidgetInputProps<"app">
})
}
>
{t("widget.common.app.quickCreate")}
{tApp("quickCreate")}
</Button>
)}
</SimpleGrid>
@@ -25,7 +25,7 @@ import { normalizeCustomWidgetOptions, validateCustomWidgetOptions } from "@homa
import { CustomWidgetCodeEditor } from "@homarr/custom-widgets/workbench";
import type { CustomWidgetEditorMessages } from "@homarr/custom-widgets/workbench";
import { IconPicker } from "@homarr/forms-collection";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { CommonWidgetInputProps } from "./common";
import { useFormContext } from "./form";
@@ -34,7 +34,7 @@ export const WidgetCustomWidgetConfigurationInput = ({
property,
}: CommonWidgetInputProps<"customWidgetConfiguration">) => {
const form = useFormContext();
const labels = useScopedI18n("widget.customApi.configuration");
const labels = useI18n("widget.customApi.configuration");
const board = useOptionalBoard();
const definitionId = typeof form.values.options.definitionId === "string" ? form.values.options.definitionId : "";
const configurationValue = form.values.options[property];
@@ -72,7 +72,7 @@ vi.mock("@homarr/boards/context", () => ({
}));
vi.mock("@homarr/translation/client", () => ({
useScopedI18n: () => (key: string) =>
useI18n: () => (key: string) =>
({
migrationRequired: "Migration required",
migrationDescription: "Copy its redacted prompt to an LLM, then paste the single JSON response into Homarr.",
@@ -19,7 +19,7 @@ import { IconAlertTriangle, IconApi } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { useSession } from "@homarr/auth/client";
import { useOptionalBoard } from "@homarr/boards/context";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import type { CommonWidgetInputProps } from "./common";
@@ -32,7 +32,7 @@ export const WidgetCustomWidgetSelectInput = ({
options,
}: CommonWidgetInputProps<"customWidgetSelect">) => {
const t = useWidgetInputTranslation(kind, property);
const labels = useScopedI18n("widget.customApi.picker");
const labels = useI18n("widget.customApi.picker");
const { data: session } = useSession();
const isAdmin = session?.user.permissions.includes("admin") ?? false;
const form = useFormContext();
@@ -24,7 +24,7 @@ import dayjs from "dayjs";
import { createId } from "@homarr/common";
import type { WidgetKind } from "@homarr/definitions";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import { formatWallDateTime, resolveWallDateTimeString } from "../countdown/recurrence";
import type { DateTimeEventRecurrence, OptionDateTimeEvent } from "../options";
@@ -314,4 +314,4 @@ type EventListTranslationKey =
type EventListTranslation = (key: EventListTranslationKey, values?: Record<string, string | number>) => string;
const useEventListTranslation = (kind: WidgetKind) =>
useScopedI18n(`widget.${kind}.dateTimeEventList` as never) as unknown as EventListTranslation;
useI18n(`widget.${kind}.dateTimeEventList` as never) as unknown as EventListTranslation;
@@ -2,7 +2,7 @@
import { Loader, MultiSelect } from "@mantine/core";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { CommonWidgetInputProps } from "./common";
import { useWidgetInputTranslation } from "./common";
@@ -13,7 +13,8 @@ export const WidgetDynamicMultiSelectInput = ({
kind,
options,
}: CommonWidgetInputProps<"dynamicMultiSelect">) => {
const t = useScopedI18n("widget.dynamicSelect");
const t = useI18n("widget.common.select");
const tCommon = useI18n("common");
const tInput = useWidgetInputTranslation(kind, property);
const form = useFormContext();
const inputProps = form.getInputProps(`options.${property}`);
@@ -36,7 +37,7 @@ export const WidgetDynamicMultiSelectInput = ({
searchable
clearable
maxValues={options.maxValues}
nothingFoundMessage={t("noResults")}
nothingFoundMessage={tCommon("noResults")}
data={data}
disabled={isError}
error={isError ? t("loadError") : inputProps.error}
@@ -6,7 +6,7 @@ import { useDebouncedValue } from "@mantine/hooks";
import { IconCheck } from "@tabler/icons-react";
import { translateIfNecessary } from "@homarr/translation";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { CommonWidgetInputProps } from "./common";
import { useWidgetInputTranslation } from "./common";
@@ -25,7 +25,7 @@ export const WidgetDynamicSelectInput = ({
boardId,
}: CommonWidgetInputProps<"dynamicSelect">) => {
const t = useI18n();
const tSelect = useScopedI18n("widget.dynamicSelect");
const tCommon = useI18n("common");
const tWidget = useWidgetInputTranslation(kind, property);
const form = useFormContext();
const inputProps = form.getInputProps(`options.${property}`);
@@ -78,7 +78,7 @@ export const WidgetDynamicSelectInput = ({
searchValue={search}
onSearchChange={setSearch}
placeholder={tWidget("placeholder")}
nothingFoundMessage={tSelect("noResults")}
nothingFoundMessage={tCommon("noResults")}
leftSection={isPending && <Loader size="xs" />}
renderOption={({ option, checked }) => {
return (
@@ -2,20 +2,20 @@
import { MultiSelect, Text } from "@mantine/core";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { CommonWidgetInputProps } from "./common";
import { useWidgetInputTranslation } from "./common";
import { useFormContext } from "./form";
const useIntegrationSelectTranslation = () => useScopedI18n("widget.integrationSelect");
export const WidgetIntegrationMultiSelectInput = ({
property,
kind,
options,
}: CommonWidgetInputProps<"integrationMultiSelect">) => {
const t = useIntegrationSelectTranslation();
const t = useI18n("widget.integrationSelect");
const tSelect = useI18n("widget.common.select");
const tCommon = useI18n("common");
const tInput = useWidgetInputTranslation(kind, property);
const form = useFormContext();
@@ -40,8 +40,8 @@ export const WidgetIntegrationMultiSelectInput = ({
}
const description = options.withDescription ? tInput("description") : undefined;
const placeholder = isPending ? t("loading") : tInput("placeholder");
const loadError = isError ? t("loadError") : undefined;
const placeholder = isPending ? tSelect("loading") : tInput("placeholder");
const loadError = isError ? tSelect("loadError") : undefined;
return (
<MultiSelect
@@ -51,7 +51,7 @@ export const WidgetIntegrationMultiSelectInput = ({
placeholder={placeholder}
searchable
clearable
nothingFoundMessage={t("noResults")}
nothingFoundMessage={tCommon("noResults")}
data={data}
disabled={isError}
error={loadError ?? inputProps.error}
@@ -2,20 +2,20 @@
import { Select, Text } from "@mantine/core";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { CommonWidgetInputProps } from "./common";
import { useWidgetInputTranslation } from "./common";
import { useFormContext } from "./form";
const useIntegrationSelectTranslation = () => useScopedI18n("widget.integrationSelect");
export const WidgetIntegrationSelectInput = ({
property,
kind,
options,
}: CommonWidgetInputProps<"integrationSelect">) => {
const t = useIntegrationSelectTranslation();
const t = useI18n("widget.integrationSelect");
const tSelect = useI18n("widget.common.select");
const tCommon = useI18n("common");
const tInput = useWidgetInputTranslation(kind, property);
const form = useFormContext();
@@ -35,8 +35,8 @@ export const WidgetIntegrationSelectInput = ({
}
const description = options.withDescription ? tInput("description") : undefined;
const placeholder = isPending ? t("loading") : t("placeholder");
const error = isError ? t("loadError") : undefined;
const placeholder = isPending ? tSelect("loading") : t("placeholder");
const error = isError ? tSelect("loadError") : undefined;
return (
<Select
@@ -45,7 +45,7 @@ export const WidgetIntegrationSelectInput = ({
placeholder={placeholder}
clearable={options.clearable}
searchable={options.searchable}
nothingFoundMessage={t("noResults")}
nothingFoundMessage={tCommon("noResults")}
data={data}
disabled={isError}
error={error}
@@ -46,7 +46,7 @@ vi.mock("@homarr/modals", () => ({
vi.mock("@homarr/translation/client", () => ({
useCurrentIntlLocale: () => "en",
useScopedI18n: () => (key: string) => key,
useI18n: () => (key: string) => key,
}));
vi.mock("./common", async (importOriginal) => ({
@@ -21,7 +21,7 @@ import { IconClick, IconListSearch } from "@tabler/icons-react";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { createModal, useModalAction } from "@homarr/modals";
import { useCurrentIntlLocale, useScopedI18n } from "@homarr/translation/client";
import { useCurrentIntlLocale, useI18n } from "@homarr/translation/client";
import { formatLocalizedCompactNumber } from "../common/locale";
import type { OptionLocation } from "../options";
@@ -31,7 +31,7 @@ import { useFormContext } from "./form";
export const WidgetLocationInput = ({ property, kind, options }: CommonWidgetInputProps<"location">) => {
const t = useWidgetInputTranslation(kind, property);
const tLocation = useScopedI18n("widget.common.location");
const tLocation = useI18n("widget.common.location");
const form = useFormContext();
const { openModal } = useModalAction(LocationSearchModal);
const fieldPath = `options.${property}`;
@@ -149,8 +149,8 @@ interface LocationSearchInnerProps {
}
const LocationSearchModal = createModal<LocationSearchInnerProps>(({ actions, innerProps }) => {
const t = useScopedI18n("widget.common.location.table");
const tCommon = useScopedI18n("common");
const t = useI18n("widget.common.location.table");
const tCommon = useI18n("common");
const { data, isPending, error } = clientApi.location.searchCity.useQuery({
query: innerProps.query,
});
@@ -216,7 +216,7 @@ interface LocationSearchTableRowProps {
}
const LocationSelectTableRow = ({ city, onLocationSelect, closeModal }: LocationSearchTableRowProps) => {
const t = useScopedI18n("widget.common.location.table");
const t = useI18n("widget.common.location.table");
const locale = useCurrentIntlLocale();
const onSelect = useCallback(() => {
onLocationSelect({
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import { Combobox, Pill, PillsInput, useCombobox } from "@mantine/core";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { CommonWidgetInputProps } from "./common";
import { useWidgetInputTranslation } from "./common";
@@ -9,7 +9,7 @@ import { useFormContext } from "./form";
export const WidgetMultiTextInput = ({ property, kind, options }: CommonWidgetInputProps<"multiText">) => {
const t = useWidgetInputTranslation(kind, property);
const tCommon = useScopedI18n("common");
const tCommon = useI18n("common");
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
onDropdownOpen: () => combobox.updateSelectedOptionIndex("active"),
@@ -48,7 +48,7 @@ import {
import { IconPicker } from "@homarr/forms-collection";
import { createModal, useModalAction } from "@homarr/modals";
import { showErrorNotification } from "@homarr/notifications";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import { MaskedImage } from "@homarr/ui";
import type { ReleasesRepository, ReleasesVersionFilter } from "../releases/releases-repository";
@@ -69,7 +69,8 @@ export const WidgetMultiReleasesRepositoriesInput = ({
itemId,
}: CommonWidgetInputProps<"multiReleasesRepositories">) => {
const t = useWidgetInputTranslation(kind, property);
const tRepository = useScopedI18n("widget.releases.option.repositories");
const tRepository = useI18n("widget.releases.option.repositories");
const actionT = useI18n("common.action");
const form = useFormContext();
const repositories = form.values.options[property] as ReleasesRepository[];
const { openModal: openEditModal } = useModalAction(RepositoryEditModal);
@@ -232,7 +233,7 @@ export const WidgetMultiReleasesRepositoriesInput = ({
leftSection={<IconEdit size="var(--mantine-font-size-sm)" />}
size="xs"
>
{tRepository("edit.label")}
{actionT("edit")}
</Button>
<ActionIcon variant="transparent" color="red" onClick={() => onRepositoryRemove(index)}>
@@ -267,7 +268,7 @@ const providersWithAuth: ReleaseProviderKind[] = [
];
const ProviderTokensSection = ({ itemId, repositories }: { itemId: string; repositories: ReleasesRepository[] }) => {
const tRepository = useScopedI18n("widget.releases.option.repositories");
const tRepository = useI18n("widget.releases.option.repositories");
const { data: configuredKinds = [], refetch } = clientApi.widget.secrets.getConfiguredKinds.useQuery({ itemId });
const setSecret = clientApi.widget.secrets.setSecret.useMutation({
onSuccess: () => refetch(),
@@ -331,7 +332,8 @@ const ProviderTokenInput = ({
onSave: (value: string) => Promise<void>;
onDelete: () => Promise<void>;
}) => {
const tRepository = useScopedI18n("widget.releases.option.repositories");
const tRepository = useI18n("widget.releases.option.repositories");
const actionT = useI18n("common.action");
const [value, setValue] = useState("");
const [editing, setEditing] = useState(false);
const [saving, setSaving] = useState(false);
@@ -375,7 +377,7 @@ const ProviderTokenInput = ({
/>
{editing && value.trim() && (
<Button size="xs" onClick={handleSave} loading={saving}>
{tRepository("tokens.save")}
{actionT("save")}
</Button>
)}
{hasToken && !editing && (
@@ -412,7 +414,8 @@ interface RepositoryEditProps {
}
const RepositoryEditModal = createModal<RepositoryEditProps>(({ innerProps, actions }) => {
const tRepository = useScopedI18n("widget.releases.option.repositories");
const tRepository = useI18n("widget.releases.option.repositories");
const actionT = useI18n("common.action");
const [loading, setLoading] = useState(false);
const [tempRepository, setTempRepository] = useState(() => ({ ...innerProps.repository }));
const [formErrors, setFormErrors] = useState<FormErrors>({});
@@ -576,11 +579,11 @@ const RepositoryEditModal = createModal<RepositoryEditProps>(({ innerProps, acti
<Divider my={"sm"} />
<Group justify="flex-end">
<Button variant="default" onClick={handleCancel} color="gray.5">
{tRepository("editForm.cancel.label")}
{actionT("cancel")}
</Button>
<Button data-autofocus onClick={handleConfirm} loading={loading}>
{tRepository("editForm.confirm.label")}
{actionT("confirm")}
</Button>
</Group>
</Stack>
@@ -611,7 +614,7 @@ const ImportRepositorySelect = ({
disabled = false,
onImageSelectionChanged = undefined,
}: ImportRepositorySelectProps) => {
const tRepository = useScopedI18n("widget.releases.option.repositories");
const tRepository = useI18n("widget.releases.option.repositories");
const provider = repository.provider;
return (
@@ -687,7 +690,8 @@ interface RepositoryImportProps {
}
const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps, actions }) => {
const tRepository = useScopedI18n("widget.releases.option.repositories");
const tRepository = useI18n("widget.releases.option.repositories");
const actionT = useI18n("common.action");
const [loading, setLoading] = useState(false);
const [selectedImages, setSelectedImages] = useState([] as ReleasesRepositoryImport[]);
@@ -847,11 +851,11 @@ const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps,
<Group justify="flex-end">
<Button variant="default" onClick={actions.closeModal} color="gray.5">
{tRepository("editForm.cancel.label")}
{actionT("cancel")}
</Button>
<Button onClick={handleConfirm} loading={loading} disabled={selectedImages.length === 0}>
{tRepository("editForm.confirm.label")}
{actionT("confirm")}
</Button>
</Group>
</Stack>
@@ -22,7 +22,7 @@ import { IconGripVertical, IconPlus, IconTrash } from "@tabler/icons-react";
import { createId } from "@homarr/common";
import type { WidgetKind } from "@homarr/definitions";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { OptionTimezone } from "../options";
import type { CommonWidgetInputProps } from "./common";
@@ -246,4 +246,4 @@ type TimezoneListTranslationKey =
type TimezoneListTranslation = (key: TimezoneListTranslationKey, values?: Record<string, string | number>) => string;
const useTimezoneListTranslation = (kind: WidgetKind) =>
useScopedI18n(`widget.${kind}.timezoneList` as never) as unknown as TimezoneListTranslation;
useI18n(`widget.${kind}.timezoneList` as never) as unknown as TimezoneListTranslation;
@@ -3,14 +3,15 @@
import { Select, Text } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { CommonWidgetInputProps } from "./common";
import { useWidgetInputTranslation } from "./common";
import { useFormContext } from "./form";
export const WidgetUmamiEventInput = ({ property, kind }: CommonWidgetInputProps<"umamiEventName">) => {
const t = useScopedI18n("widget.umami.option.eventName");
const t = useI18n("widget.umami.option.eventName");
const tEvents = useI18n("widget.umami.option.events");
const tInput = useWidgetInputTranslation(kind, property);
const form = useFormContext();
@@ -25,7 +26,7 @@ export const WidgetUmamiEventInput = ({ property, kind }: CommonWidgetInputProps
if (!websiteId || integrationIds.length === 0) {
return (
<Text size="sm" c="dimmed">
{t("configureWebsiteFirst")}
{tEvents("configureWebsiteFirst")}
</Text>
);
}
@@ -36,9 +37,9 @@ export const WidgetUmamiEventInput = ({ property, kind }: CommonWidgetInputProps
<Select
label={tInput("label")}
description={tInput("description")}
placeholder={isPending ? t("loading") : undefined}
placeholder={isPending ? tEvents("loading") : undefined}
searchable
nothingFoundMessage={t("noEvents")}
nothingFoundMessage={tEvents("noEvents")}
data={data}
{...form.getInputProps(`options.${property}`)}
/>
@@ -3,14 +3,15 @@
import { MultiSelect, Text } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { CommonWidgetInputProps } from "./common";
import { useWidgetInputTranslation } from "./common";
import { useFormContext } from "./form";
export const WidgetUmamiEventNamesInput = ({ property, kind }: CommonWidgetInputProps<"umamiEventNames">) => {
const t = useScopedI18n("widget.umami.option.eventNames");
const t = useI18n("widget.umami.option.eventNames");
const tEvents = useI18n("widget.umami.option.events");
const tInput = useWidgetInputTranslation(kind, property);
const form = useFormContext();
@@ -25,7 +26,7 @@ export const WidgetUmamiEventNamesInput = ({ property, kind }: CommonWidgetInput
if (!websiteId || integrationIds.length === 0) {
return (
<Text size="sm" c="dimmed">
{t("configureWebsiteFirst")}
{tEvents("configureWebsiteFirst")}
</Text>
);
}
@@ -34,9 +35,9 @@ export const WidgetUmamiEventNamesInput = ({ property, kind }: CommonWidgetInput
<MultiSelect
label={tInput("label")}
description={tInput("description")}
placeholder={isPending ? t("loading") : t("placeholder")}
placeholder={isPending ? tEvents("loading") : t("placeholder")}
searchable
nothingFoundMessage={t("noEvents")}
nothingFoundMessage={tEvents("noEvents")}
data={eventNames ?? []}
{...form.getInputProps(`options.${property}`)}
/>
@@ -3,14 +3,14 @@
import { Select, Text } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import type { CommonWidgetInputProps } from "./common";
import { useWidgetInputTranslation } from "./common";
import { useFormContext } from "./form";
export const WidgetUmamiWebsiteInput = ({ property, kind }: CommonWidgetInputProps<"umamiWebsite">) => {
const t = useScopedI18n("widget.umami.option.websiteId");
const t = useI18n("widget.umami.option.websiteId");
const tInput = useWidgetInputTranslation(kind, property);
const form = useFormContext();