From 289c6931d1d387703976b25694c45984e0530b41 Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Fri, 17 Jul 2026 17:39:14 +0300 Subject: [PATCH 1/2] feat(i18n): migrate rdi pipeline-management to i18n (RI-8277) Route the RDI pipeline-management surface through i18n under rdi.pipeline.* with Bulgarian translations: navigation, config & job editor pages, dry-run panel, test-connections panel & log, template form/popover/button, and the upload/download modals. Shared rdiErrorMessages resolves via the i18n singleton; TestConnectionsLog headers via a render-time getColumns(t); for doc links and
tooltips. The template "No template" fallback option is built at render (useMemo[t]) instead of a module-level i18n.t so it follows the active language. Rebased onto main after #6221/#6219/#6224 merged; rdi.* locale keys merged and sorted. Co-Authored-By: Claude Opus 4.8 --- redisinsight/ui/src/i18n/locales/bg.json | 80 ++++++++ redisinsight/ui/src/i18n/locales/en.json | 80 ++++++++ .../ui/src/pages/rdi/constants/errors.tsx | 7 +- .../PipelineManagementPage.tsx | 4 +- .../DownloadFromServerModal.tsx | 13 +- .../DryRunJobCommands.tsx | 12 +- .../DryRunJobTransformations.tsx | 13 +- .../components/jobs-panel/Panel.tsx | 44 +++-- .../components/navigation/Navigation.tsx | 4 +- .../navigation/cards/ConfigurationCard.tsx | 8 +- .../navigation/cards/jobs/JobNameForm.tsx | 7 +- .../navigation/cards/jobs/JobsCard.tsx | 8 +- .../navigation/cards/jobs/JobsItem.tsx | 178 +++++++++--------- .../SourcePipelineModal.tsx | 10 +- .../template-button/TemplateButton.tsx | 6 +- .../template-form/TemplateForm.spec.tsx | 4 +- .../components/template-form/TemplateForm.tsx | 51 +++-- .../components/template-form/constants.ts | 9 - .../template-popover/TemplatePopover.tsx | 6 +- .../TestConnectionsLog.tsx | 14 +- .../TestConnectionsPanel.tsx | 52 ++--- .../components/upload-modal/UploadModal.tsx | 6 +- .../components/upload-dialog/UploadDialog.tsx | 26 +-- .../pages/config/Config.tsx | 41 ++-- .../rdi/pipeline-management/pages/job/Job.tsx | 44 +++-- 25 files changed, 478 insertions(+), 249 deletions(-) diff --git a/redisinsight/ui/src/i18n/locales/bg.json b/redisinsight/ui/src/i18n/locales/bg.json index 004a39f1b4..2955ef3724 100644 --- a/redisinsight/ui/src/i18n/locales/bg.json +++ b/redisinsight/ui/src/i18n/locales/bg.json @@ -608,6 +608,86 @@ "rdi.instance.stop.ariaLabel": "Спиране на конвейера", "rdi.instance.stop.button": "Стоп", "rdi.instance.stop.tooltip": "Спрете конвейера, за да предотвратите обработката на нови постъпващи данни.", + "rdi.pipeline.config.description": "Конфигурирайте детайлите за връзка и настройките за прилагане на целевата инстанция.", + "rdi.pipeline.config.testButton": "Тест на връзката", + "rdi.pipeline.config.title": "Конфигурация на целевата база данни", + "rdi.pipeline.download.body": "При изтегляне на конфигурацията на конвейера от сървъра, тя ще замени съществуващата, показана в Redis Insight.", + "rdi.pipeline.download.cancel": "Отказ", + "rdi.pipeline.download.confirm": "Изтегляне от сървъра", + "rdi.pipeline.download.saveToFile": "Запазване във файл", + "rdi.pipeline.download.title": "Изтегляне на конвейер от сървъра", + "rdi.pipeline.dryRun.closeAria": "затваряне на панела за пробно изпълнение", + "rdi.pipeline.dryRun.fullscreenAria": "превключване на цял екран за панела за пробно изпълнение", + "rdi.pipeline.dryRun.inputHelp": "Добавете входни данни, за да тествате логиката на трансформация.", + "rdi.pipeline.dryRun.inputInvalid": "Входните данни трябва да са във формат JSON", + "rdi.pipeline.dryRun.inputTitle": "Вход", + "rdi.pipeline.dryRun.jobOutput": "Изход на задачата", + "rdi.pipeline.dryRun.jobOutputTooltip": "Показва списъка с Redis команди, които ще бъдат генерирани въз основа на детайлите на вашата задача.Не се записват данни в целевата база данни.", + "rdi.pipeline.dryRun.noCommands": "Сървърът не предостави Redis команди.", + "rdi.pipeline.dryRun.noTransformation": "Сървърът не предостави резултати от трансформация.", + "rdi.pipeline.dryRun.runButton": "Пробно изпълнение", + "rdi.pipeline.dryRun.title": "Тестване на логиката на трансформация", + "rdi.pipeline.dryRun.transformationOutput": "Изход на трансформацията", + "rdi.pipeline.dryRun.transformationTooltip": "Показва резултатите от трансформациите, които сте дефинирали. Данните са представени във формат JSON.Не се записват данни в целевата база данни.", + "rdi.pipeline.error.defaultMsg": "Неуспешно преобразуване на YAML в JSON структура", + "rdi.pipeline.error.defaultName": "Стойността", + "rdi.pipeline.invalidStructure": "{{name}} има невалидна структура.", + "rdi.pipeline.job.dedicatedEditorButton": "SQL и JMESPath редактор", + "rdi.pipeline.job.description": "Създайте задача за всяка таблица източник, за да филтрирате, трансформирате и съпоставите данни към Redis.", + "rdi.pipeline.job.dryRunButton": "Пробно изпълнение", + "rdi.pipeline.jobName.inUse": "Името на задачата вече се използва", + "rdi.pipeline.jobName.placeholder": "Въведете име на задача", + "rdi.pipeline.jobName.required": "Името на задачата е задължително", + "rdi.pipeline.loading": "Зареждане...", + "rdi.pipeline.nav.addJobAria": "добавяне на нов файл на задача", + "rdi.pipeline.nav.addJobTooltip": "Добавяне на файл на задача", + "rdi.pipeline.nav.configFile": "Конфигурационен файл", + "rdi.pipeline.nav.configTitle": "Конфигурация", + "rdi.pipeline.nav.deleteConfirm": "Изтриване", + "rdi.pipeline.nav.deleteJobAria": "изтриване на задача", + "rdi.pipeline.nav.deleteJobBody": "Промените няма да бъдат приложени, докато конвейерът не бъде внедрен.", + "rdi.pipeline.nav.deleteJobTitle": "Изтриване на {{name}}", + "rdi.pipeline.nav.deleteJobTooltip": "Изтриване на задача", + "rdi.pipeline.nav.editJobAria": "редактиране на името на файла на задачата", + "rdi.pipeline.nav.editJobTooltip": "Редактиране на името на файла на задачата", + "rdi.pipeline.nav.jobsTitle": "Трансформиране и валидиране", + "rdi.pipeline.nav.title": "Управление на конвейера", + "rdi.pipeline.nav.undeployedChanges": "Този файл съдържа невнедрени промени.", + "rdi.pipeline.pageTitle": "{{name}} - Управление на конвейера", + "rdi.pipeline.source.createNew": "Създаване на нов конвейер", + "rdi.pipeline.source.importZip": "Импортиране на конвейер от ZIP файл", + "rdi.pipeline.source.subtitle": "за да започнете с вашия конвейер", + "rdi.pipeline.source.title": "Изберете опция", + "rdi.pipeline.template.apply": "Прилагане", + "rdi.pipeline.template.cancel": "Отказ", + "rdi.pipeline.template.dbType": "Тип база данни", + "rdi.pipeline.template.editorOnly": "Шаблоните са достъпни само с празен редактор, за да се предотврати потенциална загуба на данни.", + "rdi.pipeline.template.insertAria": "Вмъкване на шаблон", + "rdi.pipeline.template.insertButton": "Вмъкване на шаблон", + "rdi.pipeline.template.noTemplateLabel": "Без шаблон", + "rdi.pipeline.template.noneAvailableLine1": "Няма наличен шаблон.", + "rdi.pipeline.template.noneAvailableLine2": "Затворете формата и опитайте отново.", + "rdi.pipeline.template.pipelineType": "Тип конвейер", + "rdi.pipeline.template.title": "Изберете шаблон", + "rdi.pipeline.testConn.closeAria": "затваряне на панела за тест на връзките", + "rdi.pipeline.testConn.colEndpoint": "Крайна точка", + "rdi.pipeline.testConn.colResults": "Резултати", + "rdi.pipeline.testConn.loading": "Зареждане на резултатите...", + "rdi.pipeline.testConn.noResults": "Няма намерени резултати. Моля, опитайте отново.", + "rdi.pipeline.testConn.source": "Връзки към източника", + "rdi.pipeline.testConn.successful": "Успешно", + "rdi.pipeline.testConn.target": "Връзки към целта", + "rdi.pipeline.testConn.title": "Тест на връзката", + "rdi.pipeline.upload.errorNoConfig": "config.yaml липсва", + "rdi.pipeline.upload.errorNoJobs": "Не е намерена папка jobs", + "rdi.pipeline.upload.errorZip": "Възникна проблем с .zip файла", + "rdi.pipeline.upload.resultFail": "Неуспешно качване на конвейера", + "rdi.pipeline.upload.resultSuccess": "Конвейерът е качен", + "rdi.pipeline.upload.submitButton": "Качване", + "rdi.pipeline.upload.submitResults": "Нов конвейер беше успешно качен.", + "rdi.pipeline.upload.titleArchive": "Качете архив с RDI конвейер", + "rdi.pipeline.upload.titleNew": "Качване на нов конвейер", + "rdi.pipeline.upload.warning": "Ако бъде качен нов конвейер, съществуващата конфигурация на конвейера и задачите за трансформация ще бъдат презаписани. Промените няма да бъдат приложени, докато конвейерът не бъде внедрен.", "rdi.statistics.empty.addButton": "Добавяне на конвейер", "rdi.statistics.empty.description": "Създайте първия си конвейер, за да започнете!", "rdi.statistics.empty.title": "Все още няма разгърнат конвейер", diff --git a/redisinsight/ui/src/i18n/locales/en.json b/redisinsight/ui/src/i18n/locales/en.json index 24becd408a..8a70fea529 100644 --- a/redisinsight/ui/src/i18n/locales/en.json +++ b/redisinsight/ui/src/i18n/locales/en.json @@ -608,6 +608,86 @@ "rdi.instance.stop.ariaLabel": "Stop running pipeline", "rdi.instance.stop.button": "Stop", "rdi.instance.stop.tooltip": "Stop the pipeline to prevent processing of new data arrivals.", + "rdi.pipeline.config.description": "Configure target instance connection details and applier settings.", + "rdi.pipeline.config.testButton": "Test Connection", + "rdi.pipeline.config.title": "Target database configuration", + "rdi.pipeline.download.body": "When downloading the pipeline configuration from the server, it will overwrite the existing one displayed in Redis Insight.", + "rdi.pipeline.download.cancel": "Cancel", + "rdi.pipeline.download.confirm": "Download from server", + "rdi.pipeline.download.saveToFile": "Save to file", + "rdi.pipeline.download.title": "Download a pipeline from the server", + "rdi.pipeline.dryRun.closeAria": "close dry run panel", + "rdi.pipeline.dryRun.fullscreenAria": "toggle fullscrenn dry run panel", + "rdi.pipeline.dryRun.inputHelp": "Add input data to test the transformation logic.", + "rdi.pipeline.dryRun.inputInvalid": "Input should have JSON format", + "rdi.pipeline.dryRun.inputTitle": "Input", + "rdi.pipeline.dryRun.jobOutput": "Job output", + "rdi.pipeline.dryRun.jobOutputTooltip": "Displays the list of Redis commands that will be generated based on your job details.No data is written to the target database.", + "rdi.pipeline.dryRun.noCommands": "No Redis commands provided by the server.", + "rdi.pipeline.dryRun.noTransformation": "No transformation results provided by the server.", + "rdi.pipeline.dryRun.runButton": "Dry run", + "rdi.pipeline.dryRun.title": "Test transformation logic", + "rdi.pipeline.dryRun.transformationOutput": "Transformation output", + "rdi.pipeline.dryRun.transformationTooltip": "Displays the results of the transformations you defined. The data is presented in JSON format.No data is written to the target database.", + "rdi.pipeline.error.defaultMsg": "Failed to convert YAML to JSON structure", + "rdi.pipeline.error.defaultName": "Value", + "rdi.pipeline.invalidStructure": "{{name}} has an invalid structure.", + "rdi.pipeline.job.dedicatedEditorButton": "SQL and JMESPath Editor", + "rdi.pipeline.job.description": "Create a job per source table to filter, transform, and map data to Redis.", + "rdi.pipeline.job.dryRunButton": "Dry Run", + "rdi.pipeline.jobName.inUse": "Job name is already in use", + "rdi.pipeline.jobName.placeholder": "Enter job name", + "rdi.pipeline.jobName.required": "Job name is required", + "rdi.pipeline.loading": "Loading...", + "rdi.pipeline.nav.addJobAria": "add new job file", + "rdi.pipeline.nav.addJobTooltip": "Add a job file", + "rdi.pipeline.nav.configFile": "Configuration file", + "rdi.pipeline.nav.configTitle": "Configuration", + "rdi.pipeline.nav.deleteConfirm": "Delete", + "rdi.pipeline.nav.deleteJobAria": "delete job", + "rdi.pipeline.nav.deleteJobBody": "Changes will not be applied until the pipeline is deployed.", + "rdi.pipeline.nav.deleteJobTitle": "Delete {{name}}", + "rdi.pipeline.nav.deleteJobTooltip": "Delete job", + "rdi.pipeline.nav.editJobAria": "edit job file name", + "rdi.pipeline.nav.editJobTooltip": "Edit job file name", + "rdi.pipeline.nav.jobsTitle": "Transform and Validate", + "rdi.pipeline.nav.title": "Pipeline management", + "rdi.pipeline.nav.undeployedChanges": "This file contains undeployed changes.", + "rdi.pipeline.pageTitle": "{{name}} - Pipeline Management", + "rdi.pipeline.source.createNew": "Create new pipeline", + "rdi.pipeline.source.importZip": "Import pipeline from ZIP file", + "rdi.pipeline.source.subtitle": "to start with your pipeline", + "rdi.pipeline.source.title": "Select an option", + "rdi.pipeline.template.apply": "Apply", + "rdi.pipeline.template.cancel": "Cancel", + "rdi.pipeline.template.dbType": "Database type", + "rdi.pipeline.template.editorOnly": "Templates can be accessed only with the empty Editor to prevent potential data loss.", + "rdi.pipeline.template.insertAria": "Insert template", + "rdi.pipeline.template.insertButton": "Insert template", + "rdi.pipeline.template.noTemplateLabel": "No template", + "rdi.pipeline.template.noneAvailableLine1": "No template is available.", + "rdi.pipeline.template.noneAvailableLine2": "Close the form and try again.", + "rdi.pipeline.template.pipelineType": "Pipeline type", + "rdi.pipeline.template.title": "Select a template", + "rdi.pipeline.testConn.closeAria": "close test connections panel", + "rdi.pipeline.testConn.colEndpoint": "Endpoint", + "rdi.pipeline.testConn.colResults": "Results", + "rdi.pipeline.testConn.loading": "Loading results...", + "rdi.pipeline.testConn.noResults": "No results found. Please try again.", + "rdi.pipeline.testConn.source": "Source connections", + "rdi.pipeline.testConn.successful": "Successful", + "rdi.pipeline.testConn.target": "Target connections", + "rdi.pipeline.testConn.title": "Test connection", + "rdi.pipeline.upload.errorNoConfig": "config.yaml is missing", + "rdi.pipeline.upload.errorNoJobs": "No jobs folder found", + "rdi.pipeline.upload.errorZip": "There was a problem with the .zip file", + "rdi.pipeline.upload.resultFail": "Failed to upload pipeline", + "rdi.pipeline.upload.resultSuccess": "Pipeline has been uploaded", + "rdi.pipeline.upload.submitButton": "Upload", + "rdi.pipeline.upload.submitResults": "A new pipeline has been successfully uploaded.", + "rdi.pipeline.upload.titleArchive": "Upload an archive with an RDI pipeline", + "rdi.pipeline.upload.titleNew": "Upload a new pipeline", + "rdi.pipeline.upload.warning": "If a new pipeline is uploaded, existing pipeline configuration and transformation jobs will be overwritten. Changes will not be applied until the pipeline is deployed.", "rdi.statistics.empty.addButton": "Add Pipeline", "rdi.statistics.empty.description": "Create your first pipeline to get started!", "rdi.statistics.empty.title": "No pipeline deployed yet", diff --git a/redisinsight/ui/src/pages/rdi/constants/errors.tsx b/redisinsight/ui/src/pages/rdi/constants/errors.tsx index 8692f1b699..64132fc56c 100644 --- a/redisinsight/ui/src/pages/rdi/constants/errors.tsx +++ b/redisinsight/ui/src/pages/rdi/constants/errors.tsx @@ -1,13 +1,14 @@ import { upperFirst } from 'lodash' import React from 'react' +import i18n from 'uiSrc/i18n' export const rdiErrorMessages = { invalidStructure: ( - name = 'Value', - msg = 'Failed to convert YAML to JSON structure', + name: string = i18n.t('rdi.pipeline.error.defaultName'), + msg: string = i18n.t('rdi.pipeline.error.defaultMsg'), ) => ( <> - {`${upperFirst(name)} has an invalid structure.`} + {i18n.t('rdi.pipeline.invalidStructure', { name: upperFirst(name) })}
{msg} diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/PipelineManagementPage.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/PipelineManagementPage.tsx index 42bacce2e1..d74aabdf8c 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/PipelineManagementPage.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/PipelineManagementPage.tsx @@ -15,6 +15,7 @@ import { setLastPipelineManagementPage, } from 'uiSrc/slices/app/context' import { formatLongName, setTitle } from 'uiSrc/utils' +import { useTranslation } from 'uiSrc/i18n' import SourcePipelineDialog from 'uiSrc/pages/rdi/pipeline-management/components/source-pipeline-dialog' import Navigation from 'uiSrc/pages/rdi/pipeline-management/components/navigation' @@ -32,6 +33,7 @@ export interface Props { } const PipelineManagementPage = ({ routes = [] }: Props) => { + const { t } = useTranslation() const { rdiInstanceId } = useParams<{ rdiInstanceId: string }>() const { lastViewedPage } = useAppSelector(appContextPipelineManagement) const { name: connectedRdiInstanceName } = useAppSelector( @@ -45,7 +47,7 @@ const PipelineManagementPage = ({ routes = [] }: Props) => { const { pathname } = useLocation() const rdiInstanceName = formatLongName(connectedRdiInstanceName, 33, 0, '...') - setTitle(`${rdiInstanceName} - Pipeline Management`) + setTitle(t('rdi.pipeline.pageTitle', { name: rdiInstanceName })) useEffect(() => { if ( diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/download-from-server-modal/DownloadFromServerModal.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/download-from-server-modal/DownloadFromServerModal.tsx index 7571665dfc..37baf28fdd 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/download-from-server-modal/DownloadFromServerModal.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/download-from-server-modal/DownloadFromServerModal.tsx @@ -13,6 +13,7 @@ import { } from 'uiSrc/slices/rdi/pipeline' import { useParams } from 'react-router-dom' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { useTranslation } from 'uiSrc/i18n' export interface Props { trigger?: React.ReactElement @@ -20,6 +21,7 @@ export interface Props { } const DownloadFromServerModal = (props: Props) => { + const { t } = useTranslation() const { trigger, onClose } = props const { loading, data } = useAppSelector(rdiPipelineSelector) @@ -70,10 +72,9 @@ const DownloadFromServerModal = (props: Props) => { {button && {button}} - + - When downloading the pipeline configuration from the server, it will - overwrite the existing one displayed in Redis Insight. + {t('rdi.pipeline.download.body')} { trigger={ - Save to file + {t('rdi.pipeline.download.saveToFile')} } /> - Cancel + {t('rdi.pipeline.download.cancel')} { loading={loading} data-testid="upload-confirm-btn" > - Download from server + {t('rdi.pipeline.download.confirm')} diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/dry-run-job-commands/DryRunJobCommands.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/dry-run-job-commands/DryRunJobCommands.tsx index 3a4a614014..ab78884fcc 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/dry-run-job-commands/DryRunJobCommands.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/dry-run-job-commands/DryRunJobCommands.tsx @@ -6,14 +6,14 @@ import { monaco } from 'react-monaco-editor' import { CodeBlock } from 'uiSrc/components' import { rdiDryRunJobSelector } from 'uiSrc/slices/rdi/dryRun' import { MonacoLanguage } from 'uiSrc/constants' +import { useTranslation } from 'uiSrc/i18n' export interface Props { target?: string } -const NO_COMMANDS_MESSAGE = 'No Redis commands provided by the server.' - const DryRunJobCommands = ({ target }: Props) => { + const { t } = useTranslation() const { results } = useAppSelector(rdiDryRunJobSelector) const [commands, setCommands] = useState('') @@ -22,13 +22,15 @@ const DryRunJobCommands = ({ target }: Props) => { return } + const noCommandsMessage = t('rdi.pipeline.dryRun.noCommands') + try { const targetCommands = results?.output?.find( (el) => el.connection === target, )?.commands if (!targetCommands) { - setCommands(NO_COMMANDS_MESSAGE) + setCommands(noCommandsMessage) return } monaco.editor @@ -41,9 +43,9 @@ const DryRunJobCommands = ({ target }: Props) => { setCommands(data) }) } catch (e) { - setCommands(NO_COMMANDS_MESSAGE) + setCommands(noCommandsMessage) } - }, [results, target]) + }, [results, target, t]) return (
diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/dry-run-job-transformations/DryRunJobTransformations.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/dry-run-job-transformations/DryRunJobTransformations.tsx index bbe23cb55f..3b503bea3c 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/dry-run-job-transformations/DryRunJobTransformations.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/dry-run-job-transformations/DryRunJobTransformations.tsx @@ -3,11 +3,10 @@ import { useAppSelector } from 'uiSrc/slices/hooks' import { rdiDryRunJobSelector } from 'uiSrc/slices/rdi/dryRun' import MonacoJson from 'uiSrc/components/monaco-editor/components/monaco-json' - -const NO_TRANSFORMATION_MESSAGE = - 'No transformation results provided by the server.' +import { useTranslation } from 'uiSrc/i18n' const DryRunJobTransformations = () => { + const { t } = useTranslation() const { results } = useAppSelector(rdiDryRunJobSelector) const [transformations, setTransformations] = useState('') @@ -17,13 +16,15 @@ const DryRunJobTransformations = () => { return } + const noTransformationMessage = t('rdi.pipeline.dryRun.noTransformation') + try { const transformations = JSON.stringify(results?.transformation, null, 2) - setTransformations(transformations || NO_TRANSFORMATION_MESSAGE) + setTransformations(transformations || noTransformationMessage) } catch (e) { - setTransformations(NO_TRANSFORMATION_MESSAGE) + setTransformations(noTransformationMessage) } - }, [results]) + }, [results, t]) return ( <> diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/jobs-panel/Panel.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/jobs-panel/Panel.tsx index 1a8c1ec07f..50e9d56ba8 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/jobs-panel/Panel.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/jobs-panel/Panel.tsx @@ -36,6 +36,7 @@ import { } from 'uiSrc/components/base/forms/select/RiSelect' import { DryRunPanelContainer } from 'uiSrc/pages/rdi/pipeline-management/components/jobs-panel/styles' import { Button, TextButton } from '@redis-ui/components' +import { Trans, useTranslation } from 'uiSrc/i18n' export interface Props { job: string @@ -55,6 +56,7 @@ const getTargetOption = (value: string) => { } const DryRunJobPanel = (props: Props) => { + const { t } = useTranslation() const { job, name, onClose } = props const { loading: isDryRunning, results } = useAppSelector(rdiDryRunJobSelector) @@ -129,7 +131,11 @@ const DryRunJobPanel = (props: Props) => { createAxiosError({ message: ( <> - {`${upperFirst(name)} has an invalid structure.`} + + {t('rdi.pipeline.invalidStructure', { + name: upperFirst(name), + })} + {msg} ), @@ -159,15 +165,15 @@ const DryRunJobPanel = (props: Props) => { - Displays the results of the transformations you defined. The data - is presented in JSON format. -
- No data is written to the target database. + }} + /> } data-testid="transformation-output-tooltip" > - Transformation output + {t('rdi.pipeline.dryRun.transformationOutput')}
), content: null, @@ -178,15 +184,15 @@ const DryRunJobPanel = (props: Props) => { - Displays the list of Redis commands that will be generated based - on your job details. -
- No data is written to the target database. + }} + /> } data-testid="job-output-tooltip" > - Job output + {t('rdi.pipeline.dryRun.jobOutput')}
), content: null, @@ -209,32 +215,32 @@ const DryRunJobPanel = (props: Props) => { - Test transformation logic + {t('rdi.pipeline.dryRun.title')} - Add input data to test the transformation logic. + {t('rdi.pipeline.dryRun.inputHelp')} {/* Input section */} - Input + {t('rdi.pipeline.dryRun.inputTitle')} { { data-testid="dry-run-btn" > - Dry run + {t('rdi.pipeline.dryRun.runButton')} diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/Navigation.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/Navigation.tsx index 2b36913086..d56f4b05ca 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/Navigation.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/Navigation.tsx @@ -9,6 +9,7 @@ import { Col } from 'uiSrc/components/base/layout/flex' import { LoadingContent } from 'uiSrc/components/base' import { RdiPipelineTabs } from 'uiSrc/slices/interfaces/rdi' import { rdiPipelineSelector } from 'uiSrc/slices/rdi/pipeline' +import { useTranslation } from 'uiSrc/i18n' import { ConfigurationCard, JobsCard } from './cards' @@ -27,6 +28,7 @@ const getSelectedTab = (path: string, rdiInstanceId: string) => { } const Navigation = () => { + const { t } = useTranslation() const [selectedTab, setSelectedTab] = useState>(null) @@ -52,7 +54,7 @@ const Navigation = () => { return ( - Pipeline management + {t('rdi.pipeline.nav.title')} {loading && } diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/ConfigurationCard.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/ConfigurationCard.tsx index 633c86548c..838fe63094 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/ConfigurationCard.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/ConfigurationCard.tsx @@ -6,6 +6,7 @@ import { Indicator } from 'uiSrc/components/base/text/text.styles' import { Row } from 'uiSrc/components/base/layout/flex' import { Text } from 'uiSrc/components/base/text' import { Icon, ToastNotificationIcon } from 'uiSrc/components/base/icons' +import { useTranslation } from 'uiSrc/i18n' import { useConfigurationState } from './hooks' import BaseCard, { BaseCardProps } from './BaseCard' @@ -22,6 +23,7 @@ const ConfigurationCard = ({ onSelect, isSelected, }: ConfigurationCardProps) => { + const { t } = useTranslation() const { hasChanges, isValid, configValidationErrors } = useConfigurationState() @@ -31,7 +33,7 @@ const ConfigurationCard = ({ return ( )} - Configuration file + {t('rdi.pipeline.nav.configFile')} {!isValid && ( { if (!jobName) { - return buildValidationMessage('Job name is required') + return buildValidationMessage(i18n.t('rdi.pipeline.jobName.required')) } if (jobName === currentJobName) return undefined if (jobs.some((job) => job.name === jobName)) { - return buildValidationMessage('Job name is already in use') + return buildValidationMessage(i18n.t('rdi.pipeline.jobName.inUse')) } return undefined @@ -71,7 +72,7 @@ const JobNameForm = ({ isLoading={isLoading} declineOnUnmount={false} initialValue={currentJobName || ''} - placeholder="Enter job name" + placeholder={i18n.t('rdi.pipeline.jobName.placeholder')} maxLength={250} viewChildrenMode={false} disableEmpty diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/jobs/JobsCard.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/jobs/JobsCard.tsx index 181722c05d..199b9deff0 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/jobs/JobsCard.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/jobs/JobsCard.tsx @@ -19,6 +19,7 @@ import { Row } from 'uiSrc/components/base/layout/flex' import { RiTooltip } from 'uiSrc/components' import { IconButton } from 'uiSrc/components/base/forms/buttons' import { PlusIcon } from 'uiSrc/components/base/icons' +import { useTranslation } from 'uiSrc/i18n' import BaseCard, { BaseCardProps } from '../BaseCard' import JobNameForm from './JobNameForm' @@ -32,6 +33,7 @@ export type JobsCardProps = Omit< } const JobsCard = (props: JobsCardProps) => { + const { t } = useTranslation() const { onSelect, isSelected } = props const [currentJobName, setCurrentJobName] = useState>(null) @@ -132,10 +134,10 @@ const JobsCard = (props: JobsCardProps) => { return ( @@ -151,7 +153,7 @@ const JobsCard = (props: JobsCardProps) => { setHideTooltip(true) }} disabled={isNewJob} - aria-label="add new job file" + aria-label={t('rdi.pipeline.nav.addJobAria')} data-testid="add-new-job" /> diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/jobs/JobsItem.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/jobs/JobsItem.tsx index d9a930a8c1..97cb35ce43 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/jobs/JobsItem.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/navigation/cards/jobs/JobsItem.tsx @@ -13,6 +13,7 @@ import ValidationErrorsList from 'uiSrc/pages/rdi/pipeline-management/components import { Indicator } from 'uiSrc/components/base/text/text.styles' import { ToastNotificationIcon } from '@redis-ui/icons' import { truncateText } from 'uiSrc/utils' +import { useTranslation } from 'uiSrc/i18n' type JobItemProps = { name: string @@ -34,102 +35,105 @@ const JobItem = ({ onSelect, onEdit, onDelete, -}: JobItemProps) => ( - - - {!hasChanges && } +}: JobItemProps) => { + const { t } = useTranslation() - {hasChanges && ( - - - - )} - + return ( + + + {!hasChanges && } - onSelect(name)} - data-testid={`rdi-nav-job-${name}`} - grow - > - - - - {truncateText(name, 20)} - - - - {!isValid && ( + {hasChanges && ( - } + content={t('rdi.pipeline.nav.undeployedChanges')} + position="top" > - )} - - + - - - - onEdit(name)} - aria-label="edit job file name" - data-testid={`edit-job-name-${name}`} - /> - + onSelect(name)} + data-testid={`rdi-nav-job-${name}`} + grow + > + + + + {truncateText(name, 20)} + + - - - Changes will not be applied until the pipeline is deployed. - - } - submitBtn={ - - Delete - - } - onConfirm={() => onDelete(name)} - button={ - + } + > + - } - /> - - - - -) + + )} + + + + + + + onEdit(name)} + aria-label={t('rdi.pipeline.nav.editJobAria')} + data-testid={`edit-job-name-${name}`} + /> + + + + {t('rdi.pipeline.nav.deleteJobBody')}} + submitBtn={ + + {t('rdi.pipeline.nav.deleteConfirm')} + + } + onConfirm={() => onDelete(name)} + button={ + + } + /> + + + + + ) +} export default JobItem diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/source-pipeline-dialog/SourcePipelineModal.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/source-pipeline-dialog/SourcePipelineModal.tsx index 6d4b6819fd..493a6885ae 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/source-pipeline-dialog/SourcePipelineModal.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/source-pipeline-dialog/SourcePipelineModal.tsx @@ -16,6 +16,7 @@ import { ContractsIcon, UploadIcon } from 'uiSrc/components/base/icons' import { FileChangeType } from 'uiSrc/slices/interfaces' import { Modal } from 'uiSrc/components/base/display' import { Spacer } from 'uiSrc/components/base/layout' +import { useTranslation } from 'uiSrc/i18n' import { ButtonWrapper } from './SourcePipelineModal.styles' @@ -30,6 +31,7 @@ export enum PipelineSourceOptions { } const SourcePipelineDialog = () => { + const { t } = useTranslation() const [isShowDownloadDialog, setIsShowDownloadDialog] = useState(false) const { rdiInstanceId } = useParams<{ rdiInstanceId: string }>() @@ -102,10 +104,10 @@ const SourcePipelineDialog = () => { - Select an option + {t('rdi.pipeline.source.title')} - to start with your pipeline + {t('rdi.pipeline.source.subtitle')} @@ -119,7 +121,7 @@ const SourcePipelineDialog = () => { > - Import pipeline from ZIP file + {t('rdi.pipeline.source.importZip')} { > - Create new pipeline + {t('rdi.pipeline.source.createNew')} diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-button/TemplateButton.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-button/TemplateButton.tsx index 0bf164957b..3552c5aa7d 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-button/TemplateButton.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-button/TemplateButton.tsx @@ -10,6 +10,7 @@ import { import { RiTooltip } from 'uiSrc/components' import { RdiPipelineTabs } from 'uiSrc/slices/interfaces' import { SecondaryButton } from 'uiSrc/components/base/forms/buttons' +import { useTranslation } from 'uiSrc/i18n' import { getTooltipContent } from '../template-form/TemplateForm' import { INGEST_OPTION } from '../template-form/constants' @@ -19,6 +20,7 @@ export interface TemplateButtonProps { } const TemplateButton = ({ setFieldValue, value }: TemplateButtonProps) => { + const { t } = useTranslation() const dispatch = useAppDispatch() const { rdiInstanceId } = useParams<{ rdiInstanceId: string }>() const { loading, data } = useAppSelector(rdiPipelineStrategiesSelector) @@ -53,13 +55,13 @@ const TemplateButton = ({ setFieldValue, value }: TemplateButtonProps) => { - Insert template + {t('rdi.pipeline.template.insertButton')} ) diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-form/TemplateForm.spec.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-form/TemplateForm.spec.tsx index d0b6d3ce06..2eddd133ea 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-form/TemplateForm.spec.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-form/TemplateForm.spec.tsx @@ -16,7 +16,7 @@ import { rdiPipelineStrategiesSelector, } from 'uiSrc/slices/rdi/pipeline' import { RdiPipelineTabs } from 'uiSrc/slices/interfaces' -import { INGEST_OPTION, NO_TEMPLATE_LABEL } from './constants' +import { INGEST_OPTION } from './constants' import TemplateForm, { Props } from './TemplateForm' const mockedProps = mock() @@ -114,7 +114,7 @@ describe('TemplateForm', () => { ) expect(screen.getByTestId('db-type-select')).toHaveTextContent( - NO_TEMPLATE_LABEL, + 'No template', ) }) diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-form/TemplateForm.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-form/TemplateForm.tsx index b8ad880ca1..649c9c90a4 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-form/TemplateForm.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-form/TemplateForm.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect, useMemo, useState } from 'react' import { useAppDispatch, useAppSelector } from 'uiSrc/slices/hooks' import { useParams } from 'react-router-dom' @@ -23,7 +23,8 @@ import { RiSelect, defaultValueRender, } from 'uiSrc/components/base/forms/select/RiSelect' -import { NO_TEMPLATE_VALUE, NO_OPTIONS, INGEST_OPTION } from './constants' +import { NO_TEMPLATE_VALUE, INGEST_OPTION } from './constants' +import i18n, { useTranslation } from 'uiSrc/i18n' import { Col, Row } from 'uiSrc/components/base/layout/flex' @@ -41,32 +42,44 @@ export const getTooltipContent = ( if (isNoTemplateOptions) { return ( <> - No template is available. + {i18n.t('rdi.pipeline.template.noneAvailableLine1')}
- Close the form and try again. + {i18n.t('rdi.pipeline.template.noneAvailableLine2')} ) } if (value) { - return 'Templates can be accessed only with the empty Editor to prevent potential data loss.' + return i18n.t('rdi.pipeline.template.editorOnly') } return null } const TemplateForm = (props: Props) => { + const { t } = useTranslation() const { closePopover, setTemplate, source, value } = props const { loading, data } = useAppSelector(rdiPipelineStrategiesSelector) const { rdiInstanceId } = useParams<{ rdiInstanceId: string }>() + // Built at render (not module load) so the label follows the active language. + const noOptions: RiSelectOption[] = useMemo( + () => [ + { + value: NO_TEMPLATE_VALUE, + label: t('rdi.pipeline.template.noTemplateLabel'), + }, + ], + [t], + ) + const [pipelineTypeOptions, setPipelineTypeOptions] = useState< RiSelectOption[] >([]) const [dbTypeOptions, setDbTypeOptions] = - useState(NO_OPTIONS) + useState(noOptions) const [selectedDbType, setSelectedDbType] = useState('') const [selectedPipelineType, setSelectedPipelineType] = useState('') @@ -113,8 +126,8 @@ const TemplateForm = (props: Props) => { useEffect(() => { if (!selectedPipelineType || !data.length) { - setDbTypeOptions(NO_OPTIONS) - setSelectedDbType(NO_OPTIONS[0].value) + setDbTypeOptions(noOptions) + setSelectedDbType(NO_TEMPLATE_VALUE) return } @@ -132,10 +145,10 @@ const TemplateForm = (props: Props) => { setDbTypeOptions(newDbTypeOptions) setSelectedDbType(newDbTypeOptions[0].value) } else { - setDbTypeOptions(NO_OPTIONS) - setSelectedDbType(NO_OPTIONS[0].value) + setDbTypeOptions(noOptions) + setSelectedDbType(NO_TEMPLATE_VALUE) } - }, [data, selectedPipelineType]) + }, [data, selectedPipelineType, noOptions]) useEffect(() => { const newPipelineTypeOptions = data.map((strategy) => ({ @@ -144,7 +157,7 @@ const TemplateForm = (props: Props) => { })) setPipelineTypeOptions( - newPipelineTypeOptions.length ? newPipelineTypeOptions : NO_OPTIONS, + newPipelineTypeOptions.length ? newPipelineTypeOptions : noOptions, ) if (data?.length) { @@ -154,9 +167,9 @@ const TemplateForm = (props: Props) => { ) || newPipelineTypeOptions[0] setSelectedPipelineType(initialSelectedOption.value) } else { - setSelectedPipelineType(NO_OPTIONS[0].value) + setSelectedPipelineType(NO_TEMPLATE_VALUE) } - }, [data]) + }, [data, noOptions]) useEffect(() => { dispatch(fetchPipelineStrategies(rdiInstanceId)) @@ -165,12 +178,12 @@ const TemplateForm = (props: Props) => { return ( - Select a template + {t('rdi.pipeline.template.title')}
{pipelineTypeOptions?.length > 1 && ( - + { )} {source === RdiPipelineTabs.Config && ( - + { size="m" data-testid="template-cancel-btn" > - Cancel + {t('rdi.pipeline.template.cancel')} { size="m" data-testid="template-apply-btn" > - Apply + {t('rdi.pipeline.template.apply')} diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-form/constants.ts b/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-form/constants.ts index 8e99d12b5b..c55339d3d4 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-form/constants.ts +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-form/constants.ts @@ -1,12 +1,3 @@ -export const NO_TEMPLATE_LABEL = 'No template' - export const NO_TEMPLATE_VALUE = 'no_template' -export const NO_OPTIONS = [ - { - value: NO_TEMPLATE_VALUE, - label: NO_TEMPLATE_LABEL, - }, -] - export const INGEST_OPTION = 'ingest' diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-popover/TemplatePopover.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-popover/TemplatePopover.tsx index e042c41343..67d6edb34b 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-popover/TemplatePopover.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/template-popover/TemplatePopover.tsx @@ -9,6 +9,7 @@ import { OutsideClickDetector } from 'uiSrc/components/base/utils' import { SecondaryButton } from 'uiSrc/components/base/forms/buttons' import { RiPopover } from 'uiSrc/components/base' +import { useTranslation } from 'uiSrc/i18n' import styles from './styles.module.scss' export interface Props { @@ -21,6 +22,7 @@ export interface Props { } const TemplatePopover = (props: Props) => { + const { t } = useTranslation() const { isPopoverOpen, setIsPopoverOpen, @@ -56,12 +58,12 @@ const TemplatePopover = (props: Props) => { inverted size="s" className={styles.btn} - aria-label="Insert template" + aria-label={t('rdi.pipeline.template.insertAria')} disabled={loading} onClick={handleOpen} data-testid={`template-trigger-${source}`} > - Insert template + {t('rdi.pipeline.template.insertButton')} } > diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-log/TestConnectionsLog.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-log/TestConnectionsLog.tsx index ad3748303f..1162cff307 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-log/TestConnectionsLog.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-log/TestConnectionsLog.tsx @@ -1,4 +1,5 @@ -import React from 'react' +import React, { useMemo } from 'react' +import { TFunction } from 'i18next' import { IRdiConnectionResult, TransformGroupResult, @@ -6,15 +7,16 @@ import { import { StyledRdiAnalyticsTable } from 'uiSrc/pages/rdi/statistics/styles' import { ColumnDefinition, Table } from 'uiSrc/components/base/layout/table' import { RiTooltip } from 'uiSrc/components' +import { useTranslation } from 'uiSrc/i18n' -const columns: ColumnDefinition[] = [ +const getColumns = (t: TFunction): ColumnDefinition[] => [ { - header: 'Endpoint', + header: t('rdi.pipeline.testConn.colEndpoint'), id: 'endpoint', accessorKey: 'target', }, { - header: 'Results', + header: t('rdi.pipeline.testConn.colResults'), id: 'results', accessorKey: 'error', cell: ({ @@ -25,7 +27,7 @@ const columns: ColumnDefinition[] = [ if (error) { return {error} } - return 'Successful' + return t('rdi.pipeline.testConn.successful') }, }, ] @@ -35,8 +37,10 @@ export interface Props { } const TestConnectionsLog = (props: Props) => { + const { t } = useTranslation() const { data } = props const statusData = [...data.success, ...data.fail] + const columns = useMemo(() => getColumns(t), [t]) return ( <> diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-panel/TestConnectionsPanel.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-panel/TestConnectionsPanel.tsx index c582eea747..1b83efb44b 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-panel/TestConnectionsPanel.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/test-connections-panel/TestConnectionsPanel.tsx @@ -10,6 +10,7 @@ import { IconButton } from 'uiSrc/components/base/forms/buttons' import { CancelSlimIcon } from 'uiSrc/components/base/icons' import { Loader } from 'uiSrc/components/base/display' import Divider from 'uiSrc/components/divider/Divider' +import { useTranslation } from 'uiSrc/i18n' import { TestConnectionContainer } from 'uiSrc/pages/rdi/pipeline-management/components/test-connections-panel/styles' interface TestConnectionPanelWrapperProps { @@ -20,31 +21,36 @@ interface TestConnectionPanelWrapperProps { const TestConnectionPanelWrapper = ({ children, onClose, -}: TestConnectionPanelWrapperProps) => ( - - - - - Test connection - - - - - - {children} - -) +}: TestConnectionPanelWrapperProps) => { + const { t } = useTranslation() + + return ( + + + + + {t('rdi.pipeline.testConn.title')} + + + + + + {children} + + ) +} export interface Props { onClose: () => void } const TestConnectionsPanel = (props: Props) => { + const { t } = useTranslation() const { onClose } = props const { loading, results } = useAppSelector(rdiTestConnectionsSelector) @@ -53,7 +59,7 @@ const TestConnectionsPanel = (props: Props) => { - Loading results... + {t('rdi.pipeline.testConn.loading')} { return ( - No results found. Please try again. + {t('rdi.pipeline.testConn.noResults')} ) @@ -81,14 +87,14 @@ const TestConnectionsPanel = (props: Props) => { - Source connections + {t('rdi.pipeline.testConn.source')} - Target connections + {t('rdi.pipeline.testConn.target')} diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/upload-modal/UploadModal.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/upload-modal/UploadModal.tsx index f5f6c924eb..cf572cd8f2 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/upload-modal/UploadModal.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/upload-modal/UploadModal.tsx @@ -15,6 +15,7 @@ import { setPipelineJobs, } from 'uiSrc/slices/rdi/pipeline' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { useTranslation } from 'uiSrc/i18n' import UploadDialog from './components/upload-dialog/UploadDialog' export interface Props { @@ -25,6 +26,7 @@ export interface Props { } const UploadModal = (props: Props) => { + const { t } = useTranslation() const { trigger, visible, onUploadedPipeline, onClose } = props const [isModalVisible, setIsModalVisible] = useState(visible) @@ -48,7 +50,7 @@ const UploadModal = (props: Props) => { const validateZip = (zip: JSZip) => { // check if config.yaml exists if (zip.file('config.yaml') === null) { - throw new Error('config.yaml is missing') + throw new Error(t('rdi.pipeline.upload.errorNoConfig')) } // check if job files exist @@ -56,7 +58,7 @@ const UploadModal = (props: Props) => { filename.startsWith('jobs/'), ) if (!jobFiles.length) { - throw new Error('No jobs folder found') + throw new Error(t('rdi.pipeline.upload.errorNoJobs')) } } diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/upload-modal/components/upload-dialog/UploadDialog.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/upload-modal/components/upload-dialog/UploadDialog.tsx index 729cf0c379..6d2d021a9a 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/upload-modal/components/upload-dialog/UploadDialog.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/upload-modal/components/upload-dialog/UploadDialog.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react' import { Text } from 'uiSrc/components/base/text' import ImportFileModal from 'uiSrc/components/import-file-modal' +import { useTranslation } from 'uiSrc/i18n' export interface Props { onClose: () => void @@ -13,10 +14,6 @@ export interface Props { loading: boolean } -const warningMessage = - 'If a new pipeline is uploaded, existing pipeline configuration and transformation' + - 'jobs will be overwritten. Changes will not be applied until the pipeline is deployed.' - const UploadDialog = ({ onClose, onConfirm, @@ -26,6 +23,7 @@ const UploadDialog = ({ error, loading, }: Props) => { + const { t } = useTranslation() const [isSubmitDisabled, setIsSubmitDisabled] = useState(true) const handleFileChange = (files: FileList | null) => { @@ -44,27 +42,29 @@ const UploadDialog = ({ onSubmit={onConfirm} title={ showWarning - ? 'Upload a new pipeline' - : 'Upload an archive with an RDI pipeline' + ? t('rdi.pipeline.upload.titleNew') + : t('rdi.pipeline.upload.titleArchive') } resultsTitle={ - !error ? 'Pipeline has been uploaded' : 'Failed to upload pipeline' - } - submitResults={ - A new pipeline has been successfully uploaded. + !error + ? t('rdi.pipeline.upload.resultSuccess') + : t('rdi.pipeline.upload.resultFail') } + submitResults={{t('rdi.pipeline.upload.submitResults')}} loading={loading} data={isUploaded} warning={ showWarning ? ( - {warningMessage} + + {t('rdi.pipeline.upload.warning')} + ) : null } error={error} - errorMessage="There was a problem with the .zip file" + errorMessage={t('rdi.pipeline.upload.errorZip')} isInvalid={false} isSubmitDisabled={isSubmitDisabled} - submitBtnText="Upload" + submitBtnText={t('rdi.pipeline.upload.submitButton')} acceptedFileExtension=".zip" /> ) diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/pages/config/Config.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/pages/config/Config.tsx index 7fe7871b00..26bebef5d7 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/pages/config/Config.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/pages/config/Config.tsx @@ -40,9 +40,11 @@ import { Text, Title } from 'uiSrc/components/base/text' import { Loader } from 'uiSrc/components/base/display' import { Col, FlexItem, Row } from 'uiSrc/components/base/layout/flex' import { Link } from '@redis-ui/components' +import { Trans, useTranslation } from 'uiSrc/i18n' import { StyledRdiDatabaseConfigContainer } from 'uiSrc/pages/rdi/pipeline-management/pages/config/styles' const Config = () => { + const { t } = useTranslation() const [isPanelOpen, setIsPanelOpen] = useState(false) const [isPopoverOpen, setIsPopoverOpen] = useState(false) @@ -149,7 +151,7 @@ const Config = () => { - Target database configuration + {t('rdi.pipeline.config.title')} { - {'Configure target instance '} - - connection details - - {' and applier settings.'} + + ), + }} + /> {pipelineLoading ? ( - + ) : ( { aria-labelledby="test target connections" data-testid="rdi-test-connection-btn" > - Test Connection + {t('rdi.pipeline.config.testButton')} diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/pages/job/Job.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/pages/job/Job.tsx index e192218274..c257259e73 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/pages/job/Job.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/pages/job/Job.tsx @@ -34,6 +34,7 @@ import { Loader } from 'uiSrc/components/base/display' import TemplateButton from '../../components/template-button' import { Col, FlexItem, Row } from 'uiSrc/components/base/layout/flex' import { Link, TextButton } from '@redis-ui/components' +import { Trans, useTranslation } from 'uiSrc/i18n' import { StyledRdiJobConfigContainer } from 'uiSrc/pages/rdi/pipeline-management/pages/job/styles' export interface Props { @@ -45,6 +46,7 @@ export interface Props { } const Job = (props: Props) => { + const { t } = useTranslation() const { name, value = '', deployedJobValue, jobIndex, rdiInstanceId } = props const [isPanelOpen, setIsPanelOpen] = useState(false) @@ -199,7 +201,7 @@ const Job = (props: Props) => { data-testid="open-dedicated-editor-btn" variant="primary-inline" > - SQL and JMESPath Editor + {t('rdi.pipeline.job.dedicatedEditorButton')} { - {'Create a job per source table to filter, transform, and '} - - map data - - {' to Redis.'} + + ), + }} + /> {loading ? (
- +
) : ( { disabled={isPanelOpen} data-testid="rdi-job-dry-run" > - Dry Run + {t('rdi.pipeline.job.dryRunButton')} From 442deaca36369f6d10b600ff2e2778d898e536a2 Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Mon, 20 Jul 2026 08:54:48 +0300 Subject: [PATCH 2/2] fix(i18n): keep rdi upload-failure telemetry locale-independent (RI-8277) Address review feedback: validateZip threw localized strings and the catch reused that message for the RDI_PIPELINE_UPLOAD_FAILED telemetry event, so the same failure reported different values per locale and split upload analytics. Throw canonical English messages (sent as-is to telemetry) and translate only the displayed error via a canonical->key map, falling back to the raw message for other errors. Co-Authored-By: Claude Opus 4.8 --- .../components/upload-modal/UploadModal.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/redisinsight/ui/src/pages/rdi/pipeline-management/components/upload-modal/UploadModal.tsx b/redisinsight/ui/src/pages/rdi/pipeline-management/components/upload-modal/UploadModal.tsx index cf572cd8f2..99ead2b1b2 100644 --- a/redisinsight/ui/src/pages/rdi/pipeline-management/components/upload-modal/UploadModal.tsx +++ b/redisinsight/ui/src/pages/rdi/pipeline-management/components/upload-modal/UploadModal.tsx @@ -18,6 +18,16 @@ import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { useTranslation } from 'uiSrc/i18n' import UploadDialog from './components/upload-dialog/UploadDialog' +const UPLOAD_VALIDATION_ERRORS = { + noConfig: 'config.yaml is missing', + noJobs: 'No jobs folder found', +} + +const UPLOAD_ERROR_LABEL_KEYS: Record = { + [UPLOAD_VALIDATION_ERRORS.noConfig]: 'rdi.pipeline.upload.errorNoConfig', + [UPLOAD_VALIDATION_ERRORS.noJobs]: 'rdi.pipeline.upload.errorNoJobs', +} + export interface Props { trigger?: React.ReactElement onUploadedPipeline?: () => void @@ -50,7 +60,7 @@ const UploadModal = (props: Props) => { const validateZip = (zip: JSZip) => { // check if config.yaml exists if (zip.file('config.yaml') === null) { - throw new Error(t('rdi.pipeline.upload.errorNoConfig')) + throw new Error(UPLOAD_VALIDATION_ERRORS.noConfig) } // check if job files exist @@ -58,7 +68,7 @@ const UploadModal = (props: Props) => { filename.startsWith('jobs/'), ) if (!jobFiles.length) { - throw new Error(t('rdi.pipeline.upload.errorNoJobs')) + throw new Error(UPLOAD_VALIDATION_ERRORS.noJobs) } } @@ -152,7 +162,8 @@ const UploadModal = (props: Props) => { }, }) - setError(errorMessage) + const labelKey = UPLOAD_ERROR_LABEL_KEYS[errorMessage] + setError(labelKey ? t(labelKey as never) : errorMessage) } }