fix(self-hosting): use instance contacts and live product name (#3333)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-09 01:39:35 +02:00
1 parent 454fefdb92
commit a5c1362f4a
308 files changed
+16533 -13512

No files matched your search

@@ -33,7 +33,7 @@ function snapshot(branding: Partial<Branding>, origin = 'https://chat.example.or
...branding,
},
setup: {configured: true, admin_url: null},
legal: {terms_url: null, privacy_url: null},
legal: {terms_url: null, privacy_url: null, guidelines_url: null},
registration: {collect_date_of_birth: false},
},
};
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {GenericErrorModal} from '@app/features/app/components/alerts/GenericErrorModal';
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import {modal} from '@app/features/ui/commands/ModalCommands';
import {msg} from '@lingui/core/macro';
@@ -17,12 +18,22 @@ const YOUR_ACTION_COULD_NOT_BE_COMPLETED_BECAUSE_IT_DESCRIPTOR = msg({
comment:
'Modal body shown when the safety system blocks the requested action. Keep plain and calm; offer support path.',
});
const YOUR_ACTION_COULD_NOT_BE_COMPLETED_SELF_HOSTED_DESCRIPTOR = msg({
message:
'Your action could not be completed because it was flagged by our safety systems. If you believe this is a mistake, please contact the administrators of this instance.',
comment:
'Modal body shown on a self-hosted instance when the safety system blocks the requested action. Keep plain and calm.',
});
const ContentBlockedModal = observer(() => {
const {i18n} = useLingui();
return (
<GenericErrorModal
title={i18n._(CONTENT_BLOCKED_DESCRIPTOR)}
message={i18n._(YOUR_ACTION_COULD_NOT_BE_COMPLETED_BECAUSE_IT_DESCRIPTOR)}
message={i18n._(
RuntimeConfig.isSelfHosted()
? YOUR_ACTION_COULD_NOT_BE_COMPLETED_SELF_HOSTED_DESCRIPTOR
: YOUR_ACTION_COULD_NOT_BE_COMPLETED_BECAUSE_IT_DESCRIPTOR,
)}
data-flx="auth.content-blocked-handler.content-blocked-modal.confirm-modal"
/>
);
@@ -294,10 +294,18 @@ const SudoVerificationModal: React.FC = observer(() => {
className={styles.unavailableBody}
data-flx="auth.sudo-verification-modal.unavailable-body"
>
<Trans>
Your account requires multi-factor authentication, but no supported methods are set up on this
device. Sign in on a device with your authenticator app or security key, or contact support.
</Trans>
{RuntimeConfig.isSelfHosted() ? (
<Trans>
Your account requires multi-factor authentication, but no supported methods are set up on this
device. Sign in on a device with your authenticator app or security key, or contact the
administrators of this instance.
</Trans>
) : (
<Trans>
Your account requires multi-factor authentication, but no supported methods are set up on this
device. Sign in on a device with your authenticator app or security key, or contact support.
</Trans>
)}
</Modal.Description>
</div>
) : (
@@ -2,7 +2,7 @@
import {Routes} from '@app/app/Routes';
import * as Modal from '@app/features/app/components/dialogs/Modal';
import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
import {CLOSE_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {acceptTerms} from '@app/features/terms/commands/TermsAcceptanceCommands';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
@@ -72,7 +72,7 @@ export const TermsAcceptanceModal = observer(() => {
const kind = getUpdateKind(user.termsAgreedAt, user.privacyAgreedAt);
const termsUrl = Routes.terms();
const privacyUrl = Routes.privacy();
const productName = PRODUCT_NAME;
const productName = RuntimeConfig.productName;
return (
<Modal.Root
size="small"
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
import * as AuthenticationCommands from '@app/features/auth/commands/AuthenticationCommands';
import styles from '@app/features/auth/components/pages/LoginPage.module.css';
import {useAuthSingleUseRequest} from '@app/features/auth/flow/AuthSingleUseRequest';
@@ -267,7 +267,7 @@ const SsoCallbackPage = observer(function SsoCallbackPage() {
className={styles.ssoRetryButton}
data-flx="auth.sso-callback-page.sso-open-app-button"
>
{i18n._(OPEN_PRODUCT_DESCRIPTOR, {productName: PRODUCT_NAME})}
{i18n._(OPEN_PRODUCT_DESCRIPTOR, {productName: RuntimeConfig.productName})}
</a>
</flx-auth-sso-callback-page-actions>
</flx-auth-sso-callback-page>
@@ -1,10 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {Routes} from '@app/app/Routes';
import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import {detectDomainMigrationInstallKind} from '@app/features/app/domain_migration/DomainMigrationBrowser';
import type {RuntimeConfigSnapshot} from '@app/features/app/state/InstanceSnapshotStore';
import {describeAPIEndpoint, type InstanceSsoConfig} from '@app/features/app/state/RuntimeConfig';
import RuntimeConfig, {describeAPIEndpoint, type InstanceSsoConfig} from '@app/features/app/state/RuntimeConfig';
import {
FORGOT_YOUR_PASSWORD_DESCRIPTOR,
SIGN_IN_WITH_BROWSER_DESCRIPTOR,
@@ -860,10 +859,10 @@ export const AuthLoginLayout = observer(function AuthLoginLayout({
type="button"
data-flx="auth.flow.auth-login-layout.button.old-app-sign-in"
>
{i18n._(SIGN_IN_WITH_OLD_APP_DESCRIPTOR, {productName: PRODUCT_NAME})}
{i18n._(SIGN_IN_WITH_OLD_APP_DESCRIPTOR, {productName: RuntimeConfig.productName})}
</Button>
<div className={styles.ssoSubtitle} data-flx="auth.flow.auth-login-layout.old-app-subtitle">
{i18n._(OLD_APP_SIGN_IN_HINT_DESCRIPTOR, {productName: PRODUCT_NAME})}
{i18n._(OLD_APP_SIGN_IN_HINT_DESCRIPTOR, {productName: RuntimeConfig.productName})}
</div>
</div>
);
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
import styles from '@app/features/auth/flow/AuthPageStyles.module.css';
import type {LoadableErrorProps} from '@app/features/platform/components/loadable/LoadableComponent';
import {Button} from '@app/features/ui/button/Button';
@@ -15,7 +15,7 @@ export function AuthRouteLoadError({retry}: LoadableErrorProps): React.JSX.Eleme
<Trans>Could not load this page</Trans>
</div>
<div className={styles.errorText} data-flx="auth.flow.auth-route-load-error.text">
<Trans>Try again, or refresh {ph({productName: PRODUCT_NAME})} if the problem continues.</Trans>
<Trans>Try again, or refresh {ph({productName: RuntimeConfig.productName})} if the problem continues.</Trans>
</div>
<div className={styles.disabledActions} data-flx="auth.flow.auth-route-load-error.actions">
<Button onClick={retry} fitContainer data-flx="auth.flow.auth-route-load-error.retry">
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import * as Modal from '@app/features/app/components/dialogs/Modal';
import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
import * as AuthenticationCommands from '@app/features/auth/commands/AuthenticationCommands';
import styles from '@app/features/auth/flow/BrowserLoginHandoffModal.module.css';
@@ -14,7 +13,7 @@ import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import {modal} from '@app/features/ui/commands/ModalCommands';
import {openExternalUrl} from '@app/features/ui/utils/NativeUtils';
import * as FormUtils from '@app/lib/forms';
import {msg} from '@lingui/core/macro';
import {msg, ph} from '@lingui/core/macro';
import {Trans, useLingui} from '@lingui/react/macro';
import {ArrowSquareOutIcon} from '@phosphor-icons/react';
import {observer} from 'mobx-react-lite';
@@ -123,7 +122,7 @@ const BrowserLoginHandoffModal = observer(({onSuccess, prefillLogin, variant}: B
<Modal.Header
title={
isOldAppVariant
? i18n._(SIGN_IN_WITH_OLD_APP_DESCRIPTOR, {productName: PRODUCT_NAME})
? i18n._(SIGN_IN_WITH_OLD_APP_DESCRIPTOR, {productName: RuntimeConfig.productName})
: i18n._(ADD_ACCOUNT_DESCRIPTOR)
}
data-flx="auth.flow.browser-login-handoff-modal.modal-header"
@@ -132,7 +131,10 @@ const BrowserLoginHandoffModal = observer(({onSuccess, prefillLogin, variant}: B
<Modal.ContentLayout className={styles.content} data-flx="auth.flow.browser-login-handoff-modal.content">
<Modal.Description data-flx="auth.flow.browser-login-handoff-modal.description">
{isOldAppVariant ? (
<Trans>Open your old {PRODUCT_NAME} app and choose Link a new device, then enter the code below.</Trans>
<Trans>
Open your old {ph({PRODUCT_NAME: RuntimeConfig.productName})} app and choose Link a new device, then
enter the code below.
</Trans>
) : (
<Trans>Open your browser, sign in, then enter the code below to link your account.</Trans>
)}
@@ -145,7 +147,9 @@ const BrowserLoginHandoffModal = observer(({onSuccess, prefillLogin, variant}: B
onRetry={generateCode}
description={
isOldAppVariant ? (
<Trans>Enter this code in your old {PRODUCT_NAME} app to complete sign-in.</Trans>
<Trans>
Enter this code in your old {ph({PRODUCT_NAME: RuntimeConfig.productName})} app to complete sign-in.
</Trans>
) : undefined
}
data-flx="auth.flow.browser-login-handoff-modal.handoff-code-display"
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {Routes} from '@app/app/Routes';
import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
import styles from '@app/features/auth/flow/DesktopDeepLinkPrompt.module.css';
import {Platform} from '@app/features/platform/types/Platform';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
@@ -64,7 +64,7 @@ export const DesktopDeepLinkPrompt: React.FC<DesktopDeepLinkPromptProps> = ({cod
<div className={styles.banner} data-flx="auth.flow.desktop-deep-link-prompt.banner">
<div className={styles.copy} data-flx="auth.flow.desktop-deep-link-prompt.copy">
<p className={styles.title} data-flx="auth.flow.desktop-deep-link-prompt.title">
{i18n._(OPEN_IN_DESKTOP_PRODUCT_DESCRIPTOR, {productName: PRODUCT_NAME})}
{i18n._(OPEN_IN_DESKTOP_PRODUCT_DESCRIPTOR, {productName: RuntimeConfig.productName})}
</p>
{error ? (
<p className={styles.notInstalled} data-flx="auth.flow.desktop-deep-link-prompt.not-installed">
@@ -89,7 +89,7 @@ export const DesktopDeepLinkPrompt: React.FC<DesktopDeepLinkPromptProps> = ({cod
data-flx="auth.flow.desktop-deep-link-prompt.arrow-square-out-icon"
/>
<span data-flx="auth.flow.desktop-deep-link-prompt.span">
{i18n._(OPEN_PRODUCT_DESCRIPTOR, {productName: PRODUCT_NAME})}
{i18n._(OPEN_PRODUCT_DESCRIPTOR, {productName: RuntimeConfig.productName})}
</span>
</Button>
</div>
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import styles from '@app/features/app/components/ErrorFallback.module.css';
import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import {Endpoints} from '@app/features/app/constants/Endpoints';
import type {DomainMigrationSide} from '@app/features/app/domain_migration/DomainMigrationCore';
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
import {
readPasskeyBridgePageLoginReturnPath,
readPasskeyBridgeReturn,
@@ -183,7 +183,7 @@ const LegacyPasskeyBridgePage: React.FC<PasskeyBridgePageProps> = ({side, hash,
<div className={styles.errorFallbackActions} data-flx="auth.passkey-bridge-page.actions">
{expired ? (
<Button onClick={handleBack} autoFocus data-flx="auth.passkey-bridge-page.button.back">
{i18n._(BACK_TO_PRODUCT_DESCRIPTOR, {productName: PRODUCT_NAME})}
{i18n._(BACK_TO_PRODUCT_DESCRIPTOR, {productName: RuntimeConfig.productName})}
</Button>
) : failed ? (
<>
@@ -258,7 +258,7 @@ const TargetPasskeyBridgePage: React.FC<{hash: string}> = ({hash}) => {
{i18n._(YOU_CAN_CLOSE_THIS_TAB_DESCRIPTOR)}
</h1>
<p className={styles.errorFallbackDescription} data-flx="auth.passkey-bridge-page.description">
{i18n._(GO_BACK_TO_CONTINUE_DESCRIPTOR, {productName: PRODUCT_NAME})}
{i18n._(GO_BACK_TO_CONTINUE_DESCRIPTOR, {productName: RuntimeConfig.productName})}
</p>
</div>
</main>
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {ConfirmModal} from '@app/features/app/components/dialogs/ConfirmModal';
import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import type {DomainMigrationMediaDeviceKind} from '@app/features/app/domain_migration/DomainMigrationCore';
import {remapMigratedDeviceIds} from '@app/features/app/domain_migration/DomainMigrationDeviceRemap';
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
import styles from '@app/features/auth/state/NewDeviceMonitoring.module.css';
import {
getNewDevicePromptCandidates,
@@ -18,7 +18,7 @@ import VoiceDevicePermissionState from '@app/features/voice/engine/VoiceDevicePe
import VoiceSettings from '@app/features/voice/state/VoiceSettings';
import type {VoiceDeviceState} from '@app/features/voice/utils/VoiceDeviceManager';
import type {I18n} from '@lingui/core';
import {msg} from '@lingui/core/macro';
import {msg, ph} from '@lingui/core/macro';
import {Trans} from '@lingui/react/macro';
import {makeAutoObservable, runInAction} from 'mobx';
import type React from 'react';
@@ -195,18 +195,18 @@ class NewDeviceMonitoring {
description={
deviceType === 'input' ? (
<Trans>
{PRODUCT_NAME} has found a new audio input device named{' '}
{ph({PRODUCT_NAME: RuntimeConfig.productName})} has found a new audio input device named{' '}
<strong data-flx="auth.new-device-monitoring.strong">{deviceName}</strong>. Do you want to switch to it?
</Trans>
) : deviceType === 'output' ? (
<Trans>
{PRODUCT_NAME} has found a new audio output device named{' '}
{ph({PRODUCT_NAME: RuntimeConfig.productName})} has found a new audio output device named{' '}
<strong data-flx="auth.new-device-monitoring.strong--2">{deviceName}</strong>. Do you want to switch to
it?
</Trans>
) : (
<Trans>
{PRODUCT_NAME} has found a new audio device named{' '}
{ph({PRODUCT_NAME: RuntimeConfig.productName})} has found a new audio device named{' '}
<strong data-flx="auth.new-device-monitoring.strong--3">{deviceName}</strong>. Do you want to switch to
it?
</Trans>
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
const TOTP_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
const getRandomBytes = (length = 20): Uint8Array => {
@@ -34,7 +34,7 @@ export function encodeTotpSecret(secret: string) {
return secret.replace(/[\s._-]+/g, '').toUpperCase();
}
export function encodeTotpSecretAsURL(accountName: string, secret: string, issuer = PRODUCT_NAME) {
export function encodeTotpSecretAsURL(accountName: string, secret: string, issuer = RuntimeConfig.productName) {
const url = new URL('otpauth://totp');
url.pathname = `/${encodeURIComponent(issuer)}:${encodeURIComponent(accountName)}`;
url.searchParams.set('secret', encodeTotpSecret(secret));