From 3d1330ebb9f9cf8b24fa96d163cce1a248cac91c Mon Sep 17 00:00:00 2001 From: Nicolas Gorga <62995075+NicolasGorga@users.noreply.github.com> Date: Wed, 17 Dec 2025 09:36:50 -0300 Subject: [PATCH] feat: Translations UI (#14217) * Add Translations route and guard it with feature flag. Empty TranslationsList main component to test route. * Translation list component * Add translations namespace to js-sdk * Translations hook * Avoid incorrectly throwing when updating and locale not included * Translations bulk editor component v1 * Add batch method to translations namespace in js-sdk * Protect translations edit route with feature flag * Handle reference_id search param * Replace entity_type entity_id for reference reference_id * Manage translations from product detail page * Dynamically resolve base hook for retrieving translations * Fix navigation from outside settings/translations * Navigation to bulk editor from product list * Add Translations to various product module types * Type useVariants hook * Handle product module entities translations in bulk editor * Fix categories issue in datagrid due to column clash * Translations bulk navigation from remaining entities detail pages * Add remaining bulk editor navigation for list components. Fix invalidation query for variants * Expandable text cell v1 * Popover approach * Add *supported_locales.locale to default fields in stores list endpoint * Make popover more aligned to excell approach * Correctly tie the focused cell anchor to popover * Rework translations main component UI * Fix link def export * Swap axis for translations datagrid * Add original column to translations data grid * Remove is_default store locale from backend * Remove ldefault locale from ui * Type * Add changeset * Comments * Remove unused import * Add translations to admin product categories endpoint allowed fields * Default locale removal * Lazy loading with infinite scroll data grid * Infinite list hook and implementation for products and variants * Translation bulk editor lazy loaded datagrid * Prevent scroll when forcing focus, to avoid scrollTop reset on infinite loading * Confgiure placeholder data * Cleanup logs and refactor * Infinite query hooks for translatable entities * Batch requests for translation batch endpoint * Clean up * Update icon * Add query param validator in settings endpoint * Settings endpoint param type * JS sdk methods for translation settings and statistics * Retrieve translatable fields and entities dynamically. Remove hardcoded information from tranlations list * Resolve translation aggregate completion dynamically * Format label * Resolve bulk editor header label dynamically * Include type and collection in translations config * Avoid showing product option and option values in translatable entities list * Translations * Make translations bulk editor content columns wider * Disable hiding Original column in translations bulk editor * Adjust translations completion styles * Fix translations config screen * Locale selector switcher with conditional locale column rendering * Batch one locale at a time * Hooks save actions to footer buttons * Reset dirty state on save * Dynamic row heights for translations bulk editor. Replace expandable cell for text cell, with additional isMultiLine config * Make columns take as much responsive width as possible and divide equally * more padding to avoid unnecessary horizontal scrollbar * Update statistics graphs * Translations * Statistics graphs translations * Translation, text sizes and weight in stat graphs * Conditionally show/hide column visibility dropdown in datagrid * Allow to pass component to place in DataGrid header and use it in translations bulk editor * Center text regardless of multiLine config * Apply full height to datagrid cell regardles of multiSelect config * Colors and fonts * Handle key down for text area in text cell * MultilineCell with special keydown handling * Rework form schema to match new single locale edit flow * Update created translations to include id, to avoid duplication issue on subsequent calls * Handle space key for text cells * Finish hooking up multiline cell with key and mouse events * Disable remaining buttons when batch is ongoing * Style updates * Update style * Refactor to make form updates and sync/comparison with server data more comprehensive and robust * Update styles * Bars and labels alignment * Add languages tooltip * Styles and translation * Navigation update * Disable edit translations button when no reference count * Invert colors --------- Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> Co-authored-by: Adrien de Peretti --- .../common/icon-avatar/icon-avatar.tsx | 4 + .../components/data-grid-cell-container.tsx | 17 +- .../components/data-grid-multiline-cell.tsx | 103 +++ .../components/data-grid-readonly-cell.tsx | 16 +- .../data-grid/components/data-grid-root.tsx | 327 ++++++-- .../components/data-grid-text-cell.tsx | 14 +- .../data-grid-textarea-modal-cell.tsx | 233 ++++++ .../components/data-grid/components/index.ts | 2 + .../src/components/data-grid/data-grid.tsx | 17 +- .../helpers/create-data-grid-column-helper.ts | 18 + .../data-grid/hooks/use-data-grid-cell.tsx | 14 +- .../hooks/use-data-grid-form-handlers.tsx | 1 + .../hooks/use-data-grid-keydown-event.tsx | 72 +- .../src/components/data-grid/types.ts | 1 + .../settings-layout/settings-layout.tsx | 12 +- .../route-focus-modal/route-focus-modal.tsx | 11 +- .../route-modal-form/route-modal-form.tsx | 7 +- .../route-modal-provider/route-provider.tsx | 20 +- .../dashboard-app/routes/get-route.map.tsx | 23 + .../dashboard/src/hooks/api/categories.tsx | 32 + .../dashboard/src/hooks/api/collections.tsx | 31 + .../admin/dashboard/src/hooks/api/index.ts | 1 + .../dashboard/src/hooks/api/product-types.tsx | 32 + .../src/hooks/api/product-variants.tsx | 50 +- .../dashboard/src/hooks/api/products.tsx | 29 + .../admin/dashboard/src/hooks/api/tags.tsx | 32 + .../dashboard/src/hooks/api/translations.tsx | 315 ++++++++ .../dashboard/src/hooks/use-infinite-list.tsx | 92 +++ .../src/i18n/translations/$schema.json | 122 +++ .../dashboard/src/i18n/translations/en.json | 38 + .../dashboard/src/i18n/translations/es.json | 38 + .../category-general-section.tsx | 17 +- .../category-list-table.tsx | 17 +- .../collection-general-section.tsx | 17 +- .../collection-row-actions.tsx | 17 +- .../product-tag-general-section.tsx | 17 +- .../product-tag-list-table.tsx | 17 +- .../product-type-general-section.tsx | 17 +- .../product-table-row-actions.tsx | 17 +- .../variant-general-section.tsx | 17 +- .../product-general-section.tsx | 17 +- .../product-variant-section.tsx | 33 +- .../product-list-table/product-list-table.tsx | 16 +- .../translations/add-locales/add-locales.tsx | 29 + .../routes/translations/add-locales/index.tsx | 1 + .../active-locales-section.tsx | 93 +++ .../translation-list-section.tsx | 48 ++ .../translations-completion-section.tsx | 285 +++++++ .../translations/translation-list/index.tsx | 1 + .../translation-list/translation-list.tsx | 138 ++++ .../translations-edit-form/index.ts | 1 + .../translations-edit-form.tsx | 747 ++++++++++++++++++ .../translations/translations-edit/index.ts | 1 + .../translations-edit/translations-edit.tsx | 90 +++ .../steps/validate-translations.ts | 2 +- packages/core/js-sdk/src/admin/index.ts | 6 + packages/core/js-sdk/src/admin/translation.ts | 206 +++++ .../src/http/collection/admin/entities.ts | 8 +- .../http/product-category/admin/entities.ts | 5 + .../src/http/product-tag/admin/entities.ts | 8 +- .../src/http/product-type/admin/entities.ts | 8 +- .../types/src/http/product/admin/entitites.ts | 9 + .../src/http/translations/admin/index.ts | 1 + .../src/http/translations/admin/payloads.ts | 44 ++ .../src/http/translations/admin/queries.ts | 10 + .../admin/product-categories/query-config.ts | 1 + .../src/api/admin/translations/middlewares.ts | 5 +- .../api/admin/translations/settings/route.ts | 10 +- .../src/api/admin/translations/validators.ts | 7 + 69 files changed, 3595 insertions(+), 112 deletions(-) create mode 100644 packages/admin/dashboard/src/components/data-grid/components/data-grid-multiline-cell.tsx create mode 100644 packages/admin/dashboard/src/components/data-grid/components/data-grid-textarea-modal-cell.tsx create mode 100644 packages/admin/dashboard/src/hooks/api/translations.tsx create mode 100644 packages/admin/dashboard/src/hooks/use-infinite-list.tsx create mode 100644 packages/admin/dashboard/src/routes/translations/add-locales/add-locales.tsx create mode 100644 packages/admin/dashboard/src/routes/translations/add-locales/index.tsx create mode 100644 packages/admin/dashboard/src/routes/translations/translation-list/components/active-locales-section/active-locales-section.tsx create mode 100644 packages/admin/dashboard/src/routes/translations/translation-list/components/translation-list-section/translation-list-section.tsx create mode 100644 packages/admin/dashboard/src/routes/translations/translation-list/components/translations-completion-section/translations-completion-section.tsx create mode 100644 packages/admin/dashboard/src/routes/translations/translation-list/index.tsx create mode 100644 packages/admin/dashboard/src/routes/translations/translation-list/translation-list.tsx create mode 100644 packages/admin/dashboard/src/routes/translations/translations-edit/components/translations-edit-form/index.ts create mode 100644 packages/admin/dashboard/src/routes/translations/translations-edit/components/translations-edit-form/translations-edit-form.tsx create mode 100644 packages/admin/dashboard/src/routes/translations/translations-edit/index.ts create mode 100644 packages/admin/dashboard/src/routes/translations/translations-edit/translations-edit.tsx create mode 100644 packages/core/js-sdk/src/admin/translation.ts create mode 100644 packages/core/types/src/http/translations/admin/payloads.ts diff --git a/packages/admin/dashboard/src/components/common/icon-avatar/icon-avatar.tsx b/packages/admin/dashboard/src/components/common/icon-avatar/icon-avatar.tsx index 9d5cdc3e4e..d10216fc5a 100644 --- a/packages/admin/dashboard/src/components/common/icon-avatar/icon-avatar.tsx +++ b/packages/admin/dashboard/src/components/common/icon-avatar/icon-avatar.tsx @@ -4,6 +4,7 @@ import { PropsWithChildren } from "react" type IconAvatarProps = PropsWithChildren<{ className?: string size?: "small" | "large" | "xlarge" + variant?: "squared" | "rounded" }> /** @@ -13,6 +14,7 @@ type IconAvatarProps = PropsWithChildren<{ */ export const IconAvatar = ({ size = "small", + variant = "rounded", children, className, }: IconAvatarProps) => { @@ -20,6 +22,8 @@ export const IconAvatar = ({
div]:bg-ui-bg-field [&>div]:text-ui-fg-subtle [&>div]:flex [&>div]:size-6 [&>div]:items-center [&>div]:justify-center", { "size-7 rounded-md [&>div]:size-6 [&>div]:rounded-[4px]": diff --git a/packages/admin/dashboard/src/components/data-grid/components/data-grid-cell-container.tsx b/packages/admin/dashboard/src/components/data-grid/components/data-grid-cell-container.tsx index 3a4f81369b..a361e40416 100644 --- a/packages/admin/dashboard/src/components/data-grid/components/data-grid-cell-container.tsx +++ b/packages/admin/dashboard/src/components/data-grid/components/data-grid-cell-container.tsx @@ -20,16 +20,20 @@ export const DataGridCellContainer = ({ errors, rowErrors, outerComponent, -}: DataGridCellContainerProps & DataGridErrorRenderProps) => { + isMultiLine, +}: DataGridCellContainerProps & + DataGridErrorRenderProps & { isMultiLine?: boolean }) => { const error = get(errors, field) const hasError = !!error return ( -
+
-
+
{children} diff --git a/packages/admin/dashboard/src/components/data-grid/components/data-grid-multiline-cell.tsx b/packages/admin/dashboard/src/components/data-grid/components/data-grid-multiline-cell.tsx new file mode 100644 index 0000000000..ab2381f853 --- /dev/null +++ b/packages/admin/dashboard/src/components/data-grid/components/data-grid-multiline-cell.tsx @@ -0,0 +1,103 @@ +import { clx } from "@medusajs/ui" +import { useCallback, useEffect, useRef, useState } from "react" +import { Controller, ControllerRenderProps } from "react-hook-form" + +import { useCombinedRefs } from "../../../hooks/use-combined-refs" +import { useDataGridCell, useDataGridCellError } from "../hooks" +import { DataGridCellProps, InputProps } from "../types" +import { DataGridCellContainer } from "./data-grid-cell-container" + +export const DataGridMultilineCell = ({ + context, +}: DataGridCellProps) => { + const { field, control, renderProps } = useDataGridCell({ + context, + }) + const errorProps = useDataGridCellError({ context }) + + const { container, input } = renderProps + + return ( + { + return ( + + + + ) + }} + /> + ) +} + +const Inner = ({ + field, + inputProps, +}: { + field: ControllerRenderProps + inputProps: InputProps +}) => { + const { onChange: _, onBlur, ref, value, ...rest } = field + const { ref: inputRef, onBlur: onInputBlur, onChange, ...input } = inputProps + + const [localValue, setLocalValue] = useState(value) + const textareaRef = useRef(null) + + useEffect(() => { + setLocalValue(value) + }, [value]) + + const combinedRefs = useCombinedRefs(inputRef, ref, textareaRef) + + const adjustTextareaHeight = useCallback(() => { + const textarea = textareaRef.current + if (textarea) { + // Reset height to 0 to get accurate scrollHeight + textarea.style.height = "0px" + // Set the height to match content (minimum 24px for min visible height) + const newHeight = Math.max(textarea.scrollHeight, 24) + textarea.style.height = `${newHeight}px` + } + }, []) + + // Adjust height when value changes + useEffect(() => { + adjustTextareaHeight() + }, [localValue, adjustTextareaHeight]) + + useEffect(() => { + // Immediate adjustment + adjustTextareaHeight() + // Delayed adjustment to handle any layout shifts + const timeoutId = setTimeout(adjustTextareaHeight, 50) + return () => clearTimeout(timeoutId) + }, [adjustTextareaHeight]) + + return ( +