diff --git a/.cursor/rules/translations.mdc b/.cursor/rules/translations.mdc index 7dd7021dec..dd5e48b3b8 100644 --- a/.cursor/rules/translations.mdc +++ b/.cursor/rules/translations.mdc @@ -42,6 +42,7 @@ yarn workspace @openshift-assisted/locales run validate_translation_files - **`lib/es/`, `lib/fr/`, `lib/ja/`, `lib/ko/`, `lib/zh/`** — Handled by a separate localization process. **Do not edit** those `translation.json` files. - **`libs/ui-lib/lib/ocm/**`** — No **`t('ai:…')`**; use plain English strings or text supplied via props. +- **Bare string literals in `i18nKey` or other translation props** — Always wrap `'ai:…'` strings with `t()` even when assigning to props like `i18nKey`. ## Allowed edits diff --git a/libs/locales/lib/en/translation.json b/libs/locales/lib/en/translation.json index 1d7552aecd..20e0178cbc 100644 --- a/libs/locales/lib/en/translation.json +++ b/libs/locales/lib/en/translation.json @@ -742,6 +742,7 @@ "ai:Progress": "Progress", "ai:Provide a comma separated list of valid DNS names or IP addresses.": "Provide a comma separated list of valid DNS names or IP addresses.", "ai:Provide a valid DNS name or IP Address": "Provide a valid DNS name or IP Address", + "ai:Provide a valid HTTP or HTTPS URL.": "Provide a valid HTTP or HTTPS URL.", "ai:Provide a valid HTTP URL.": "Provide a valid HTTP URL.", "ai:Provide an SSH key to be able to connect to the hosts for debugging purposes during the discovery process": "Provide an SSH key to be able to connect to the hosts for debugging purposes during the discovery process", "ai:Provide as many labels as you can to narrow the list to relevant hosts only.": "Provide as many labels as you can to narrow the list to relevant hosts.", @@ -978,7 +979,7 @@ "ai:Upload a YAML file": "Upload a YAML file", "ai:Upload a YAML file in NMstate format (not the entire NMstate config CR) that includes your network configuration (static IPs, bonds, etc.).": "Upload a YAML file in NMstate format (not the entire NMstate config CR) that includes your network configuration (static IPs, bonds, etc.).", "ai:Upload a YAML file with BareMetalHost and Secret resource definitions. Use the provided template as a reference.": "Upload a YAML file with BareMetalHost and Secret resource definitions. Use the provided template as a reference.", - "ai:URL must start with http (https schemes are not currently supported).": "URL must start with http (https schemes are not currently supported).", + "ai:URL must start with http or https.": "URL must start with http or https.", "ai:URL must start with http.": "URL must start with http.", "ai:Use a comma to separate each listed domain. Preface a domain with . to include its subdomains. Use * to bypass the proxy for all destinations.": "Use a comma to separate each listed domain. Preface a domain with . to include its subdomains. Use * to bypass the proxy for all destinations.", "ai:Use advanced networking": "Use advanced networking", diff --git a/libs/ui-lib/lib/cim/components/ClusterDeployment/use-networking-formik.ts b/libs/ui-lib/lib/cim/components/ClusterDeployment/use-networking-formik.ts index cc62cec2b1..2e8f3e1813 100644 --- a/libs/ui-lib/lib/cim/components/ClusterDeployment/use-networking-formik.ts +++ b/libs/ui-lib/lib/cim/components/ClusterDeployment/use-networking-formik.ts @@ -22,6 +22,7 @@ import { CLUSTER_DEFAULT_NETWORK_SETTINGS, clusterNetworksSchema, serviceNetworksSchema, + httpsProxyValidationSchema, } from '../../../common'; import { ClusterDeploymentNetworkingValues } from './types'; import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; @@ -65,7 +66,7 @@ const getNetworkConfigurationValidationSchema = ( ingressVips: vipArrayValidationSchema(hostSubnets, values, t), sshPublicKey: sshPublicKeyListValidationSchema(t), httpProxy: httpProxyValidationSchema({ values, pairValueName: 'httpsProxy', t }), - httpsProxy: httpProxyValidationSchema({ values, pairValueName: 'httpProxy', t }), // share the schema, httpS is currently not supported + httpsProxy: httpsProxyValidationSchema({ values, pairValueName: 'httpProxy', t }), noProxy: noProxyValidationSchema(t), }), ); diff --git a/libs/ui-lib/lib/cim/components/Hypershift/HostedClusterWizard/NetworkStep/NetworkStep.tsx b/libs/ui-lib/lib/cim/components/Hypershift/HostedClusterWizard/NetworkStep/NetworkStep.tsx index 5e7bf4bb34..a7d5a584a9 100644 --- a/libs/ui-lib/lib/cim/components/Hypershift/HostedClusterWizard/NetworkStep/NetworkStep.tsx +++ b/libs/ui-lib/lib/cim/components/Hypershift/HostedClusterWizard/NetworkStep/NetworkStep.tsx @@ -9,6 +9,7 @@ import { day2ApiVipValidationSchema, hostPrefixValidationSchema, httpProxyValidationSchema, + httpsProxyValidationSchema, ipBlockValidationSchema, noProxyValidationSchema, sshPublicKeyValidationSchema, @@ -29,7 +30,7 @@ const getValidationSchema = (t: TFunction) => ? hostPrefixValidationSchema(values.clusterNetworkCidr, t) : Yup.number(), httpProxy: httpProxyValidationSchema({ values, pairValueName: 'httpsProxy', t }), - httpsProxy: httpProxyValidationSchema({ values, pairValueName: 'httpProxy', t }), // share the schema, httpS is currently not supported + httpsProxy: httpsProxyValidationSchema({ values, pairValueName: 'httpProxy', t }), // share the schema, httpS is currently not supported noProxy: noProxyValidationSchema(t), nodePortAddress: values.apiPublishingStrategy === 'NodePort' diff --git a/libs/ui-lib/lib/cim/components/InfraEnv/InfraEnvFormPage.tsx b/libs/ui-lib/lib/cim/components/InfraEnv/InfraEnvFormPage.tsx index ae97eb1708..718302df7e 100644 --- a/libs/ui-lib/lib/cim/components/InfraEnv/InfraEnvFormPage.tsx +++ b/libs/ui-lib/lib/cim/components/InfraEnv/InfraEnvFormPage.tsx @@ -42,6 +42,7 @@ import { ntpSourceValidationSchema, CpuArchitecture, getOCPStaticIPDocLink, + httpsProxyValidationSchema, } from '../../../common'; import './infra-env.css'; @@ -85,7 +86,7 @@ const validationSchema = (usedNames: string[], t: TFunction) => allowEmpty: true, t, }), - httpsProxy: httpProxyValidationSchema({ + httpsProxy: httpsProxyValidationSchema({ values, pairValueName: 'httpProxy', allowEmpty: true, diff --git a/libs/ui-lib/lib/cim/components/modals/AddHostModal.tsx b/libs/ui-lib/lib/cim/components/modals/AddHostModal.tsx index 3c2a1c2906..bb79f7b8c0 100644 --- a/libs/ui-lib/lib/cim/components/modals/AddHostModal.tsx +++ b/libs/ui-lib/lib/cim/components/modals/AddHostModal.tsx @@ -9,7 +9,8 @@ import { ModalVariant, Spinner, } from '@patternfly/react-core'; -import { DownloadIso, DiscoveryImageConfigForm, DiscoveryImageFormValues } from '../../../common'; +import { DownloadIso } from '../../../common'; +import { DiscoveryImageConfigForm, DiscoveryImageFormValues } from './DiscoveryImageConfigForm'; import { AddHostModalProps } from './types'; import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; import { EnvironmentErrors } from '../InfraEnv/EnvironmentErrors'; diff --git a/libs/ui-lib/lib/common/components/clusterConfiguration/DiscoveryImageConfigForm.tsx b/libs/ui-lib/lib/cim/components/modals/DiscoveryImageConfigForm.tsx similarity index 75% rename from libs/ui-lib/lib/common/components/clusterConfiguration/DiscoveryImageConfigForm.tsx rename to libs/ui-lib/lib/cim/components/modals/DiscoveryImageConfigForm.tsx index 3ff8956af2..014638fa3d 100644 --- a/libs/ui-lib/lib/common/components/clusterConfiguration/DiscoveryImageConfigForm.tsx +++ b/libs/ui-lib/lib/cim/components/modals/DiscoveryImageConfigForm.tsx @@ -11,7 +11,6 @@ import { ModalBody, ModalFooter, } from '@patternfly/react-core'; -import { ExternalLinkAltIcon } from '@patternfly/react-icons/dist/js/icons/external-link-alt-icon'; import { Formik, FormikHelpers } from 'formik'; import { ImageCreateParams, @@ -21,40 +20,16 @@ import { import { AlertFormikError } from '../../../common/components/ui'; import { httpProxyValidationSchema, + httpsProxyValidationSchema, noProxyValidationSchema, sshPublicKeyValidationSchema, -} from '../../validationSchemas'; -import { ProxyFieldsType, StatusErrorType } from '../../types'; -import ProxyFields from './ProxyFields'; -import UploadSSH from './UploadSSH'; -import DiscoveryImageTypeControlGroup from './DiscoveryImageTypeControlGroup'; -import { getOCPStaticIPDocLink } from '../../config/docs_links'; -import { useTranslation } from '../../hooks/use-translation-wrapper'; - -export const StaticIPInfo = ({ docVersion }: { docVersion?: string }) => { - const { t } = useTranslation(); - return ( - - - - ); -}; +} from '../../../common'; +import { ProxyFieldsType, StatusErrorType } from '../../../common/types'; +import ProxyFields from '../../../common/components/clusterConfiguration/ProxyFields'; +import UploadSSH from '../../../common/components/clusterConfiguration/UploadSSH'; +import DiscoveryImageTypeControlGroup from '../../../common/components/clusterConfiguration/DiscoveryImageTypeControlGroup'; +import { StaticIPInfo } from '../../../common/components/clusterConfiguration/StaticIPInfo'; +import { useTranslation } from '../../../common/hooks/use-translation-wrapper'; export type DiscoveryImageFormValues = ImageCreateParams & ProxyFieldsType; @@ -63,7 +38,12 @@ const getValidationSchema = (allowEmpty: boolean, t: TFunction) => Yup.object().shape({ sshPublicKey: sshPublicKeyValidationSchema(t), httpProxy: httpProxyValidationSchema({ values, pairValueName: 'httpsProxy', allowEmpty, t }), - httpsProxy: httpProxyValidationSchema({ values, pairValueName: 'httpProxy', allowEmpty, t }), // share the schema, httpS is currently not supported + httpsProxy: httpsProxyValidationSchema({ + values, + pairValueName: 'httpProxy', + allowEmpty, + t, + }), noProxy: noProxyValidationSchema(t), }), ); diff --git a/libs/ui-lib/lib/cim/components/modals/EditProxyModal.tsx b/libs/ui-lib/lib/cim/components/modals/EditProxyModal.tsx index cb8df33021..b6a51b85bf 100644 --- a/libs/ui-lib/lib/cim/components/modals/EditProxyModal.tsx +++ b/libs/ui-lib/lib/cim/components/modals/EditProxyModal.tsx @@ -20,6 +20,7 @@ import { ProxyInputFields } from '../../../common/components/clusterConfiguratio import { Formik, useFormikContext } from 'formik'; import { httpProxyValidationSchema, + httpsProxyValidationSchema, noProxyValidationSchema, ProxyFieldsType, } from '../../../common'; @@ -35,7 +36,7 @@ const validationSchema = (t: TFunction) => allowEmpty: true, t, }), - httpsProxy: httpProxyValidationSchema({ + httpsProxy: httpsProxyValidationSchema({ values, pairValueName: 'httpProxy', allowEmpty: true, diff --git a/libs/ui-lib/lib/cim/components/modals/types.ts b/libs/ui-lib/lib/cim/components/modals/types.ts index 8d27c598d2..e259ea4679 100644 --- a/libs/ui-lib/lib/cim/components/modals/types.ts +++ b/libs/ui-lib/lib/cim/components/modals/types.ts @@ -1,4 +1,5 @@ -import { DiscoveryImageFormValues, ProxyFieldsType } from '../../../common'; +import { ProxyFieldsType } from '../../../common'; +import { DiscoveryImageFormValues } from './DiscoveryImageConfigForm'; import { BMCFormProps } from '../Agent/types'; import { BareMetalHostK8sResource, diff --git a/libs/ui-lib/lib/common/components/clusterConfiguration/DownloadIso.tsx b/libs/ui-lib/lib/common/components/clusterConfiguration/DownloadIso.tsx index 1237acf50f..ceb1758877 100644 --- a/libs/ui-lib/lib/common/components/clusterConfiguration/DownloadIso.tsx +++ b/libs/ui-lib/lib/common/components/clusterConfiguration/DownloadIso.tsx @@ -12,7 +12,7 @@ import { } from '@patternfly/react-core'; import { DetailItem, DetailList } from '../ui'; import DiscoveryInstructions from './DiscoveryInstructions'; -import { StaticIPInfo } from './DiscoveryImageConfigForm'; +import { StaticIPInfo } from './StaticIPInfo'; import { useTranslation } from '../../hooks/use-translation-wrapper'; import { ExternalLinkAltIcon } from '@patternfly/react-icons/dist/js/icons/external-link-alt-icon'; import { getCiscoIntersightLink } from '../../config'; diff --git a/libs/ui-lib/lib/common/components/clusterConfiguration/ProxyFields.tsx b/libs/ui-lib/lib/common/components/clusterConfiguration/ProxyFields.tsx index 4edabb08aa..13d0e8afb9 100644 --- a/libs/ui-lib/lib/common/components/clusterConfiguration/ProxyFields.tsx +++ b/libs/ui-lib/lib/common/components/clusterConfiguration/ProxyFields.tsx @@ -64,7 +64,7 @@ export const ProxyInputFields = () => { }} - i18nKey="ai:URL must start with http (https schemes are not currently supported)." + i18nKey={'ai:URL must start with http or https.'} /> } diff --git a/libs/ui-lib/lib/common/components/clusterConfiguration/StaticIPInfo.tsx b/libs/ui-lib/lib/common/components/clusterConfiguration/StaticIPInfo.tsx new file mode 100644 index 0000000000..68d26c13c8 --- /dev/null +++ b/libs/ui-lib/lib/common/components/clusterConfiguration/StaticIPInfo.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Alert, Button } from '@patternfly/react-core'; +import { ExternalLinkAltIcon } from '@patternfly/react-icons/dist/js/icons/external-link-alt-icon'; +import { getOCPStaticIPDocLink } from '../../config/docs_links'; +import { useTranslation } from '../../hooks/use-translation-wrapper'; + +export const StaticIPInfo = ({ docVersion }: { docVersion?: string }) => { + const { t } = useTranslation(); + return ( + + + + ); +}; diff --git a/libs/ui-lib/lib/common/components/clusterConfiguration/index.ts b/libs/ui-lib/lib/common/components/clusterConfiguration/index.ts index 91962f2f1e..0b468a048d 100644 --- a/libs/ui-lib/lib/common/components/clusterConfiguration/index.ts +++ b/libs/ui-lib/lib/common/components/clusterConfiguration/index.ts @@ -8,5 +8,5 @@ export { default as SecurityFields } from './SecurityFields'; export * from './utils'; export * from './DiscoveryTroubleshootingModal'; -export * from './DiscoveryImageConfigForm'; +export * from './StaticIPInfo'; export * from './ExternalPlatformsDropdown'; diff --git a/libs/ui-lib/lib/common/validationSchemas/proxyValidation.tsx b/libs/ui-lib/lib/common/validationSchemas/proxyValidation.tsx index 55103ab838..05bd087a48 100644 --- a/libs/ui-lib/lib/common/validationSchemas/proxyValidation.tsx +++ b/libs/ui-lib/lib/common/validationSchemas/proxyValidation.tsx @@ -48,6 +48,49 @@ export const httpProxyValidationSchema = ({ ); }; +export const httpsProxyValidationSchema = ({ + values, + pairValueName, + allowEmpty, + t, +}: { + values: ProxyFieldsType; + pairValueName: 'httpProxy' | 'httpsProxy'; + allowEmpty?: boolean; + t: TFunction; +}) => { + const validation = Yup.string().test( + 'http-proxy-validation', + t('ai:Provide a valid HTTP or HTTPS URL.'), + (value?: string) => { + if (!value) { + return true; + } + + if (!['http://', 'https://'].some((protocol) => value.startsWith(protocol))) { + return false; + } + + try { + new URL(value); + } catch { + return false; + } + return true; + }, + ); + + if (allowEmpty) { + return validation; + } + + return validation.test( + 'http-proxy-no-empty-validation', + t('ai:At least one of the HTTP or HTTPS proxy URLs is required.'), + (value) => !values.enableProxy || !!value || !!values[pairValueName], + ); +}; + export const noProxyValidationSchema = (t: TFunction) => Yup.string().test( 'no-proxy-validation', diff --git a/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmDiscoveryImageConfigForm.tsx b/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmDiscoveryImageConfigForm.tsx index eef443ada7..00b9789ced 100644 --- a/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmDiscoveryImageConfigForm.tsx +++ b/libs/ui-lib/lib/ocm/components/clusterConfiguration/OcmDiscoveryImageConfigForm.tsx @@ -35,6 +35,7 @@ import DiscoveryImageTypeDropdown, { discoveryImageTypes } from './DiscoveryImag import CertificateFields from '../../../common/components/clusterConfiguration/CertificateFields'; import { httpProxyValidationSchema, + httpsProxyValidationSchema, noProxyValidationSchema, sshPublicKeyValidationSchema, } from '../../../common'; @@ -60,7 +61,7 @@ const validationSchema = (t: TFunction) => Yup.object().shape({ sshPublicKey: sshPublicKeyValidationSchema(t), httpProxy: httpProxyValidationSchema({ values, pairValueName: 'httpsProxy', t }), - httpsProxy: httpProxyValidationSchema({ values, pairValueName: 'httpProxy', t }), // share the schema, httpS is currently not supported + httpsProxy: httpsProxyValidationSchema({ values, pairValueName: 'httpProxy', t }), noProxy: noProxyValidationSchema(t), }), );