From 9e14a07e64b8146207f96f262ff738cf2b6ee35b Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 21 Aug 2026 19:00:27 +0200 Subject: [PATCH] refactor: consolidate English translations (#6640) --- .../auth/invite/[id]/_registration-form.tsx | 4 +- .../app/[locale]/auth/invite/[id]/page.tsx | 4 +- .../app/[locale]/auth/login/_login-form.tsx | 6 +- .../src/app/[locale]/auth/login/page.tsx | 4 +- .../boards/(content)/[name]/not-found.tsx | 4 +- .../app/[locale]/boards/(content)/_client.tsx | 8 +- .../[locale]/boards/(content)/_creator.tsx | 4 +- .../boards/(content)/_edit-actions.tsx | 14 +- .../boards/(content)/_header-actions.tsx | 42 +- .../[locale]/boards/(content)/not-found.tsx | 18 +- .../boards/[name]/settings/_appereance.tsx | 31 +- .../boards/[name]/settings/_background.tsx | 27 +- .../boards/[name]/settings/_behavior.tsx | 8 +- .../boards/[name]/settings/_board-access.tsx | 4 +- .../boards/[name]/settings/_customCss.tsx | 10 +- .../boards/[name]/settings/_danger.tsx | 9 +- .../boards/[name]/settings/_general.tsx | 18 +- .../[name]/settings/_layout-preview.tsx | 12 +- .../boards/[name]/settings/_layout.tsx | 66 +- .../boards/[name]/settings/_settings-form.tsx | 18 +- .../[locale]/boards/[name]/settings/page.tsx | 4 +- .../[token]/configuration-entry.tsx | 7 +- .../manage/_components/hero-banner.tsx | 4 +- .../src/app/[locale]/manage/about/page.tsx | 6 +- .../manage/apps/_app-delete-button.tsx | 14 +- .../manage/apps/edit/[id]/_app-edit-form.tsx | 12 +- .../src/app/[locale]/manage/apps/page.tsx | 11 +- .../_components/assistant-configuration.tsx | 13 +- .../app/[locale]/manage/assistant/page.tsx | 6 +- .../_components/board-card-menu-dropdown.tsx | 15 +- .../_components/create-board-button.tsx | 4 +- .../src/app/[locale]/manage/boards/page.tsx | 4 +- .../manage/custom-widgets/_beta-banner.tsx | 4 +- .../manage/custom-widgets/_code-editor.tsx | 16 +- .../custom-widgets/_copy-ai-prompt-button.tsx | 4 +- .../custom-widgets/_custom-widget-actions.tsx | 4 +- .../_custom-widget-advanced-manifest.tsx | 4 +- .../custom-widgets/_custom-widget-ai-card.tsx | 4 +- .../_custom-widget-form-layout.tsx | 7 +- .../custom-widgets/_custom-widget-form.tsx | 9 +- .../custom-widgets/_custom-widget-list.tsx | 25 +- .../_custom-widget-option-fields.tsx | 8 +- .../_custom-widget-options-editor.tsx | 4 +- .../_custom-widget-preview-action.tsx | 4 +- .../_custom-widget-preview-panel.spec.tsx | 2 +- .../_custom-widget-preview-panel.tsx | 9 +- .../_custom-widget-request-value-editors.tsx | 6 +- .../_custom-widget-requests-editor.tsx | 4 +- .../_custom-widget-save-issues-alert.tsx | 4 +- .../_custom-widget-source-field.tsx | 7 +- .../_custom-widget-sources-editor.tsx | 6 +- .../custom-widgets/_custom-widget-tabs.tsx | 4 +- .../custom-widgets/_form-error-boundary.tsx | 7 +- .../_import-custom-widget-button.tsx | 15 +- .../_json-preview-editor.spec.tsx | 8 +- .../custom-widgets/_json-preview-editor.tsx | 4 +- .../_use-custom-widget-form-actions.ts | 21 +- .../_use-unsaved-changes-guard.ts | 9 +- .../manage/custom-widgets/new/page.tsx | 4 +- .../[locale]/manage/custom-widgets/page.tsx | 9 +- .../publish/[id]/_workshop-publish-form.tsx | 7 +- .../workshop/[id]/_workshop-detail.tsx | 4 +- .../workshop/_workshop-browse.tsx | 9 +- apps/nextjs/src/app/[locale]/manage/error.tsx | 8 +- .../integration-access-settings.tsx | 4 +- .../secrets/integration-secret-card.tsx | 15 +- .../secrets/integration-secret-inputs.tsx | 16 +- .../integration-secret-segmented-control.tsx | 4 +- .../integration-test-connection-error.tsx | 18 +- .../test-connection-certificate.tsx | 45 +- .../integrations/_integration-buttons.tsx | 12 +- .../edit/[id]/_integration-edit-form.tsx | 65 +- .../manage/integrations/edit/[id]/page.tsx | 4 +- .../new/_integration-new-form.tsx | 62 +- .../[locale]/manage/integrations/new/page.tsx | 6 +- .../app/[locale]/manage/integrations/page.tsx | 13 +- .../nextjs/src/app/[locale]/manage/layout.tsx | 31 +- .../manage/medias/_actions/copy-media.tsx | 4 +- .../manage/medias/_actions/delete-media.tsx | 8 +- .../manage/medias/_actions/upload-media.tsx | 4 +- .../src/app/[locale]/manage/medias/page.tsx | 21 +- .../src/app/[locale]/manage/not-found.tsx | 4 +- apps/nextjs/src/app/[locale]/manage/page.tsx | 65 +- .../[locale]/manage/search-engines/_form.tsx | 27 +- .../_search-engine-delete-button.tsx | 8 +- .../edit/[id]/_search-engine-edit-form.tsx | 13 +- .../manage/search-engines/edit/[id]/page.tsx | 4 +- .../new/_search-engine-new-form.tsx | 8 +- .../manage/search-engines/new/page.tsx | 4 +- .../[locale]/manage/search-engines/page.tsx | 14 +- .../_components/analytics.settings.tsx | 4 +- .../_components/appearance-settings-form.tsx | 7 +- .../_components/board-settings-form.tsx | 7 +- .../crawling-and-indexing.settings.tsx | 4 +- .../_components/culture-settings-form.tsx | 4 +- .../_components/search-settings-form.tsx | 4 +- .../settings/_components/settings-form.tsx | 14 +- .../_components/user-settings-form.tsx | 7 +- .../src/app/[locale]/manage/settings/page.tsx | 6 +- .../manage/tools/api/components/api-keys.tsx | 7 +- .../api/components/copy-api-key-modal.tsx | 4 +- .../tools/api/components/mcp-instructions.tsx | 13 +- .../app/[locale]/manage/tools/api/page.tsx | 6 +- .../backup/_components/backup-export-card.tsx | 10 +- .../backup/_components/backup-import-card.tsx | 4 +- .../app/[locale]/manage/tools/backup/page.tsx | 6 +- .../_components/add-certificate.tsx | 4 +- .../_components/remove-certificate.tsx | 14 +- .../hostnames/_components/remove-hostname.tsx | 14 +- .../tools/certificates/hostnames/page.tsx | 16 +- .../manage/tools/certificates/page.tsx | 16 +- .../tools/docker/docker-reconciliation.tsx | 27 +- .../manage/tools/docker/docker-table.tsx | 66 +- .../[locale]/manage/tools/docker/error.tsx | 7 +- .../app/[locale]/manage/tools/docker/page.tsx | 4 +- .../cluster-dashboard/cluster-dashboard.tsx | 11 +- .../kubernetes/cluster-dashboard/error.tsx | 7 +- .../header-card/header-card.tsx | 8 +- .../resource-gauge/resource-gauge.tsx | 10 +- .../resource-gauge/resource-icon.tsx | 3 +- .../resource-tile/resource-tile.tsx | 5 +- .../configmaps/configmaps-table.tsx | 14 +- .../tools/kubernetes/configmaps/page.tsx | 7 +- .../kubernetes/ingresses/ingresses-table.tsx | 18 +- .../tools/kubernetes/ingresses/page.tsx | 6 +- .../kubernetes-context-selector.tsx | 7 +- .../namespaces/namespaces-table.tsx | 18 +- .../tools/kubernetes/namespaces/page.tsx | 6 +- .../tools/kubernetes/nodes/nodes-table.tsx | 31 +- .../manage/tools/kubernetes/nodes/page.tsx | 6 +- .../manage/tools/kubernetes/pods/page.tsx | 6 +- .../tools/kubernetes/pods/pods-table.tsx | 20 +- .../manage/tools/kubernetes/secrets/page.tsx | 6 +- .../kubernetes/secrets/secrets-table.tsx | 20 +- .../manage/tools/kubernetes/services/page.tsx | 6 +- .../kubernetes/services/services-table.tsx | 20 +- .../manage/tools/kubernetes/volumes/page.tsx | 6 +- .../kubernetes/volumes/volumes-table.tsx | 20 +- .../manage/tools/logs/level-selection.tsx | 4 +- .../app/[locale]/manage/tools/logs/page.tsx | 4 +- .../tools/tasks/_components/tasks-table.tsx | 70 +- .../app/[locale]/manage/tools/tasks/page.tsx | 8 +- .../_components/_delete-user-button.tsx | 11 +- .../_components/_general-settings-form.tsx | 42 +- .../_components/_profile-avatar-form.tsx | 8 +- .../general/_components/_reset-tours.tsx | 4 +- .../manage/users/[userId]/general/page.tsx | 15 +- .../[locale]/manage/users/[userId]/layout.tsx | 13 +- .../_components/_change-password-form.tsx | 17 +- .../manage/users/[userId]/security/page.tsx | 4 +- .../manage/users/_components/user-list.tsx | 8 +- .../_components/create-user-stepper.tsx | 28 +- .../create/_components/stepper-navigation.tsx | 11 +- .../app/[locale]/manage/users/create/page.tsx | 4 +- .../users/groups/[id]/_delete-group.tsx | 12 +- .../users/groups/[id]/_rename-group-form.tsx | 17 +- .../groups/[id]/_reserved-group-alert.tsx | 7 +- .../groups/[id]/_transfer-group-ownership.tsx | 14 +- .../manage/users/groups/[id]/layout.tsx | 8 +- .../groups/[id]/members/_add-group-member.tsx | 4 +- .../[id]/members/_remove-group-member.tsx | 8 +- .../manage/users/groups/[id]/members/page.tsx | 8 +- .../manage/users/groups/[id]/page.tsx | 6 +- .../permissions/_group-permission-form.tsx | 21 +- .../users/groups/[id]/permissions/page.tsx | 8 +- .../[id]/settings/_group-home-boards.tsx | 21 +- .../users/groups/[id]/settings/page.tsx | 9 +- .../[locale]/manage/users/groups/_client.tsx | 8 +- .../manage/users/groups/_groups-table.tsx | 19 +- .../app/[locale]/manage/users/groups/page.tsx | 9 +- .../_components/invite-create-button.tsx | 4 +- .../users/invites/_components/invite-list.tsx | 9 +- .../[locale]/manage/users/invites/page.tsx | 4 +- .../src/app/[locale]/manage/users/page.tsx | 9 +- .../app/[locale]/widgets/[kind]/_content.tsx | 3 +- .../src/components/access/access-settings.tsx | 4 +- .../components/access/access-table-rows.tsx | 4 +- .../components/access/group-access-form.tsx | 10 +- .../components/access/group-select-modal.tsx | 11 +- .../access/inherit-access-table.tsx | 4 +- .../components/access/user-access-form.tsx | 10 +- .../components/access/user-select-modal.tsx | 11 +- .../assistant-board-settings-tool.tsx | 12 +- .../components/assistant/assistant-gate.tsx | 4 +- .../assistant/assistant-human-tools.tsx | 8 +- .../components/assistant/assistant-panel.tsx | 90 +- .../assistant/assistant-provider.tsx | 10 +- .../assistant-runtime-provider.spec.ts | 2 +- .../assistant/assistant-runtime-provider.tsx | 6 +- .../assistant-spotlight-placeholder.ts | 4 +- .../assistant/assistant-spotlight.tsx | 6 +- .../assistant/assistant-widget-tool.tsx | 14 +- .../backup/backup-preview-panel.tsx | 23 +- .../backup/database-restore-flow.tsx | 7 +- .../backup/migration-progress-panel.tsx | 4 +- .../backup/restore-confirmation.tsx | 10 +- .../backup/restore-progress-panel.spec.tsx | 18 +- .../backup/restore-progress-panel.tsx | 10 +- .../components/backup/use-backup-analysis.ts | 4 +- .../components/board/board-empty-state.tsx | 4 +- .../board/board-setup-checklist.tsx | 9 +- .../components/board/items/item-content.tsx | 3 +- .../src/components/board/items/item-menu.tsx | 9 +- .../board/items/item-move-modal.tsx | 8 +- .../board/items/item-select-modal.spec.tsx | 1 - .../board/items/item-select-modal.tsx | 23 +- .../board/items/widget-context-menu.tsx | 16 +- .../board/items/widget-hover-overlay.tsx | 4 +- .../board/modals/board-rename-modal.tsx | 10 +- .../board/sections/container-section.tsx | 8 +- .../container/container-edit-modal.tsx | 20 +- .../sections/container/container-menu.tsx | 6 +- .../board/sections/empty-section.tsx | 4 +- .../board/sections/grid/fixed-grid-item.tsx | 3 +- .../board/sections/grid/grid-editor.tsx | 3 +- .../board/sections/use-open-section-apps.ts | 6 +- .../selection/board-selection-context.tsx | 12 +- .../selection/board-selection-toolbar.tsx | 10 +- .../board/selection/board-selection.spec.tsx | 1 - .../src/components/create/setup-analytics.ts | 6 +- .../custom-widget-import-dialog.tsx | 4 +- .../custom-widget-source-setup-dialog.tsx | 20 +- .../use-custom-widget-import.ts | 11 +- .../embedded-integration-edit-form.tsx | 13 +- .../integration-completion-modal.tsx | 36 +- .../integration/integration-select-modal.tsx | 17 +- .../src/components/layout/header/update.tsx | 4 +- .../src/components/manage/danger-zone.tsx | 4 +- .../navigation/dynamic-breadcrumb.spec.ts | 3 +- .../navigation/dynamic-breadcrumb.tsx | 74 +- .../src/components/onboarding/board-tour.tsx | 4 +- .../src/components/onboarding/manage-tour.tsx | 4 +- .../src/components/onboarding/tour-shell.tsx | 11 +- .../onboarding/tour-step-content.tsx | 4 +- .../src/components/user-avatar-menu.tsx | 8 +- .../workshop/workshop-code-viewer.tsx | 18 +- .../workshop/workshop-css-import-button.tsx | 6 +- .../workshop/workshop-report-modal.tsx | 7 +- .../workshop/workshop-screenshots.tsx | 4 +- .../components/workshop/workshop-session.tsx | 6 +- .../workshop/workshop-submission-card.tsx | 4 +- .../workshop/workshop-submission-grid.tsx | 7 +- .../workshop/workshop-vote-control.tsx | 4 +- packages/api/src/router/home.ts | 6 +- .../src/router/kubernetes/router/cluster.ts | 3 +- packages/definitions/src/assistant.ts | 14 + packages/definitions/src/index.ts | 2 + .../src/invariant-technical-labels.ts | 14 + packages/definitions/src/kubernetes.ts | 5 +- packages/definitions/src/widget-name.ts | 24 + packages/forms-collection/package.json | 1 + .../src/icon-picker/icon-picker.spec.ts | 2 +- .../src/icon-picker/icon-picker.tsx | 4 +- .../src/new-app/_app-new-form.tsx | 16 +- .../forms-collection/src/new-app/_form.tsx | 16 +- .../user-create-password-fields.tsx | 4 +- .../quick-add-app/quick-add-app-modal.tsx | 13 +- .../src/boards/add-board-modal.tsx | 32 +- .../src/boards/duplicate-board-modal.tsx | 19 +- .../confirm-docker-container-removal.spec.ts | 8 +- .../confirm-docker-container-removal.ts | 14 +- .../src/groups/add-group-modal.tsx | 17 +- .../src/invites/invite-copy-modal.tsx | 7 +- .../src/invites/invite-create-modal.tsx | 4 +- packages/onboarding/src/account.tsx | 6 +- packages/onboarding/src/finish.tsx | 4 +- packages/onboarding/src/onboarding-studio.tsx | 6 +- .../onboarding/src/service-url-template.tsx | 4 +- packages/onboarding/src/setup-studio.tsx | 37 +- packages/onboarding/src/welcome.tsx | 6 +- .../apps-search-group.tsx | 2 +- .../boards-search-group.tsx | 2 +- .../integrations-search-group.tsx | 2 +- .../children/preferences/action-row.tsx | 6 +- .../preferences/settings-children.tsx | 18 +- .../src/modes/command/global-group.tsx | 4 +- .../external/search-engines-search-group.tsx | 14 +- .../src/modes/help/home-empty-groups.tsx | 17 +- .../media/media-request-search-group.tsx | 6 +- .../src/modes/page/pages-search-group.tsx | 4 +- .../src/modes/preferences/groups.tsx | 34 +- .../modes/user-group/groups-search-group.tsx | 2 +- .../modes/user-group/users-search-group.tsx | 2 +- .../src/preferences/preference-registry.tsx | 21 +- packages/translation/src/client/index.ts | 3 - packages/translation/src/lang/en.json | 2214 +++++------------ packages/translation/src/server.ts | 5 +- packages/translation/src/type.ts | 4 +- packages/ui/src/components/beta-badge.tsx | 6 +- .../src/components/color-scheme-combobox.tsx | 4 +- .../password-requirements-popover.tsx | 6 +- .../integration-multi-select-grid.tsx | 3 +- .../integration-select-grid.tsx | 3 +- packages/widgets/package.json | 1 + packages/widgets/src/_inputs/common.tsx | 20 +- .../src/_inputs/widget-anchor-note-input.tsx | 8 +- .../widgets/src/_inputs/widget-app-input.tsx | 11 +- ...dget-custom-widget-configuration-input.tsx | 4 +- ...widget-custom-widget-select-input.spec.tsx | 2 +- .../widget-custom-widget-select-input.tsx | 4 +- .../widget-date-time-event-list-input.tsx | 4 +- .../widget-dynamic-multiselect-input.tsx | 7 +- .../_inputs/widget-dynamic-select-input.tsx | 6 +- .../widget-integration-multi-select-input.tsx | 14 +- .../widget-integration-select-input.tsx | 14 +- .../_inputs/widget-location-input.spec.tsx | 2 +- .../src/_inputs/widget-location-input.tsx | 10 +- .../src/_inputs/widget-multi-text-input.tsx | 4 +- ...widget-multiReleasesRepositories-input.tsx | 30 +- .../_inputs/widget-timezone-list-input.tsx | 4 +- .../src/_inputs/widget-umami-event-input.tsx | 11 +- .../widget-umami-event-names-input.tsx | 11 +- .../_inputs/widget-umami-website-input.tsx | 4 +- .../widgets/src/air-quality/component.tsx | 12 +- .../widgets/src/anchor-note/component.tsx | 20 +- packages/widgets/src/app/component.tsx | 7 +- packages/widgets/src/app/index.ts | 4 +- .../widgets/src/app/ping/ping-indicator.tsx | 6 +- .../src/archive-team-warrior/component.tsx | 7 +- packages/widgets/src/assistant/component.tsx | 4 +- .../src/audio-stats/audio-stats-content.tsx | 4 +- .../widgets/src/audio-stats/component.tsx | 9 +- packages/widgets/src/bazarr/component.tsx | 17 +- .../widgets/src/beszel-alerts/component.tsx | 4 +- packages/widgets/src/beszel-alerts/index.ts | 2 +- .../src/beszel-system-grid/component.tsx | 72 +- .../widgets/src/beszel-system-grid/index.ts | 2 +- .../src/beszel-system-stats/component.tsx | 7 +- .../widgets/src/beszel-system-stats/index.ts | 2 +- .../src/beszel-system-table/component.tsx | 47 +- .../widgets/src/beszel-system-table/index.ts | 29 +- .../widgets/src/beszel/_shared/disk-usage.tsx | 4 +- .../widgets/src/beszel/_shared/stats-view.tsx | 12 +- .../widgets/src/beszel/_shared/tooltip.tsx | 4 +- packages/widgets/src/bookmarks/add-button.tsx | 8 +- packages/widgets/src/bookmarks/index.tsx | 7 +- .../src/calendar/calendar-event-list.tsx | 6 +- packages/widgets/src/calendar/component.tsx | 10 +- packages/widgets/src/clock/advanced-view.tsx | 18 +- .../widgets/src/clock/time-of-day-bar.tsx | 4 +- .../widgets/src/clock/weather-summary.tsx | 4 +- packages/widgets/src/common/empty-state.tsx | 4 +- .../common/integration-error-indicator.tsx | 4 +- .../src/common/query-state-indicator.spec.ts | 2 +- .../src/common/query-state-indicator.tsx | 6 +- .../src/coolify/applications-section.tsx | 6 +- packages/widgets/src/coolify/component.tsx | 11 +- .../widgets/src/coolify/instance-card.tsx | 8 +- packages/widgets/src/coolify/resource-row.tsx | 4 +- .../widgets/src/coolify/servers-section.tsx | 15 +- .../widgets/src/coolify/services-section.tsx | 8 +- .../src/coolify/single-instance-layout.tsx | 6 +- packages/widgets/src/countdown/component.tsx | 10 +- packages/widgets/src/custom-api/component.tsx | 6 +- .../src/custom-api/custom-jsx-display.spec.ts | 2 +- .../src/custom-api/custom-jsx-display.tsx | 10 +- .../custom-api/widget-definition-context.tsx | 7 +- .../src/dns-hole/controls/TimerModal.tsx | 13 +- .../src/dns-hole/controls/component.tsx | 44 +- .../src/dns-hole/summary/component.tsx | 11 +- .../widgets/src/dns-hole/summary/index.ts | 6 +- packages/widgets/src/docker/component.tsx | 32 +- packages/widgets/src/docker/index.ts | 11 +- packages/widgets/src/downloads/component.tsx | 36 +- .../widgets/src/errors/base-component.tsx | 5 +- packages/widgets/src/firewall/component.tsx | 48 +- .../widgets/src/firewall/firewall-version.tsx | 4 +- .../cluster/cluster-health.tsx | 19 +- .../cluster/resource-popover.tsx | 10 +- .../cluster/resource-table.tsx | 11 +- .../src/health-monitoring/component.tsx | 6 +- .../src/health-monitoring/system-health.tsx | 38 +- packages/widgets/src/iframe/component.tsx | 12 +- packages/widgets/src/iframe/index.ts | 2 +- .../src/immich/album-carousel/component.tsx | 18 +- .../src/immich/album-carousel/index.ts | 4 +- .../widgets/src/immich/albums-query.spec.ts | 1 - .../src/immich/server-stats/component.tsx | 21 +- .../widgets/src/indexer-manager/component.tsx | 19 +- .../widgets/src/media-missing/component.tsx | 18 +- .../widgets/src/media-releases/component.tsx | 16 +- .../src/media-requests/list/component.tsx | 10 +- .../src/media-requests/stats/component.tsx | 9 +- .../src/media-server/component.spec.tsx | 2 +- .../widgets/src/media-server/component.tsx | 14 +- .../media-transcoding/health-check-status.tsx | 5 +- .../media-transcoding/panels/queue.panel.tsx | 9 +- .../panels/statistics.panel.tsx | 15 +- .../panels/workers.panel.tsx | 9 +- .../src/minecraft/server-status/component.tsx | 23 +- .../src/modals/embedded-app-edit-form.tsx | 19 +- .../modals/widget-advanced-options-modal.tsx | 11 +- .../widgets/src/modals/widget-edit-modal.tsx | 28 +- .../network-status/component.tsx | 11 +- .../network-status/variants/wifi-variant.tsx | 4 +- .../network-status/variants/wired-variant.tsx | 4 +- .../network-controller/summary/component.tsx | 48 +- packages/widgets/src/notebook/notebook.tsx | 98 +- .../widgets/src/notifications/component.tsx | 11 +- packages/widgets/src/option-translation.ts | 96 + .../widgets/src/paperless-ngx/component.tsx | 6 +- packages/widgets/src/patchmon/component.tsx | 14 +- .../src/patchmon/os-distribution-section.tsx | 4 +- packages/widgets/src/releases/component.tsx | 8 +- packages/widgets/src/rssFeed/component.tsx | 19 +- .../src/smart-home/entity-state/component.tsx | 9 +- .../execute-automation/component.tsx | 32 +- .../src/speedtest-tracker/averages.tsx | 4 +- .../src/speedtest-tracker/component.tsx | 4 +- .../src/speedtest-tracker/latest-result.tsx | 4 +- .../src/speedtest-tracker/recent-results.tsx | 12 +- packages/widgets/src/stocks/component.tsx | 7 +- .../widgets/src/system-disks/component.tsx | 12 +- .../chart/combined-network-traffic.tsx | 4 +- .../src/system-resources/chart/cpu-chart.tsx | 5 +- .../src/system-resources/chart/gpu-chart.tsx | 5 +- .../system-resources/chart/memory-chart.tsx | 4 +- .../chart/network-traffic.tsx | 4 +- .../src/system-resources/component.tsx | 4 +- .../widgets/src/system-resources/index.ts | 9 +- packages/widgets/src/test/translation.spec.ts | 38 +- packages/widgets/src/timer/component.tsx | 16 +- packages/widgets/src/timetable/component.tsx | 15 +- packages/widgets/src/timetable/index.ts | 4 +- packages/widgets/src/tracearr/component.tsx | 9 +- .../src/tracearr/recent-activity-section.tsx | 4 +- .../widgets/src/tracearr/stats-section.tsx | 22 +- .../widgets/src/tracearr/streams-section.tsx | 6 +- .../src/tracearr/violations-section.tsx | 4 +- packages/widgets/src/traefik/component.tsx | 56 +- packages/widgets/src/umami/component.tsx | 4 +- packages/widgets/src/umami/umami-content.tsx | 4 +- .../src/umami/umami-events-content.tsx | 4 +- packages/widgets/src/umami/umami-top-list.tsx | 16 +- packages/widgets/src/umami/umami-utils.ts | 7 +- packages/widgets/src/ups/component.tsx | 4 +- .../widgets/src/uptime-kuma/component.tsx | 11 +- packages/widgets/src/video/component.tsx | 11 +- packages/widgets/src/vpn/component.tsx | 6 +- packages/widgets/src/vpn/vpn-card.tsx | 8 +- packages/widgets/src/weather/advanced.tsx | 8 +- packages/widgets/src/weather/compact.tsx | 6 +- packages/widgets/src/weather/component.tsx | 4 +- packages/widgets/src/weather/details.tsx | 6 +- packages/widgets/src/weather/icon.tsx | 6 +- .../widgets/src/widget-integration-select.tsx | 14 +- packages/widgets/src/wud/component.tsx | 6 +- packages/widgets/src/wud/index.ts | 2 +- pnpm-lock.yaml | 3 + 449 files changed, 3515 insertions(+), 3922 deletions(-) create mode 100644 packages/definitions/src/invariant-technical-labels.ts create mode 100644 packages/definitions/src/widget-name.ts create mode 100644 packages/widgets/src/option-translation.ts diff --git a/apps/nextjs/src/app/[locale]/auth/invite/[id]/_registration-form.tsx b/apps/nextjs/src/app/[locale]/auth/invite/[id]/_registration-form.tsx index 9bfe3bfd5..677356dd4 100644 --- a/apps/nextjs/src/app/[locale]/auth/invite/[id]/_registration-form.tsx +++ b/apps/nextjs/src/app/[locale]/auth/invite/[id]/_registration-form.tsx @@ -7,7 +7,7 @@ import type { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; import { useZodForm } from "@homarr/form"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; import { CustomPasswordInput } from "@homarr/ui"; import { userRegistrationSchema } from "@homarr/validation/user"; @@ -19,7 +19,7 @@ interface RegistrationFormProps { } export const RegistrationForm = ({ invite }: RegistrationFormProps) => { - const t = useScopedI18n("user"); + const t = useI18n("user"); const router = useRouter(); const { mutate, isPending } = clientApi.user.register.useMutation(); const form = useZodForm(userRegistrationSchema, { diff --git a/apps/nextjs/src/app/[locale]/auth/invite/[id]/page.tsx b/apps/nextjs/src/app/[locale]/auth/invite/[id]/page.tsx index 475464aff..0f7ddefc7 100644 --- a/apps/nextjs/src/app/[locale]/auth/invite/[id]/page.tsx +++ b/apps/nextjs/src/app/[locale]/auth/invite/[id]/page.tsx @@ -6,7 +6,7 @@ import { isProviderEnabled } from "@homarr/auth/server"; import { and, db, eq } from "@homarr/db"; import { invites } from "@homarr/db/schema"; import { OnboardingAuthShell } from "@homarr/onboarding"; -import { getScopedI18n } from "@homarr/translation/server"; +import { getI18n } from "@homarr/translation/server"; import { RegistrationForm } from "./_registration-form"; @@ -45,7 +45,7 @@ export default async function InviteUsagePage(props: InviteUsagePageProps) { if (!invite || invite.expirationDate < new Date()) notFound(); - const t = await getScopedI18n("user.page.invite"); + const t = await getI18n("user.page.invite"); return ( { - const t = useScopedI18n("user"); + const t = useI18n("user"); const searchParams = useSearchParams(); const isError = searchParams.has("error"); const router = useRouter(); @@ -196,7 +196,7 @@ interface PasswordForgottenCollapseProps { } const PasswordForgottenCollapse = ({ username }: PasswordForgottenCollapseProps) => { const [visible, { toggle }] = useDisclosure(false); - const tForgotPassword = useScopedI18n("user.action.login.forgotPassword"); + const tForgotPassword = useI18n("user.action.login.forgotPassword"); const commandUsername = username.trim().length >= 1 ? username.trim() : ""; diff --git a/apps/nextjs/src/app/[locale]/auth/login/page.tsx b/apps/nextjs/src/app/[locale]/auth/login/page.tsx index 8286c6205..b1c0a1d1b 100644 --- a/apps/nextjs/src/app/[locale]/auth/login/page.tsx +++ b/apps/nextjs/src/app/[locale]/auth/login/page.tsx @@ -5,7 +5,7 @@ import { IconLogin } from "@tabler/icons-react"; import { env } from "@homarr/auth/env"; import { auth } from "@homarr/auth/next"; import { OnboardingAuthShell } from "@homarr/onboarding"; -import { getScopedI18n } from "@homarr/translation/server"; +import { getI18n } from "@homarr/translation/server"; import { sanitizeRedirectionUrl } from "@homarr/validation/redirection-url"; import { env as appEnv } from "~/env"; @@ -26,7 +26,7 @@ export default async function Login(props: LoginProps) { redirect(sanitizeRedirectionUrl(searchParams.callbackUrl)); } - const t = await getScopedI18n("user.page.login"); + const t = await getI18n("user.page.login"); return ( }> diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/[name]/not-found.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/not-found.tsx index 9c398669b..12128b7e4 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/[name]/not-found.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/[name]/not-found.tsx @@ -1,11 +1,11 @@ import { IconLayoutOff } from "@tabler/icons-react"; -import { getScopedI18n } from "@homarr/translation/server"; +import { getI18n } from "@homarr/translation/server"; import { BoardNotFound } from "~/components/board/not-found"; export default async function BoardNotFoundPage() { - const tNotFound = await getScopedI18n("board.error.notFound"); + const tNotFound = await getI18n("board.error.notFound"); return ( { const board = useRequiredBoard(); - const t = useScopedI18n("board.landmark"); - const tPreview = useScopedI18n("board.setting.section.layout.preview"); - const tTips = useScopedI18n("tips"); + const t = useI18n("board.landmark"); + const tPreview = useI18n("board.setting.section.layout.preview"); + const tTips = useI18n("tips"); const [isEditMode] = useEditMode(); const currentLayoutId = useCurrentLayout(); const initialViewportWidth = useInitialViewportWidth(); diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx index 9833cc4e2..9cf907b5e 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx @@ -104,10 +104,10 @@ export const createBoardContentPage = >( generateMetadataAsync: async ({ params }: { params: Promise }): Promise => { try { const board = await getInitialBoard(await params); - const t = await getI18n(); + const t = await getI18n("board"); return { - title: board.metaTitle ?? createMetaTitle(t("board.content.metaTitle", { boardName: board.name })), + title: board.metaTitle ?? createMetaTitle(t("content.metaTitle", { boardName: board.name })), icons: { icon: !isNullOrWhitespace(board.faviconImageUrl) ? board.faviconImageUrl : undefined, apple: !isNullOrWhitespace(board.faviconImageUrl) ? board.faviconImageUrl : undefined, diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_edit-actions.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_edit-actions.tsx index 0c9404897..29dbc8ac0 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_edit-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_edit-actions.tsx @@ -29,7 +29,9 @@ const AddMenu = () => { const { openModal: openIntegrationSelectModal } = useModalAction(IntegrationSelectModal); const { addContainer } = useContainerActions(); const { createItem } = useItemActions(); - const t = useI18n(); + const tBoard = useI18n("board"); + const tIntegration = useI18n("integration"); + const tContainer = useI18n("section.container"); const pathname = usePathname(); const router = useRouter(); const searchParams = useSearchParams(); @@ -77,7 +79,7 @@ const AddMenu = () => { return ( - + @@ -86,19 +88,19 @@ const AddMenu = () => { } onClick={handleSelectItem}> - {t("item.action.create")} + {tBoard("emptyState.addWidget")} } onClick={handleSelectApp}> - {t("app.action.add")} + {tBoard("emptyState.addApp")} {session?.user.permissions.includes("integration-create") && ( } onClick={handleAddIntegration}> - {t("integration.action.create")} + {tIntegration("action.create")} )} } onClick={addContainer}> - {t("section.container.action.create")} + {tContainer("action.create")} diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx index 397d5274f..f85d78d27 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx @@ -24,7 +24,7 @@ import { env } from "@homarr/common/env"; import { hotkeys } from "@homarr/definitions"; import { useConfirmModal } from "@homarr/modals"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; -import { useI18n, useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; import { Link } from "@homarr/ui"; import { useBoardPermissions } from "~/components/board/permissions/client"; @@ -53,7 +53,7 @@ const BoardEditActions = dynamic(loadEditActionsAsync, { ssr: false }); export const BoardContentHeaderActions = ({ demoReadOnly }: { demoReadOnly: boolean }) => { const [isEditMode] = useEditMode(); const board = useRequiredBoard(); - const t = useI18n(); + const t = useI18n("board"); const { hasChangeAccess } = useBoardPermissions(board); if (!hasChangeAccess) { @@ -68,7 +68,7 @@ export const BoardContentHeaderActions = ({ demoReadOnly }: { demoReadOnly: bool {!demoReadOnly && ( - + @@ -88,8 +88,9 @@ const EditModeMenu = ({ demoReadOnly }: { demoReadOnly: boolean }) => { const router = useRouter(); const searchParams = useSearchParams(); const { openConfirmModal } = useConfirmModal(); - const commonT = useI18n(); - const t = useScopedI18n("board.action.edit"); + const tCommon = useI18n("common"); + const tBoardSetting = useI18n("board.setting"); + const t = useI18n("board.action.edit"); const returnToLayoutSettings = isEditMode && searchParams.get("edit") === "true" && @@ -104,7 +105,7 @@ const EditModeMenu = ({ demoReadOnly }: { demoReadOnly: boolean }) => { }, onSuccess() { showSuccessNotification({ - title: t("notification.success.title"), + title: tCommon("notification.update.success"), message: t("notification.success.message"), }); void utils.board.getBoardByName.invalidate({ name: board.name }); @@ -115,7 +116,7 @@ const EditModeMenu = ({ demoReadOnly }: { demoReadOnly: boolean }) => { }, onError() { showErrorNotification({ - title: t("notification.error.title"), + title: tCommon("notification.update.error"), message: t("notification.error.message"), }); }, @@ -178,9 +179,9 @@ const EditModeMenu = ({ demoReadOnly }: { demoReadOnly: boolean }) => { const cancelLayoutEdit = useCallback(() => { openConfirmModal({ - title: commonT("board.setting.section.layout.edit.cancel.title"), - children: commonT("board.setting.section.layout.edit.cancel.message"), - confirmProps: { children: commonT("common.action.discard"), color: "red" }, + title: tBoardSetting("section.layout.edit.cancel.title"), + children: tBoardSetting("section.layout.edit.cancel.message"), + confirmProps: { children: tCommon("action.discard"), color: "red" }, onConfirm() { void (async () => { await utils.board.getBoardByName.invalidate({ name: board.name }); @@ -189,7 +190,7 @@ const EditModeMenu = ({ demoReadOnly }: { demoReadOnly: boolean }) => { })(); }, }); - }, [board.name, close, commonT, openConfirmModal, router, utils.board.getBoardByName]); + }, [board.name, close, openConfirmModal, router, tBoardSetting, tCommon, utils.board.getBoardByName]); useHotkeys([[hotkeys.toggleBoardEdit, () => void toggle()]]); usePreventLeaveWithDirty(isEditMode); @@ -200,14 +201,14 @@ const EditModeMenu = ({ demoReadOnly }: { demoReadOnly: boolean }) => { saveBoard(board)} loading={isPending} - aria-label={commonT("board.setting.section.layout.edit.save")} + aria-label={tBoardSetting("section.layout.edit.save")} > @@ -235,7 +236,7 @@ const EditModeMenu = ({ demoReadOnly }: { demoReadOnly: boolean }) => { }; const SelectBoardsMenu = () => { - const t = useI18n(); + const t = useI18n("board"); const [isOpen, setIsOpen] = useState(false); const utils = clientApi.useUtils(); const { data: boards = [], isPending } = clientApi.board.getAllBoards.useQuery(undefined, { enabled: isOpen }); @@ -246,7 +247,7 @@ const SelectBoardsMenu = () => { - + @@ -277,7 +278,8 @@ const SelectBoardsMenu = () => { const anchorSelector = "a[href]:not([target='_blank'])"; const usePreventLeaveWithDirty = (isDirty: boolean) => { - const t = useI18n(); + const tBoard = useI18n("board"); + const tCommon = useI18n("common"); const { openConfirmModal } = useConfirmModal(); const router = useRouter(); @@ -293,13 +295,13 @@ const usePreventLeaveWithDirty = (isDirty: boolean) => { event.stopPropagation(); openConfirmModal({ - title: t("board.action.edit.confirmLeave.title"), - children: t("board.action.edit.confirmLeave.message"), + title: tBoard("action.edit.confirmLeave.title"), + children: tBoard("action.edit.confirmLeave.message"), onConfirm() { router.push(target.href); }, confirmProps: { - children: t("common.action.discard"), + children: tCommon("action.discard"), }, }); }; @@ -325,5 +327,5 @@ const usePreventLeaveWithDirty = (isDirty: boolean) => { window.removeEventListener("popstate", handlePopState); window.removeEventListener("beforeunload", handleBeforeUnload); }; - }, [isDirty, openConfirmModal, router, t]); + }, [isDirty, openConfirmModal, router, tBoard, tCommon]); }; diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/not-found.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/not-found.tsx index 5ab6c8c61..016d0163e 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/not-found.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/not-found.tsx @@ -16,15 +16,15 @@ export default async function NotFoundBoardHomePage() { const getPropsAsync = async (): Promise => { const boardCount = await db.$count(boards); - const t = await getI18n(); + const t = await getI18n("board"); if (boardCount === 0) { return { icon: { src: "/favicon.ico", alt: "Homarr logo" }, - title: t("board.error.noBoard.title"), - description: t("board.error.noBoard.description"), - link: { label: t("board.error.noBoard.link"), href: "/manage/boards" }, - notice: t("board.error.noBoard.notice"), + title: t("error.noBoard.title"), + description: t("error.noBoard.description"), + link: { label: t("error.noBoard.link"), href: "/manage/boards" }, + notice: t("error.noBoard.notice"), }; } @@ -39,9 +39,9 @@ const getPropsAsync = async (): Promise => { return { icon: IconHomeOff, - title: t(`board.error.homeBoard.title`), - description: t(`board.error.homeBoard.${type}.description`), - link: { label: t(`board.error.homeBoard.${type}.link`), href }, - notice: t(`board.error.homeBoard.${type}.notice`), + title: t("error.homeBoard.title"), + description: t(`error.homeBoard.${type}.description` as never), + link: { label: t(`error.homeBoard.${type}.link` as never), href }, + notice: t(`error.homeBoard.${type}.notice` as never), }; }; diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_appereance.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_appereance.tsx index b0e75d34d..674e5f29b 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/_appereance.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/_appereance.tsx @@ -37,22 +37,23 @@ const progressPercentageLabel = (value: number) => `${value}%`; export const ColorSettingsContent = ({ form }: Props) => { const [showPreview, { toggle }] = useDisclosure(false); - const t = useI18n(); + const tBoard = useI18n("board"); + const tCommon = useI18n("common"); const theme = useMantineTheme(); return ( - + - + - + - {showPreview ? t("common.preview.hide") : t("common.preview.show")} + {showPreview ? tCommon("preview.hide") : tCommon("preview.show")} @@ -63,7 +64,7 @@ export const ColorSettingsContent = ({ form }: Props) => { - + { color[6])} flex={1} @@ -90,20 +91,20 @@ export const ColorSettingsContent = ({ form }: Props) => { onClick={() => form.setFieldValue("iconColor", "")} disabled={!form.values.iconColor} > - {t("board.field.clearColor.label")} + {tBoard("field.clearColor.label")} } value={values[kind] ?? ""} onChange={(event) => setValues((current) => ({ ...current, [kind]: event.currentTarget.value }))} diff --git a/apps/nextjs/src/app/[locale]/manage/_components/hero-banner.tsx b/apps/nextjs/src/app/[locale]/manage/_components/hero-banner.tsx index e4ea2fc02..52340a72e 100644 --- a/apps/nextjs/src/app/[locale]/manage/_components/hero-banner.tsx +++ b/apps/nextjs/src/app/[locale]/manage/_components/hero-banner.tsx @@ -1,12 +1,12 @@ import { Box, Group, Image, Stack, Title } from "@mantine/core"; -import { getScopedI18n } from "@homarr/translation/server"; +import { getI18n } from "@homarr/translation/server"; import { IntegrationMarquee } from "@homarr/ui"; import classes from "./hero-banner.module.css"; export const HeroBanner = async () => { - const t = await getScopedI18n("management.page.home"); + const t = await getI18n("management.page.home"); return ( diff --git a/apps/nextjs/src/app/[locale]/manage/about/page.tsx b/apps/nextjs/src/app/[locale]/manage/about/page.tsx index 9577d39a0..d0388f427 100644 --- a/apps/nextjs/src/app/[locale]/manage/about/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/about/page.tsx @@ -35,7 +35,7 @@ import { import { capitalize, objectEntries } from "@homarr/common"; import { hotkeys } from "@homarr/definitions"; -import { getScopedI18n } from "@homarr/translation/server"; +import { getI18n } from "@homarr/translation/server"; import { homarrLogoPath } from "~/components/layout/logo/homarr-logo"; import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; @@ -47,7 +47,7 @@ import crowdinContributors from "@static-data/translators.json"; import classes from "./about.module.css"; export async function generateMetadata() { - const t = await getScopedI18n("management"); + const t = await getI18n("management"); return { title: createMetaTitle(t("metaTitle")), @@ -55,7 +55,7 @@ export async function generateMetadata() { } export default async function AboutPage() { - const t = await getScopedI18n("management.page.about"); + const t = await getI18n("management.page.about"); const version = getPackageVersion(); const dependencies = await getDependenciesAsync(); diff --git a/apps/nextjs/src/app/[locale]/manage/apps/_app-delete-button.tsx b/apps/nextjs/src/app/[locale]/manage/apps/_app-delete-button.tsx index 190737611..477dffa24 100644 --- a/apps/nextjs/src/app/[locale]/manage/apps/_app-delete-button.tsx +++ b/apps/nextjs/src/app/[locale]/manage/apps/_app-delete-button.tsx @@ -9,15 +9,15 @@ import { clientApi } from "@homarr/api/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; import { useConfirmModal } from "@homarr/modals"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; interface AppDeleteButtonProps { app: RouterOutputs["app"]["all"][number]; } export const AppDeleteButton = ({ app }: AppDeleteButtonProps) => { - const t = useScopedI18n("app.page.delete"); - const tList = useScopedI18n("app.page.list.action"); + const t = useI18n("app.page.delete"); + const tCommon = useI18n("common"); const { openConfirmModal } = useConfirmModal(); const { mutate, isPending } = clientApi.app.delete.useMutation(); @@ -33,14 +33,14 @@ export const AppDeleteButton = ({ app }: AppDeleteButtonProps) => { { onSuccess: () => { showSuccessNotification({ - title: t("notification.success.title"), + title: tCommon("notification.delete.success"), message: t("notification.success.message"), }); void revalidatePathActionAsync("/manage/apps"); }, onError: () => { showErrorNotification({ - title: t("notification.error.title"), + title: tCommon("notification.delete.error"), message: t("notification.error.message"), }); }, @@ -48,7 +48,7 @@ export const AppDeleteButton = ({ app }: AppDeleteButtonProps) => { ); }, }); - }, [app, mutate, t, openConfirmModal]); + }, [app, mutate, t, tCommon, openConfirmModal]); return ( { color="red" size={44} onClick={onClick} - aria-label={tList("delete", { name: app.name })} + aria-label={tCommon("action.deleteNamed", { name: app.name })} > diff --git a/apps/nextjs/src/app/[locale]/manage/apps/edit/[id]/_app-edit-form.tsx b/apps/nextjs/src/app/[locale]/manage/apps/edit/[id]/_app-edit-form.tsx index 0f14be6b4..cf4be14b8 100644 --- a/apps/nextjs/src/app/[locale]/manage/apps/edit/[id]/_app-edit-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/apps/edit/[id]/_app-edit-form.tsx @@ -9,7 +9,7 @@ import { clientApi } from "@homarr/api/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; import { AppForm } from "@homarr/forms-collection"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; -import { useI18n, useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; import type { appManageSchema } from "@homarr/validation/app"; interface AppEditFormProps { @@ -17,14 +17,14 @@ interface AppEditFormProps { } export const AppEditForm = ({ app }: AppEditFormProps) => { - const tScoped = useScopedI18n("app.page.edit.notification"); - const t = useI18n(); + const tScoped = useI18n("app.page.edit.notification"); + const tCommon = useI18n("common"); const router = useRouter(); const { mutate, isPending } = clientApi.app.update.useMutation({ onSuccess: () => { showSuccessNotification({ - title: tScoped("success.title"), + title: tCommon("notification.update.success"), message: tScoped("success.message"), }); void revalidatePathActionAsync("/manage/apps").then(() => { @@ -33,7 +33,7 @@ export const AppEditForm = ({ app }: AppEditFormProps) => { }, onError: () => { showErrorNotification({ - title: tScoped("error.title"), + title: tCommon("notification.update.error"), message: tScoped("error.message"), }); }, @@ -52,7 +52,7 @@ export const AppEditForm = ({ app }: AppEditFormProps) => { return ( ) : ( ))} diff --git a/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration.tsx b/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration.tsx index 4c1b86132..9c757048b 100644 --- a/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration.tsx +++ b/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration.tsx @@ -51,7 +51,7 @@ import { } from "@homarr/definitions"; import type { AssistantProvider, AssistantProviderCategory } from "@homarr/definitions"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; import { getWorkshopAssistantProviderUrl } from "~/components/workshop/workshop-client"; @@ -140,7 +140,8 @@ const ConfigurationSkeleton = ({ label }: { label: string }) => ( ); export const AssistantConfiguration = () => { - const t = useScopedI18n("management.page.settings.section.assistant"); + const t = useI18n("management.page.settings.section.assistant"); + const tCommon = useI18n("common"); const router = useRouter(); const utils = clientApi.useUtils(); const { data: configuration, isLoading } = clientApi.assistant.getAdminConfiguration.useQuery(); @@ -208,7 +209,7 @@ export const AssistantConfiguration = () => { .filter((providerId) => assistantProviderPresets[providerId].category === category) .map((providerId) => ({ value: providerId, - label: t(`provider.options.${providerId}.label`), + label: assistantProviderPresets[providerId].label ?? t("provider.options.custom.label"), })); return { group: t(`provider.groups.${category}`), items }; @@ -372,7 +373,7 @@ export const AssistantConfiguration = () => { - {t(`provider.options.${provider}.label`)} + {assistantProviderPresets[provider].label ?? t("provider.options.custom.label")} { description={t("connection.description")} status={ - {t(`provider.options.${provider}.label`)} + {assistantProviderPresets[provider].label ?? t("provider.options.custom.label")} } > @@ -758,7 +759,7 @@ export const AssistantConfiguration = () => { {t("serverTools.webSearch.label")} - {t("serverTools.beta")} + {tCommon("beta")} diff --git a/apps/nextjs/src/app/[locale]/manage/assistant/page.tsx b/apps/nextjs/src/app/[locale]/manage/assistant/page.tsx index d8482fcfc..479beeec1 100644 --- a/apps/nextjs/src/app/[locale]/manage/assistant/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/assistant/page.tsx @@ -2,13 +2,13 @@ import { notFound } from "next/navigation"; import { Text } from "@mantine/core"; import { auth } from "@homarr/auth/next"; -import { getScopedI18n } from "@homarr/translation/server"; +import { getI18n } from "@homarr/translation/server"; import { ManagePageLayout } from "~/components/manage/manage-page-layout"; import { AssistantConfiguration } from "./_components/assistant-configuration"; export async function generateMetadata() { - const t = await getScopedI18n("management.page.settings.section.assistant"); + const t = await getI18n("management.page.settings.section.assistant"); return { title: `${t("title")} • Homarr`, }; @@ -21,7 +21,7 @@ export default async function AssistantPage() { notFound(); } - const t = await getScopedI18n("management.page.settings.section.assistant"); + const t = await getI18n("management.page.settings.section.assistant"); return ( diff --git a/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx b/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx index 744f998d5..0d15e68a1 100644 --- a/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx +++ b/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx @@ -10,7 +10,7 @@ import { useSession } from "@homarr/auth/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; import { useConfirmModal, useModalAction } from "@homarr/modals"; import { DuplicateBoardModal } from "@homarr/modals-collection"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; import { Link } from "@homarr/ui"; import { useBoardPermissions } from "~/components/board/permissions/client"; @@ -28,8 +28,9 @@ interface BoardCardMenuDropdownProps { } export const BoardCardMenuDropdown = ({ board }: BoardCardMenuDropdownProps) => { - const t = useScopedI18n("management.page.board.action"); - const tCommon = useScopedI18n("common"); + const t = useI18n("management.page.board.action"); + const tRoot = useI18n(); + const tCommon = useI18n("common"); const { hasFullAccess, hasChangeAccess } = useBoardPermissions(board); const { data: session } = useSession(); @@ -57,7 +58,7 @@ export const BoardCardMenuDropdown = ({ board }: BoardCardMenuDropdownProps) => const handleDeletion = useCallback(() => { openConfirmModal({ - title: t("delete.confirm.title"), + title: tCommon("action.deleteNamed", { name: board.name }), children: t("delete.confirm.description", { name: board.name, }), @@ -68,7 +69,7 @@ export const BoardCardMenuDropdown = ({ board }: BoardCardMenuDropdownProps) => }); }, }); - }, [board.id, board.name, deleteBoardMutation, openConfirmModal, t]); + }, [board.id, board.name, deleteBoardMutation, openConfirmModal, t, tCommon]); const handleSetHomeBoard = useCallback(async () => { await setHomeBoardMutation.mutateAsync({ id: board.id }); @@ -97,7 +98,7 @@ export const BoardCardMenuDropdown = ({ board }: BoardCardMenuDropdownProps) => {session?.user.permissions.includes("board-create") && ( }> - {t("duplicate.label")} + {tRoot("board.action.duplicate.title")} )} {hasChangeAccess && ( @@ -122,7 +123,7 @@ export const BoardCardMenuDropdown = ({ board }: BoardCardMenuDropdownProps) => onClick={handleDeletion} disabled={deleteBoardMutation.isPending} > - {t("delete.label")} + {tCommon("action.delete")} )} diff --git a/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx b/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx index 77fed0399..f6c9ed735 100644 --- a/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx +++ b/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx @@ -10,13 +10,13 @@ import { useI18n } from "@homarr/translation/client"; import { ManageMobilePrimaryAction } from "~/components/manage/manage-mobile-primary-action"; export const CreateBoardButton = () => { - const t = useI18n(); + const t = useI18n("management.page.board.action"); const { openModal } = useModalAction(AddBoardModal); return ( ); diff --git a/apps/nextjs/src/app/[locale]/manage/boards/page.tsx b/apps/nextjs/src/app/[locale]/manage/boards/page.tsx index 4fb20c461..c20cf6c74 100644 --- a/apps/nextjs/src/app/[locale]/manage/boards/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/boards/page.tsx @@ -25,7 +25,7 @@ import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; import { auth } from "@homarr/auth/next"; import { constructBoardPermissions } from "@homarr/auth/shared"; -import { getScopedI18n } from "@homarr/translation/server"; +import { getI18n } from "@homarr/translation/server"; import { Link, UserAvatar } from "@homarr/ui"; import { BoardLayoutThumbnail } from "~/components/board/board-layout-thumbnail"; @@ -36,7 +36,7 @@ import { BoardCardMenuDropdown } from "./_components/board-card-menu-dropdown"; import { CreateBoardButton } from "./_components/create-board-button"; export default async function ManageBoardsPage() { - const t = await getScopedI18n("management.page.board"); + const t = await getI18n("management.page.board"); const session = await auth(); const boards = await api.board.getManageOverview(); const canCreateBoards = session?.user.permissions.includes("board-create"); diff --git a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_beta-banner.tsx b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_beta-banner.tsx index c26960c4d..81e38ec33 100644 --- a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_beta-banner.tsx +++ b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_beta-banner.tsx @@ -3,12 +3,12 @@ import { Alert, Anchor, Text } from "@mantine/core"; import { IconFlask } from "@tabler/icons-react"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; const FEEDBACK_URL = "https://github.com/homarr-labs/homarr/discussions/categories/custom-widgets"; export function CustomWidgetBetaBanner() { - const t = useScopedI18n("customWidget.betaBanner"); + const t = useI18n("customWidget.betaBanner"); return ( }> diff --git a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_code-editor.tsx b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_code-editor.tsx index 82d9db64a..275ab1897 100644 --- a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_code-editor.tsx +++ b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_code-editor.tsx @@ -3,20 +3,22 @@ import type { ComponentProps } from "react"; import { CustomWidgetCodeEditor } from "@homarr/custom-widgets/workbench"; -import { useScopedI18n } from "@homarr/translation/client"; +import { invariantTechnicalLabels } from "@homarr/definitions"; +import { useI18n } from "@homarr/translation/client"; type SharedEditorProps = ComponentProps; type CodeEditorProps = Omit; export function CodeEditor(props: CodeEditorProps) { - const t = useScopedI18n("customWidget.editor"); + const t = useI18n("customWidget.editor"); + const tCommon = useI18n("common"); return ( t("componentReference.count", { count }), insertStarter: t("action.insertStarter"), format: t("action.format"), - copy: t("action.copy"), + copy: tCommon("action.copy"), copied: t("action.copied"), schema: t("action.schema"), - schemaTab: t("reference.schema"), + schemaTab: invariantTechnicalLabels.jsonSchema, minimalTab: t("reference.minimal"), fullTab: t("reference.full"), errors: (count) => t("status.errors", { count }), diff --git a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.tsx b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.tsx index 2cc4cb1e4..2e05917c7 100644 --- a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.tsx +++ b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.tsx @@ -5,7 +5,7 @@ import { IconCopy, IconRobot } from "@tabler/icons-react"; import { buildCustomWidgetAiPrompt } from "@homarr/custom-widgets/authoring-prompt"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; import { useOptionalHomarrAssistant } from "~/components/assistant/assistant-context"; @@ -22,7 +22,7 @@ export const CopyAiPromptButton = ({ request, documentationUrl, }: CopyAiPromptButtonProps) => { - const t = useScopedI18n("customWidget"); + const t = useI18n("customWidget"); const assistant = useOptionalHomarrAssistant(); const prompt = () => buildCustomWidgetAiPrompt(undefined, rawResponse, currentConfig, request, documentationUrl); diff --git a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-actions.tsx b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-actions.tsx index 02ee78c46..8e376330b 100644 --- a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-actions.tsx +++ b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-actions.tsx @@ -22,7 +22,7 @@ import { formatCustomWidgetImportIssues, parseCustomWidgetClipboardDetailed } fr import type { HomarrCustomWidgetV2 } from "@homarr/custom-widgets/core"; import { useConfirmModal } from "@homarr/modals"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; import { Link } from "@homarr/ui"; import { CustomWidgetImportDialog } from "~/components/custom-widgets/custom-widget-import-dialog"; @@ -39,7 +39,7 @@ interface WidgetRef { } export const CustomWidgetRowActions = ({ widget }: { widget: WidgetRef }) => { - const t = useScopedI18n("customWidget"); + const t = useI18n("customWidget"); const { openConfirmModal } = useConfirmModal(); const deleteMutation = clientApi.customWidget.delete.useMutation(); const duplicateMutation = clientApi.customWidget.duplicate.useMutation(); diff --git a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-advanced-manifest.tsx b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-advanced-manifest.tsx index 2f2f42e21..4e558f880 100644 --- a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-advanced-manifest.tsx +++ b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-advanced-manifest.tsx @@ -4,14 +4,14 @@ import { useEffect, useState } from "react"; import { Accordion } from "@mantine/core"; import { customWidgetDefinitionSchema } from "@homarr/custom-widgets/core"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; import { CodeEditor } from "./_code-editor"; import { applyDefinition, parseJson } from "./_custom-widget-form-utils"; import type { CustomWidgetWorkbenchForm } from "./_custom-widget-form-utils"; export function CustomWidgetAdvancedManifest({ form }: { form: CustomWidgetWorkbenchForm }) { - const t = useScopedI18n("customWidget.workbench.builder"); + const t = useI18n("customWidget.workbench.builder"); const serialized = JSON.stringify( { $schema: "homarr-custom-widget-v2", diff --git a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-ai-card.tsx b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-ai-card.tsx index 3e817a595..0d41ce0ee 100644 --- a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-ai-card.tsx +++ b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-ai-card.tsx @@ -4,7 +4,7 @@ import { Accordion, Button, Group, Stack, Text, TextInput, Textarea } from "@man import { IconClipboard, IconSparkles } from "@tabler/icons-react"; import type { HomarrCustomWidgetV2 } from "@homarr/custom-widgets/core"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; import { CopyAiPromptButton } from "./_copy-ai-prompt-button"; @@ -18,7 +18,7 @@ interface AiCardProps { } export function CustomWidgetAiCard(props: AiCardProps) { - const t = useScopedI18n("customWidget.workbench.ai"); + const t = useI18n("customWidget.workbench.ai"); return ( diff --git a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form-layout.tsx b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form-layout.tsx index 90829066b..85ab7c6e5 100644 --- a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form-layout.tsx +++ b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form-layout.tsx @@ -5,7 +5,7 @@ import type { Icon } from "@tabler/icons-react"; import { Button, Card, Group, Stack, Text, ThemeIcon } from "@mantine/core"; import { IconAlertTriangle, IconCheck, IconPlayerPlay } from "@tabler/icons-react"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; import classes from "./_custom-widget-form.module.css"; @@ -52,7 +52,8 @@ export function SaveActions({ mode: "create" | "edit"; onPreview(): void; }) { - const t = useScopedI18n("customWidget.workbench"); + const t = useI18n("customWidget.workbench"); + const tCommon = useI18n("common"); return ( @@ -77,7 +78,7 @@ export function SaveActions({ {previewPending ? t("action.previewLoading") : t("action.preview")} diff --git a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form.tsx b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form.tsx index c1cb2b876..3a416a250 100644 --- a/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form.tsx @@ -22,7 +22,7 @@ import { customWidgetOptionsSchema, getCustomWidgetDefaultOptions } from "@homar import type { CustomWidgetFormValues } from "@homarr/custom-widgets/workbench"; import { useZodForm } from "@homarr/form"; import { IconPicker } from "@homarr/forms-collection"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useI18n } from "@homarr/translation/client"; import { CodeEditor } from "./_code-editor"; import { CustomWidgetAiCard } from "./_custom-widget-ai-card"; @@ -57,8 +57,9 @@ const sectionLinks = [ ] as const; export function CustomWidgetForm({ mode, initialValues, definitionId }: CustomWidgetFormProps) { - const t = useScopedI18n("customWidget"); - const w = useScopedI18n("customWidget.workbench"); + const t = useI18n("customWidget"); + const tCommon = useI18n("common"); + const w = useI18n("customWidget.workbench"); const formInitialValues: CustomWidgetFormValues = { ...DEFAULT_CUSTOM_WIDGET_FORM_VALUES, ...initialValues }; const form = useZodForm(customWidgetFormSchema, { initialValues: formInitialValues }); const [mobilePane, setMobilePane] = useState<"configure" | "preview">("configure"); @@ -169,7 +170,7 @@ export function CustomWidgetForm({ mode, initialValues, definitionId }: CustomWi /> - +