diff --git a/e2e-tests/playwright/lib/src/server/default_config.ts b/e2e-tests/playwright/lib/src/server/default_config.ts index a7ca9acb4ac..2e157ad5f5c 100644 --- a/e2e-tests/playwright/lib/src/server/default_config.ts +++ b/e2e-tests/playwright/lib/src/server/default_config.ts @@ -820,4 +820,17 @@ const defaultServerConfig: AdminConfig = { TeamAdminsAsReviewers: true, }, }, + AutoTranslationSettings: { + Enable: false, + Provider: '', + LibreTranslate: { + URL: '', + APIKey: '', + }, + TimeoutMs: { + NewPost: 800, + Fetch: 2000, + Notification: 300, + }, + }, }; diff --git a/server/public/model/feature_flags.go b/server/public/model/feature_flags.go index 04655498cd4..9278bf1d591 100644 --- a/server/public/model/feature_flags.go +++ b/server/public/model/feature_flags.go @@ -113,6 +113,7 @@ func (f *FeatureFlags) SetDefaults() { f.ContentFlagging = false f.InteractiveDialogAppsForm = true f.EnableMattermostEntry = true + f.MobileSSOCodeExchange = true // FEATURE_FLAG_REMOVAL: AutoTranslation - Remove this default when MVP is to be released diff --git a/webapp/channels/src/components/admin_console/admin_definition.tsx b/webapp/channels/src/components/admin_console/admin_definition.tsx index cc084f45e69..6f91fdb9210 100644 --- a/webapp/channels/src/components/admin_console/admin_definition.tsx +++ b/webapp/channels/src/components/admin_console/admin_definition.tsx @@ -83,6 +83,7 @@ import { SystemRolesFeatureDiscovery, } from './feature_discovery/features'; import AttributeBasedAccessControlFeatureDiscovery from './feature_discovery/features/attribute_based_access_control'; +import AutoTranslationFeatureDiscovery from './feature_discovery/features/auto_translation'; import UserAttributesFeatureDiscovery from './feature_discovery/features/user_attributes'; import FeatureFlags, {messages as featureFlagsMessages} from './feature_flags'; import GroupDetails from './group_settings/group_details'; @@ -91,6 +92,8 @@ import IPFiltering from './ip_filtering'; import LDAPWizard from './ldap_wizard'; import LicenseSettings from './license_settings'; import {searchableStrings as licenseSettingsSearchableStrings} from './license_settings/license_settings'; +import AutoTranslation, {searchableStrings as autoTranslationSearchableStrings} from './localization/auto_translation'; +import Localization, {searchableStrings as localizationSearchableStrings} from './localization/localization'; import MessageExportSettings, {searchableStrings as messageExportSearchableStrings} from './message_export_settings'; import OpenIdConvert from './openid_convert'; import PasswordSettings, {searchableStrings as passwordSearchableStrings} from './password_settings'; @@ -2437,51 +2440,38 @@ const AdminDefinition: AdminDefinitionType = { url: 'site_config/localization', title: defineMessage({id: 'admin.sidebar.localization', defaultMessage: 'Localization'}), isHidden: it.not(it.userHasReadPermissionOnResource(RESOURCE_KEYS.SITE.LOCALIZATION)), + searchableStrings: localizationSearchableStrings.concat(autoTranslationSearchableStrings), schema: { id: 'LocalizationSettings', name: defineMessage({id: 'admin.site.localization', defaultMessage: 'Localization'}), settings: [ { - type: 'language', - key: 'LocalizationSettings.DefaultServerLocale', - label: defineMessage({id: 'admin.general.localization.serverLocaleTitle', defaultMessage: 'Default Server Language:'}), - help_text: defineMessage({id: 'admin.general.localization.serverLocaleDescription', defaultMessage: 'Default language for system messages.'}), + type: 'custom', + key: 'LocalizationSettings', + component: Localization, isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.LOCALIZATION)), }, { - type: 'language', - key: 'LocalizationSettings.DefaultClientLocale', - label: defineMessage({id: 'admin.general.localization.clientLocaleTitle', defaultMessage: 'Default Client Language:'}), - help_text: defineMessage({id: 'admin.general.localization.clientLocaleDescription', defaultMessage: 'Default language for newly created users and pages where the user hasn\'t logged in.'}), - isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.LOCALIZATION)), + type: 'custom', + key: 'AutoTranslationSettings', + component: AutoTranslation, + isHidden: it.any( + it.configIsFalse('FeatureFlags', 'AutoTranslation'), + it.not(it.minLicenseTier(LicenseSkus.EnterpriseAdvanced)), + ), }, { - type: 'language', - key: 'LocalizationSettings.AvailableLocales', - label: defineMessage({id: 'admin.general.localization.availableLocalesTitle', defaultMessage: 'Available Languages:'}), - help_text: defineMessage({id: 'admin.general.localization.availableLocalesDescription', defaultMessage: 'Set which languages are available for users in Settings > Display > Language (leave this field blank to have all supported languages available). If you\'re manually adding new languages, the Default Client Language must be added before saving this setting.\n \nWould like to help with translations? Join the Mattermost Translation Server to contribute.'}), - help_text_markdown: false, - help_text_values: { - link: (msg: string) => ( - - {msg} - + type: 'custom', + key: 'auto-translation-discovery', + component: AutoTranslationFeatureDiscovery, + isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.ABOUT.EDITION_AND_LICENSE)), + isHidden: it.any( + it.all( + it.configIsTrue('FeatureFlags', 'AutoTranslation'), + it.minLicenseTier(LicenseSkus.EnterpriseAdvanced), ), - strong: (msg: string) => {msg}, - }, - multiple: true, - no_result: defineMessage({id: 'admin.general.localization.availableLocalesNoResults', defaultMessage: 'No results found'}), - isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.LOCALIZATION)), - }, - { - type: 'bool', - key: 'LocalizationSettings.EnableExperimentalLocales', - label: defineMessage({id: 'admin.general.localization.enableExperimentalLocalesTitle', defaultMessage: 'Enable Experimental Locales:'}), - help_text: defineMessage({id: 'admin.general.localization.enableExperimentalLocalesDescription', defaultMessage: 'When true, it allows users to select experimental (e.g., in progress) languages.'}), - isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.LOCALIZATION)), + it.configIsFalse('FeatureFlags', 'AutoTranslation'), + ), }, ], }, diff --git a/webapp/channels/src/components/admin_console/feature_discovery/feature_discovery.tsx b/webapp/channels/src/components/admin_console/feature_discovery/feature_discovery.tsx index 63e8099cde1..6a62855f2e3 100644 --- a/webapp/channels/src/components/admin_console/feature_discovery/feature_discovery.tsx +++ b/webapp/channels/src/components/admin_console/feature_discovery/feature_discovery.tsx @@ -14,6 +14,7 @@ import AlertBanner from 'components/alert_banner'; import ExternalLink from 'components/external_link'; import StartTrialBtn from 'components/learn_more_trial_modal/start_trial_btn'; import LoadingSpinner from 'components/widgets/loading/loading_spinner'; +import SkuTag from 'components/widgets/tag/sku_tag'; import type {LicenseSkus} from 'utils/constants'; import {AboutLinks, LicenseLinks} from 'utils/constants'; @@ -49,6 +50,7 @@ type Props = { isSubscriptionLoaded: boolean; isPaidSubscription: boolean; customer?: CloudCustomer; + showSkuTag?: boolean; } type State = { @@ -203,6 +205,7 @@ export default class FeatureDiscovery extends React.PureComponent isCloud, isCloudTrial, isSubscriptionLoaded, + showSkuTag, } = this.props; // on first load the license information is available and we can know if it is cloud license, but the subscription is not loaded yet @@ -277,6 +280,11 @@ export default class FeatureDiscovery extends React.PureComponent data-testid='featureDiscovery' >
+ {showSkuTag && + }
{ + return ( + + +
+ Only available in Enterprise Advanced.', + values: {strong: (msg: string) => {msg}, br:
}, + })} + learnMoreURL='https://docs.mattermost.com' + featureDiscoveryImage={ + + } + /> +
+
+
+ ); +}; + +export default AutoTranslationFeatureDiscovery; diff --git a/webapp/channels/src/components/admin_console/feature_discovery/features/images/auto_translate_svg.tsx b/webapp/channels/src/components/admin_console/feature_discovery/features/images/auto_translate_svg.tsx new file mode 100644 index 00000000000..39094e95da0 --- /dev/null +++ b/webapp/channels/src/components/admin_console/feature_discovery/features/images/auto_translate_svg.tsx @@ -0,0 +1,185 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import * as React from 'react'; + +type SvgProps = { + width?: number; + height?: number; +} + +const AutoTranslationSVG = (props: SvgProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); +export default AutoTranslationSVG; diff --git a/webapp/channels/src/components/admin_console/localization/auto_translation.tsx b/webapp/channels/src/components/admin_console/localization/auto_translation.tsx new file mode 100644 index 00000000000..6f5e798572e --- /dev/null +++ b/webapp/channels/src/components/admin_console/localization/auto_translation.tsx @@ -0,0 +1,135 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useCallback, useMemo, useState} from 'react'; +import {defineMessages, FormattedMessage} from 'react-intl'; + +import type {AutoTranslationSettings} from '@mattermost/types/config'; + +import DropdownSetting from 'components/admin_console/dropdown_setting'; +import { + AdminSection, + SectionContent, + SectionHeader, +} from 'components/admin_console/system_properties/controls'; +import Toggle from 'components/toggle'; + +import AutoTranslationInfo from './auto_translation_info'; +import LibreTranslateSettings from './libreTranslate_settings'; + +import type {SystemConsoleCustomSettingsComponentProps} from '../schema_admin_settings'; +import './localization.scss'; +import type {SearchableStrings} from '../types'; + +const messages = defineMessages({ + enableAutoTranslationTitle: { + id: 'admin.site.localization.enableAutoTranslationTitle', + defaultMessage: 'Auto-translation', + }, + enableAutoTranslationDescription: { + id: 'admin.site.localization.enableAutoTranslationDescription', + defaultMessage: 'Configure auto-translation for channels and direct messages', + }, +}); + +export const searchableStrings: SearchableStrings = Object.values(messages); + +export default function AutoTranslation(props: SystemConsoleCustomSettingsComponentProps) { + const [autoTranslationSettings, setAutoTranslationSettings] = useState(props.value as AutoTranslationSettings); + + const handleChange = useCallback((id: string, value: any) => { + const updatedSettings = { + ...autoTranslationSettings, + [id]: value, + }; + setAutoTranslationSettings(updatedSettings); + props.onChange(props.id, updatedSettings); + }, [props, autoTranslationSettings]); + + const handleToggle = useCallback(() => { + handleChange('Enable', !autoTranslationSettings.Enable); + }, [autoTranslationSettings, handleChange]); + + const providerHelpTextValues = useMemo(() => ({ + br:
, + strong: (msg: React.ReactNode) => {msg}, + }), []); + + const on = ( + + ); + const off = ( + + ); + + return ( + + +
+
+

+ +

+
+ +
+
+
+ + {autoTranslationSettings.Enable ? on : off} + + +
+
+
+ {autoTranslationSettings.Enable && + + + } + values={[ + {value: 'libretranslate', text: 'LibreTranslate'}, + ]} + helpText={ + + } + value={autoTranslationSettings.Provider || 'libretranslate'} + disabled={props.disabled || props.setByEnv} + setByEnv={props.setByEnv} + onChange={handleChange} + /> + {autoTranslationSettings.Provider === 'libretranslate' && + + } + + + } +
+ ); +} diff --git a/webapp/channels/src/components/admin_console/localization/auto_translation_info.scss b/webapp/channels/src/components/admin_console/localization/auto_translation_info.scss new file mode 100644 index 00000000000..8a3c26b66bf --- /dev/null +++ b/webapp/channels/src/components/admin_console/localization/auto_translation_info.scss @@ -0,0 +1,3 @@ +.autoTranslationInfo .sectionNoticeTitle { + margin-bottom: 0; +} diff --git a/webapp/channels/src/components/admin_console/localization/auto_translation_info.tsx b/webapp/channels/src/components/admin_console/localization/auto_translation_info.tsx new file mode 100644 index 00000000000..f7c498e9fc6 --- /dev/null +++ b/webapp/channels/src/components/admin_console/localization/auto_translation_info.tsx @@ -0,0 +1,32 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {FormattedMessage, useIntl} from 'react-intl'; + +import './auto_translation_info.scss'; + +import SectionNotice from 'components/section_notice'; + +const AutoTranslationInfo = () => { + const intl = useIntl(); + return ( +
+ + } + text={intl.formatMessage({ + id: 'admin.site.localization.autoTranslationInfoSecondary', + defaultMessage: 'When multiple languages are detected, users will be prompted to enable auto-translation. [Learn more](https://docs.mattermost.com/).', + })} + type='info' + /> +
+ ); +}; + +export default React.memo(AutoTranslationInfo); diff --git a/webapp/channels/src/components/admin_console/localization/libreTranslate_settings.tsx b/webapp/channels/src/components/admin_console/localization/libreTranslate_settings.tsx new file mode 100644 index 00000000000..b2e1bb1503c --- /dev/null +++ b/webapp/channels/src/components/admin_console/localization/libreTranslate_settings.tsx @@ -0,0 +1,89 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useCallback, useState} from 'react'; +import {defineMessage, FormattedMessage} from 'react-intl'; + +import type {AutoTranslationSettings} from '@mattermost/types/config'; + +import TextSetting from 'components/admin_console/text_setting'; +import ExternalLink from 'components/external_link'; + +import './localization.scss'; + +import {type SystemConsoleCustomSettingsComponentProps} from '../schema_admin_settings'; + +type LibreTranslateSettings = { + URL: string; + APIKey: string; +} + +export default function LibreTranslateSettings(props: SystemConsoleCustomSettingsComponentProps) { + const values = props.value as AutoTranslationSettings; + const [libreTranslateSettings, setLibreTranslateSettings] = useState(values.LibreTranslate); + + const handleChange = useCallback((id: string, value: any) => { + const updatedSettings = { + ...libreTranslateSettings, + [id]: value, + }; + setLibreTranslateSettings(updatedSettings); + props.onChange('LibreTranslate', updatedSettings); + }, [props, libreTranslateSettings]); + + return ( + <> + + } + placeholder={defineMessage({ + id: 'admin.site.localization.autoTranslationProviderLibreTranslateURLExample', + defaultMessage: 'e.g.: "https://libretranslate.yourdomain.com"', + })} + type='url' + value={libreTranslateSettings.URL} + setByEnv={false} + onChange={handleChange} + disabled={props.disabled} + /> + + } + placeholder={defineMessage({ + id: 'admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyExample', + defaultMessage: 'Enter LibreTranslate API Key', + })} + helpText={ + ( + + {msg} + + ), + }} + />} + type='password' + value={libreTranslateSettings.APIKey} + setByEnv={false} + onChange={handleChange} + disabled={props.disabled} + /> + + ); +} diff --git a/webapp/channels/src/components/admin_console/localization/localization.scss b/webapp/channels/src/components/admin_console/localization/localization.scss new file mode 100644 index 00000000000..ed1d61bc414 --- /dev/null +++ b/webapp/channels/src/components/admin_console/localization/localization.scss @@ -0,0 +1,31 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +.autotranslation-section-header { + display: flex; + width: 100%; + min-width: 0; // prevents overflow in flex layouts + align-items: flex-start; + justify-content: space-between; + gap: 24px; +} + +.autotranslation-section-toggle { + display: flex; + align-items: center; + align-self: center; +} + +.localization-section-title { + margin: unset; + font-size: 16px; +} + +.localization-section-description { + margin-bottom: 0; + color: var(--center-channel-color-72); + font-size: 14px; + font-style: normal; + font-weight: 400; +} + diff --git a/webapp/channels/src/components/admin_console/localization/localization.tsx b/webapp/channels/src/components/admin_console/localization/localization.tsx new file mode 100644 index 00000000000..9b96d47fea1 --- /dev/null +++ b/webapp/channels/src/components/admin_console/localization/localization.tsx @@ -0,0 +1,192 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useCallback, useMemo, useState} from 'react'; +import {defineMessages, FormattedMessage} from 'react-intl'; +import styled from 'styled-components'; + +import type {LocalizationSettings} from '@mattermost/types/config'; + +import BooleanSetting from 'components/admin_console/boolean_setting'; +import DropdownSetting from 'components/admin_console/dropdown_setting'; +import MultiSelectSetting from 'components/admin_console/multiselect_settings'; +import { + SectionContent, + SectionHeader, +} from 'components/admin_console/system_properties/controls'; +import ExternalLink from 'components/external_link'; + +import * as I18n from 'i18n/i18n.jsx'; + +import type {SystemConsoleCustomSettingsComponentProps} from '../schema_admin_settings'; +import type {SearchableStrings} from '../types'; +import './localization.scss'; + +const locales = I18n.getAllLanguages(); + +const AdminSection = styled.section.attrs({className: 'AdminPanel'})` + && { + overflow: visible; + margin-top: 0; + } +`; + +const messages = defineMessages({ + langTitle: { + id: 'admin.site.localization.languages.title', + defaultMessage: 'Languages', + }, + langDescription: { + id: 'admin.site.localization.languages.description', + defaultMessage: 'Choose which languages should be the defaults', + }, + serverLocaleTitle: { + id: 'admin.general.localization.serverLocaleTitle', + defaultMessage: 'Default Server Language:', + }, + serverLocaleDescription: { + id: 'admin.general.localization.serverLocaleDescription', + defaultMessage: 'Default language for system messages.', + }, + clientLocaleTitle: { + id: 'admin.general.localization.clientLocaleTitle', + defaultMessage: 'Default Client Language:', + }, + clientLocaleDescription: { + id: 'admin.general.localization.clientLocaleDescription', + defaultMessage: "Default language for newly created users and pages where the user hasn't logged in.", + }, + availableLocalesTitle: { + id: 'admin.general.localization.availableLocalesTitle', + defaultMessage: 'Available Languages:', + }, + availableLocalesDescription: { + id: 'admin.general.localization.availableLocalesDescription', + defaultMessage: "Set which languages are available for users in Settings > Display > Language (leave this field blank to have all supported languages available). If you're manually adding new languages, the Default Client Language must be added before saving this setting.\n \nWould like to help with translations? Join the Mattermost Translation Server to contribute.", + }, + availableLocalesNoResults: { + id: 'admin.general.localization.availableLocalesNoResults', + defaultMessage: 'No results found', + }, + enableExperimentalLocalesTitle: { + id: 'admin.general.localization.enableExperimentalLocalesTitle', + defaultMessage: 'Enable Experimental Locales:', + }, + enableExperimentalLocalesDescription: { + id: 'admin.general.localization.enableExperimentalLocalesDescription', + defaultMessage: 'When true, it allows users to select experimental (e.g., in progress) languages.', + }, +}); + +export const searchableStrings: SearchableStrings = Object.values(messages); + +export default function Localization(props: SystemConsoleCustomSettingsComponentProps) { + const [localizationSettings, setLocalizationSettings] = useState(props.value as LocalizationSettings); + + const handleChange = useCallback((id: string, value: any) => { + const updatedSettings = { + ...localizationSettings, + [id]: value, + }; + setLocalizationSettings(updatedSettings); + props.onChange(props.id, updatedSettings); + }, [props, localizationSettings]); + + const availableLanguages = useMemo(() => { + const values: Array<{value: string; text: string; order: number}> = []; + for (const l of Object.values(locales)) { + values.push({value: l.value, text: l.name, order: l.order}); + } + values.sort((a, b) => a.order - b.order); + return values; + }, []); + + return ( + + +
+

+ +

+
+ +
+
+
+ + + + } + values={availableLanguages} + helpText={ + + } + value={localizationSettings.DefaultServerLocale || availableLanguages[0].value} + disabled={props.disabled} + setByEnv={props.setByEnv} + onChange={handleChange} + /> + + } + values={availableLanguages} + helpText={ + + } + value={localizationSettings.DefaultClientLocale || availableLanguages[0].value} + disabled={props.disabled} + setByEnv={props.setByEnv} + onChange={handleChange} + /> + + } + values={availableLanguages} + helpText={ + ( + + {msg} + + ), + strong: (msg: React.ReactNode) => {msg}, + }} + /> + } + selected={(localizationSettings.AvailableLocales.split(',')) || []} + disabled={props.disabled} + setByEnv={props.setByEnv} + onChange={(changedId, value) => handleChange(changedId, value.join(','))} + noOptionsMessage={ + + } + /> + + } + helpText={ + + } + value={localizationSettings.EnableExperimentalLocales} + disabled={props.disabled} + setByEnv={props.setByEnv} + onChange={handleChange} + /> + +
+ ); +} diff --git a/webapp/channels/src/components/widgets/tag/sku_tag.test.tsx b/webapp/channels/src/components/widgets/tag/sku_tag.test.tsx new file mode 100644 index 00000000000..9155ba589ca --- /dev/null +++ b/webapp/channels/src/components/widgets/tag/sku_tag.test.tsx @@ -0,0 +1,21 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +import {render, screen} from 'tests/react_testing_utils'; +import {LicenseSkus} from 'utils/constants'; + +import SkuTag from './sku_tag'; + +describe('components/widgets/tag/SkuTag', () => { + test('should match the ENTRY SKU', () => { + render(); + expect(screen.getByText('ENTRY')).toBeInTheDocument(); + }); + + test('should match the ENTERPRISE ADVANCED SKU', () => { + render(); + expect(screen.getByText('ENTERPRISE ADVANCED')).toBeInTheDocument(); + }); +}); diff --git a/webapp/channels/src/components/widgets/tag/sku_tag.tsx b/webapp/channels/src/components/widgets/tag/sku_tag.tsx new file mode 100644 index 00000000000..5e1c8feaf87 --- /dev/null +++ b/webapp/channels/src/components/widgets/tag/sku_tag.tsx @@ -0,0 +1,50 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import classNames from 'classnames'; +import React, {useMemo} from 'react'; + +import {LicenseSkus} from 'utils/constants'; + +import Tag from './tag'; +import type {TagSize} from './tag'; + +type Props = { + className?: string; + size?: TagSize; + sku: LicenseSkus; +}; + +const SkuTag = ({className = '', size = 'xs', sku}: Props) => { + const namedSku = useMemo(() => { + switch (sku) { + case LicenseSkus.Starter: + return 'STARTER'; + case LicenseSkus.Professional: + return 'PROFESSIONAL'; + case LicenseSkus.Enterprise: + return 'ENTERPRISE'; + case LicenseSkus.E10: + return 'ENTERPRISE E10'; + case LicenseSkus.E20: + return 'ENTERPRISE E20'; + case LicenseSkus.EnterpriseAdvanced: + return 'ENTERPRISE ADVANCED'; + case LicenseSkus.Entry: + return 'ENTRY'; + default: + return 'UNKNOWN'; + } + }, [sku]); + + return ( + + ); +}; + +export default SkuTag; diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index 34b04721559..64308419a3d 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -389,6 +389,8 @@ "admin.authentication.openid": "OpenID Connect", "admin.authentication.saml": "SAML 2.0", "admin.authentication.signup": "Signup", + "admin.auto_translation_feature_discovery.copy": "Effortlessly collaborate across languages with auto-translation. Messages in shared channels are instantly translated based on each user’s language preference—no extra steps required.{br}Only available in Enterprise Advanced.", + "admin.auto_translation_feature_discovery.title": "Remove language barriers with auto-translation", "admin.banner.heading": "Note:", "admin.billing.company_info_display.companyDetails": "Company Details", "admin.billing.company_info_display.detailsProvided": "Your company name and address", @@ -2751,6 +2753,21 @@ "admin.site.emoji": "Emoji", "admin.site.fileSharingDownloads": "File Sharing and Downloads", "admin.site.localization": "Localization", + "admin.site.localization.auto_translation.off": "Off", + "admin.site.localization.auto_translation.on": "On", + "admin.site.localization.autoTranslationInfo": "Auto-translation must also be enabled in each channel where it's needed.", + "admin.site.localization.autoTranslationInfoSecondary": "When multiple languages are detected, users will be prompted to enable auto-translation. [Learn more](https://docs.mattermost.com/).", + "admin.site.localization.autoTranslationProviderDescription": "NOTE: If using external translation services (e.g., cloud based),{br}message data may be processed outside of your environment.", + "admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyDescription": "If your LibreTranslate server requires an API key, enter it here. Otherwise, leave this field blank. View LibreTranslate docs for API Key management.", + "admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyExample": "Enter LibreTranslate API Key", + "admin.site.localization.autoTranslationProviderLibreTranslateAPIKeyTitle": "LibreTranslate API Key:", + "admin.site.localization.autoTranslationProviderLibreTranslateURLExample": "e.g.: \"https://libretranslate.yourdomain.com\"", + "admin.site.localization.autoTranslationProviderLibreTranslateURLTitle": "LibreTranslate API Endpoint:", + "admin.site.localization.autoTranslationProviderTitle": "Translation Service:", + "admin.site.localization.enableAutoTranslationDescription": "Configure auto-translation for channels and direct messages", + "admin.site.localization.enableAutoTranslationTitle": "Auto-translation", + "admin.site.localization.languages.description": "Choose which languages should be the defaults", + "admin.site.localization.languages.title": "Languages", "admin.site.move_thread": "Move thread", "admin.site.notices": "Notices", "admin.site.posts": "Posts", diff --git a/webapp/platform/types/src/config.ts b/webapp/platform/types/src/config.ts index 7f27320f515..0e7e5aa8162 100644 --- a/webapp/platform/types/src/config.ts +++ b/webapp/platform/types/src/config.ts @@ -739,6 +739,20 @@ export type LocalizationSettings = { EnableExperimentalLocales: boolean; }; +export type AutoTranslationSettings = { + Enable: boolean; + Provider: '' | 'libretranslate'; + LibreTranslate: { + URL: string; + APIKey: string; + }; + TimeoutMs: { + NewPost: number; + Fetch: number; + Notification: number; + }; +}; + export type SamlSettings = { Enable: boolean; EnableSyncWithLdap: boolean; @@ -1052,6 +1066,7 @@ export type AdminConfig = { ConnectedWorkspacesSettings: ConnectedWorkspacesSettings; AccessControlSettings: AccessControlSettings; ContentFlaggingSettings: ContentFlaggingSettings; + AutoTranslationSettings: AutoTranslationSettings; }; export type ReplicaLagSetting = {