[MM-67253] Add support for DMs and GMs for autotranslations (#9505) (#9551)

* [MM-67253] Add support for DMs and GMs for autotranslations

* Address copilot comments

(cherry picked from commit 1db5cd456c)

Co-authored-by: Daniel Espino García <larkox@gmail.com>
This commit is contained in:
Mattermost Build 2026-02-25 20:35:44 +01:00 committed by GitHub
parent f9d1bca05c
commit c6caf7571f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 469 additions and 27 deletions

View file

@ -159,4 +159,8 @@ export default {
EDIT_BOOKMARK_PRIVATE_CHANNEL: 'edit_bookmark_private_channel',
DELETE_BOOKMARK_PUBLIC_CHANNEL: 'delete_bookmark_public_channel',
DELETE_BOOKMARK_PRIVATE_CHANNEL: 'delete_bookmark_private_channel',
// Autotranslations
MANAGE_PUBLIC_CHANNEL_AUTO_TRANSLATION: 'manage_public_channel_auto_translation',
MANAGE_PRIVATE_CHANNEL_AUTO_TRANSLATION: 'manage_private_channel_auto_translation',
};

View file

@ -70,7 +70,7 @@ import {prepareChannels,
observeIsChannelAutotranslated,
} from './channel';
import {queryRoles} from './role';
import {getCurrentChannelId, observeConfigBooleanValue, observeCurrentChannelId, observeCurrentUserId} from './system';
import {getCurrentChannelId, observeCurrentChannelId, observeCurrentUserId} from './system';
import {observeTeammateNameDisplay} from './user';
import type ChannelModel from '@typings/database/models/servers/channel';
@ -79,7 +79,15 @@ import type ChannelInfoModel from '@typings/database/models/servers/channel_info
import type ChannelMembershipModel from '@typings/database/models/servers/channel_membership';
jest.mock('./role');
jest.mock('./system');
jest.mock('./system', () => {
const actual = jest.requireActual<typeof import('./system')>('./system');
return {
...actual,
getCurrentChannelId: jest.fn(),
observeCurrentChannelId: jest.fn(),
observeCurrentUserId: jest.fn(),
};
});
jest.mock('./user');
jest.mock('@utils/role');
@ -1669,7 +1677,14 @@ describe('queryMyChannelsByChannelIds and queryMyChannelsWithAutotranslation', (
const channelId = 'ch_observe';
it('should emit true when EnableAutoTranslation is true and channel has autotranslation', async () => {
jest.mocked(observeConfigBooleanValue).mockReturnValue(of$(true));
await operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
});
const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: true});
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
@ -1681,7 +1696,14 @@ describe('queryMyChannelsByChannelIds and queryMyChannelsWithAutotranslation', (
});
it('should emit false when EnableAutoTranslation is false', async () => {
jest.mocked(observeConfigBooleanValue).mockReturnValue(of$(false));
await operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'false'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
});
const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: true});
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
@ -1693,7 +1715,14 @@ describe('queryMyChannelsByChannelIds and queryMyChannelsWithAutotranslation', (
});
it('should emit false when channel has autotranslation false', async () => {
jest.mocked(observeConfigBooleanValue).mockReturnValue(of$(true));
await operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
});
const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: false});
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
@ -1705,20 +1734,80 @@ describe('queryMyChannelsByChannelIds and queryMyChannelsWithAutotranslation', (
});
it('should emit false when channel is not found', async () => {
jest.mocked(observeConfigBooleanValue).mockReturnValue(of$(true));
await operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
});
const subscriptionNext = jest.fn();
const result = observeChannelAutotranslation(database, 'nonexistent');
result.subscribe({next: subscriptionNext});
expect(subscriptionNext).toHaveBeenCalledWith(false);
});
it('should emit false when channel is DM and RestrictDMAndGMAutotranslation is true', async () => {
await operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'true'},
],
configsToDelete: [],
prepareRecordsOnly: false,
});
const channel = TestHelper.fakeChannel({
id: channelId,
type: General.DM_CHANNEL,
autotranslation: true,
});
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
const subscriptionNext = jest.fn();
const result = observeChannelAutotranslation(database, channelId);
result.subscribe({next: subscriptionNext});
expect(subscriptionNext).toHaveBeenCalledWith(false);
});
it('should emit true when channel is DM and RestrictDMAndGMAutotranslation is false', async () => {
await operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
});
const channel = TestHelper.fakeChannel({
id: channelId,
type: General.DM_CHANNEL,
autotranslation: true,
});
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
const subscriptionNext = jest.fn();
const result = observeChannelAutotranslation(database, channelId);
result.subscribe({next: subscriptionNext});
expect(subscriptionNext).toHaveBeenCalledWith(true);
});
});
describe('observeIsChannelAutotranslated', () => {
const channelId = 'ch_is_autotranslated';
it('should emit true when config and channel has autotranslation true and myChannel has autotranslation disabled false', async () => {
jest.mocked(observeConfigBooleanValue).mockReturnValue(of$(true));
await operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
});
const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: true});
const myChannel = TestHelper.fakeChannelMember({id: channelId, channel_id: channelId, autotranslation_disabled: false});
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
@ -1732,7 +1821,14 @@ describe('queryMyChannelsByChannelIds and queryMyChannelsWithAutotranslation', (
});
it('should emit false when channel autotranslation is false', async () => {
jest.mocked(observeConfigBooleanValue).mockReturnValue(of$(true));
await operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
});
const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: false});
const myChannel = TestHelper.fakeChannelMember({id: channelId, channel_id: channelId, autotranslation_disabled: false});
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
@ -1746,7 +1842,14 @@ describe('queryMyChannelsByChannelIds and queryMyChannelsWithAutotranslation', (
});
it('should emit false when myChannel autotranslation disabled is true', async () => {
jest.mocked(observeConfigBooleanValue).mockReturnValue(of$(true));
await operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
});
const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: true});
const myChannel = TestHelper.fakeChannelMember({id: channelId, channel_id: channelId, autotranslation_disabled: true});
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
@ -1758,5 +1861,55 @@ describe('queryMyChannelsByChannelIds and queryMyChannelsWithAutotranslation', (
expect(subscriptionNext).toHaveBeenCalledWith(false);
});
it('should emit false when channel is DM and RestrictDMAndGMAutotranslation is true', async () => {
await operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'true'},
],
configsToDelete: [],
prepareRecordsOnly: false,
});
const channel = TestHelper.fakeChannel({
id: channelId,
type: General.DM_CHANNEL,
autotranslation: true,
});
const myChannel = TestHelper.fakeChannelMember({id: channelId, channel_id: channelId, autotranslation_disabled: false});
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
await operator.handleMyChannel({channels: [channel], myChannels: [myChannel], prepareRecordsOnly: false});
const subscriptionNext = jest.fn();
const result = observeIsChannelAutotranslated(database, channelId);
result.subscribe({next: subscriptionNext});
expect(subscriptionNext).toHaveBeenCalledWith(false);
});
it('should emit true when channel is DM and RestrictDMAndGMAutotranslation is false', async () => {
await operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
});
const channel = TestHelper.fakeChannel({
id: channelId,
type: General.DM_CHANNEL,
autotranslation: true,
});
const myChannel = TestHelper.fakeChannelMember({id: channelId, channel_id: channelId, autotranslation_disabled: false});
await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
await operator.handleMyChannel({channels: [channel], myChannels: [myChannel], prepareRecordsOnly: false});
const subscriptionNext = jest.fn();
const result = observeIsChannelAutotranslated(database, channelId);
result.subscribe({next: subscriptionNext});
expect(subscriptionNext).toHaveBeenCalledWith(true);
});
});
});

View file

@ -11,7 +11,7 @@ import {General, Permissions} from '@constants';
import {MM_TABLES} from '@constants/database';
import {sanitizeLikeString} from '@helpers/database';
import EphemeralStore from '@store/ephemeral_store';
import {isDefaultChannel} from '@utils/channel';
import {isDefaultChannel, isDMorGM} from '@utils/channel';
import {hasPermission} from '@utils/role';
import {isSystemAdmin} from '@utils/user';
@ -287,9 +287,24 @@ export const observeMyChannelRoles = (database: Database, channelId: string) =>
export const observeChannelAutotranslation = (database: Database, channelId: string) => {
const enableAutoTranslation = observeConfigBooleanValue(database, 'EnableAutoTranslation');
const restrictDMAndGMAutotranslation = observeConfigBooleanValue(database, 'RestrictDMAndGMAutotranslation');
const channel = observeChannel(database, channelId);
return combineLatest([enableAutoTranslation, channel]).pipe(
switchMap(([et, c]) => of$(Boolean(et && c?.autotranslation))),
return combineLatest([enableAutoTranslation, restrictDMAndGMAutotranslation, channel]).pipe(
map$(([et, r, c]) => {
if (!et) {
return false;
}
if (!c?.autotranslation) {
return false;
}
if (isDMorGM(c) && r) {
return false;
}
return true;
}),
distinctUntilChanged(),
);
};
@ -798,7 +813,26 @@ export const observeIsReadOnlyChannel = (database: Database, channelId: string)
export const observeIsChannelAutotranslated = (database: Database, channelId: string) => {
const enableAutoTranslation = observeConfigBooleanValue(database, 'EnableAutoTranslation');
const restrictDMAndGMAutotranslation = observeConfigBooleanValue(database, 'RestrictDMAndGMAutotranslation');
const channel = observeChannel(database, channelId);
const myChannel = observeMyChannel(database, channelId);
return combineLatest([enableAutoTranslation, channel, myChannel]).pipe(map(([et, c, mc]) => Boolean(et && c?.autotranslation && !mc?.autotranslationDisabled)));
return combineLatest([enableAutoTranslation, restrictDMAndGMAutotranslation, channel, myChannel]).pipe(map(([et, r, c, mc]) => {
if (!et) {
return false;
}
if (!c?.autotranslation) {
return false;
}
if (mc?.autotranslationDisabled) {
return false;
}
if (isDMorGM(c) && r) {
return false;
}
return true;
}));
};

View file

@ -14,6 +14,7 @@ import {
observePermissionForPost,
observeCanManageChannelMembers,
observeCanManageChannelSettings,
observeCanManageChannelAutotranslations,
} from './role';
import type ServerDataOperator from '@database/operator/server_data_operator';
@ -427,4 +428,221 @@ describe('Role Queries', () => {
});
});
});
describe('observeCanManageChannelAutotranslations', () => {
it('should emit false when EnableAutoTranslation is false', async () => {
const mockUser = TestHelper.fakeUserModel({id: 'user1', roles: 'system_user'});
await Promise.all([
operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'false'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
}),
operator.handleChannel({
channels: [TestHelper.fakeChannel({
id: 'channel1',
type: General.OPEN_CHANNEL,
delete_at: 0,
})],
prepareRecordsOnly: false,
}),
]);
const subscriptionNext = jest.fn();
const result = observeCanManageChannelAutotranslations(database, 'channel1', mockUser);
result.subscribe({next: subscriptionNext});
expect(subscriptionNext).toHaveBeenCalledWith(false);
});
it('should emit false when channel is not found', async () => {
const mockUser = TestHelper.fakeUserModel({id: 'user1', roles: 'system_user'});
await operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
});
const subscriptionNext = jest.fn();
const result = observeCanManageChannelAutotranslations(database, 'nonexistent', mockUser);
result.subscribe({next: subscriptionNext});
expect(subscriptionNext).toHaveBeenCalledWith(false);
});
it('should emit false when channel is deleted', async () => {
const mockUser = TestHelper.fakeUserModel({id: 'user1', roles: 'system_user'});
await Promise.all([
operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
}),
operator.handleChannel({
channels: [TestHelper.fakeChannel({
id: 'channel1',
type: General.OPEN_CHANNEL,
delete_at: 123,
})],
prepareRecordsOnly: false,
}),
]);
const subscriptionNext = jest.fn();
const result = observeCanManageChannelAutotranslations(database, 'channel1', mockUser);
result.subscribe({next: subscriptionNext});
expect(subscriptionNext).toHaveBeenCalledWith(false);
});
it('should emit false when channel is DM and RestrictDMAndGMAutotranslation is true', async () => {
const mockUser = TestHelper.fakeUserModel({id: 'user1', roles: 'system_user'});
await Promise.all([
operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'true'},
],
configsToDelete: [],
prepareRecordsOnly: false,
}),
operator.handleChannel({
channels: [TestHelper.fakeChannel({
id: 'channel1',
type: General.DM_CHANNEL,
delete_at: 0,
})],
prepareRecordsOnly: false,
}),
]);
const subscriptionNext = jest.fn();
const result = observeCanManageChannelAutotranslations(database, 'channel1', mockUser);
result.subscribe({next: subscriptionNext});
expect(subscriptionNext).toHaveBeenCalledWith(false);
});
it('should emit true when channel is DM and RestrictDMAndGMAutotranslation is false', async () => {
const mockUser = TestHelper.fakeUserModel({id: 'user1', roles: 'system_user'});
await Promise.all([
operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
}),
operator.handleChannel({
channels: [TestHelper.fakeChannel({
id: 'channel1',
type: General.DM_CHANNEL,
delete_at: 0,
})],
prepareRecordsOnly: false,
}),
]);
const subscriptionNext = jest.fn();
const result = observeCanManageChannelAutotranslations(database, 'channel1', mockUser);
result.subscribe({next: subscriptionNext});
expect(subscriptionNext).toHaveBeenCalledWith(true);
});
it('should emit true for open channel when user has MANAGE_PUBLIC_CHANNEL_AUTO_TRANSLATION', async () => {
const mockUser = TestHelper.fakeUserModel({
id: 'user1',
roles: 'channel_admin',
});
await Promise.all([
operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
}),
operator.handleChannel({
channels: [TestHelper.fakeChannel({
id: 'channel1',
type: General.OPEN_CHANNEL,
delete_at: 0,
})],
prepareRecordsOnly: false,
}),
operator.handleRole({
roles: [{
id: 'channel_admin',
name: 'channel_admin',
permissions: [Permissions.MANAGE_PUBLIC_CHANNEL_AUTO_TRANSLATION],
}],
prepareRecordsOnly: false,
}),
]);
const subscriptionNext = jest.fn();
const result = observeCanManageChannelAutotranslations(database, 'channel1', mockUser);
result.subscribe({next: subscriptionNext});
expect(subscriptionNext).toHaveBeenCalledWith(true);
});
it('should emit true for private channel when user has MANAGE_PRIVATE_CHANNEL_AUTO_TRANSLATION', async () => {
const mockUser = TestHelper.fakeUserModel({
id: 'user1',
roles: 'channel_admin',
});
await Promise.all([
operator.handleConfigs({
configs: [
{id: 'EnableAutoTranslation', value: 'true'},
{id: 'RestrictDMAndGMAutotranslation', value: 'false'},
],
configsToDelete: [],
prepareRecordsOnly: false,
}),
operator.handleChannel({
channels: [TestHelper.fakeChannel({
id: 'channel1',
type: General.PRIVATE_CHANNEL,
delete_at: 0,
})],
prepareRecordsOnly: false,
}),
operator.handleRole({
roles: [{
id: 'channel_admin',
name: 'channel_admin',
permissions: [Permissions.MANAGE_PRIVATE_CHANNEL_AUTO_TRANSLATION],
}],
prepareRecordsOnly: false,
}),
]);
const subscriptionNext = jest.fn();
const result = observeCanManageChannelAutotranslations(database, 'channel1', mockUser);
result.subscribe({next: subscriptionNext});
expect(subscriptionNext).toHaveBeenCalledWith(true);
});
});
});

View file

@ -10,6 +10,7 @@ import {isDefaultChannel, isDMorGM} from '@utils/channel';
import {hasPermission} from '@utils/role';
import {observeChannel, observeMyChannelRoles} from './channel';
import {observeConfigBooleanValue} from './system';
import {observeMyTeam, observeMyTeamRoles} from './team';
import type ChannelModel from '@typings/database/models/servers/channel';
@ -115,3 +116,28 @@ export function observeCanManageChannelSettings(database: Database, channelId: s
distinctUntilChanged(),
);
}
export function observeCanManageChannelAutotranslations(database: Database, channelId: string, user: UserModel) {
const featureEnabled = observeConfigBooleanValue(database, 'EnableAutoTranslation');
const channel = observeChannel(database, channelId);
const restrictDMAndGMAutotranslation = observeConfigBooleanValue(database, 'RestrictDMAndGMAutotranslation');
return combineLatest([featureEnabled, channel, restrictDMAndGMAutotranslation]).pipe(
switchMap(([enabled, c, isDmGmRestricted]) => {
if (!enabled) {
return of$(false);
}
if (!c || c.deleteAt !== 0) {
return of$(false);
}
if (isDMorGM(c)) {
return of$(!isDmGmRestricted);
}
const permission = c.type === General.OPEN_CHANNEL ? Permissions.MANAGE_PUBLIC_CHANNEL_AUTO_TRANSLATION : Permissions.MANAGE_PRIVATE_CHANNEL_AUTO_TRANSLATION;
return observePermissionForChannel(database, c, user, permission, false);
}),
distinctUntilChanged(),
);
}

View file

@ -12,7 +12,7 @@ import {withServerUrl} from '@context/server';
import {observeIsPlaybooksEnabled} from '@playbooks/database/queries/version';
import {observeChannelAutotranslation, observeCurrentChannel} from '@queries/servers/channel';
import {observeCanAddBookmarks} from '@queries/servers/channel_bookmark';
import {observeCanManageChannelMembers, observeCanManageChannelSettings, observePermissionForChannel, observePermissionForTeam} from '@queries/servers/role';
import {observeCanManageChannelAutotranslations, observeCanManageChannelMembers, observeCanManageChannelSettings, observePermissionForChannel, observePermissionForTeam} from '@queries/servers/role';
import {
observeConfigBooleanValue,
observeConfigValue,
@ -137,7 +137,10 @@ const observeHasChannelSettingsActions = (
switchMap((version) => of$(isMinimumServerVersion(version || '', 9, 1))),
);
const isChannelAutotranslateEnabled = observeConfigBooleanValue(database, 'EnableAutoTranslation');
const canManageChannelAutotranslations = combineLatest([channelId, currentUser]).pipe(
switchMap(([cId, u]) => (u ? observeCanManageChannelAutotranslations(database, cId, u) : of$(false))),
distinctUntilChanged(),
);
const team = observeCurrentTeam(database);
const isArchived = channel.pipe(switchMap((c) => of$((c?.deleteAt || 0) > 0)));
@ -206,16 +209,16 @@ const observeHasChannelSettingsActions = (
canUnarchive,
canEnableDisableCalls,
convertGMOptionAvailable,
isChannelAutotranslateEnabled,
canManageChannelAutotranslations,
]).pipe(
switchMap(([manageSettings, convert, archive, unarchive, enableCalls, convertGM, autotranslateEnabled]) => {
switchMap(([manageSettings, convert, archive, unarchive, enableCalls, convertGM, manageAutotranslations]) => {
return of$(
manageSettings || // Channel info or Channel autotranslations
convert || // Convert to private
archive || unarchive || // Archive channel
enableCalls || // Enable/Disable calls
convertGM || // Convert GM to channel
(manageSettings && autotranslateEnabled), // Channel autotranslations
manageAutotranslations, // Channel autotranslations
);
}),
);

View file

@ -33,7 +33,7 @@ type Props = {
convertGMOptionAvailable: boolean;
displayName: string;
isCallsEnabledInChannel: boolean;
isChannelAutotranslateEnabled: boolean;
canManageAutotranslations: boolean;
type?: ChannelType;
}
@ -66,7 +66,7 @@ const ChannelSettings = ({
convertGMOptionAvailable,
displayName,
isCallsEnabledInChannel,
isChannelAutotranslateEnabled,
canManageAutotranslations,
type,
}: Props) => {
const theme = useTheme();
@ -116,7 +116,7 @@ const ChannelSettings = ({
{convertGMOptionAvailable &&
<ConvertToChannelLabel channelId={channelId}/>
}
{canManageSettings && isChannelAutotranslateEnabled &&
{canManageAutotranslations &&
<ChannelAutotranslation channelId={channelId}/>
}
{(canArchive || canUnarchive) &&

View file

@ -10,9 +10,8 @@ import {observeCallsConfig} from '@calls/state';
import {General, Permissions} from '@constants';
import {withServerUrl} from '@context/server';
import {observeChannel} from '@queries/servers/channel';
import {observePermissionForChannel, observePermissionForTeam, observeCanManageChannelSettings} from '@queries/servers/role';
import {observePermissionForChannel, observePermissionForTeam, observeCanManageChannelSettings, observeCanManageChannelAutotranslations} from '@queries/servers/role';
import {
observeConfigBooleanValue,
observeConfigValue,
observeCurrentTeamId,
} from '@queries/servers/system';
@ -175,7 +174,9 @@ const enhanced = withObservables(['channelId'], ({channelId, serverUrl, database
);
// Channel autotranslation observable
const isChannelAutotranslateEnabled = observeConfigBooleanValue(database, 'EnableAutoTranslation');
const canManageAutotranslations = currentUser.pipe(
switchMap((u) => (u ? observeCanManageChannelAutotranslations(database, channelId, u) : of$(false))),
);
return {
canArchive,
@ -186,7 +187,7 @@ const enhanced = withObservables(['channelId'], ({channelId, serverUrl, database
convertGMOptionAvailable,
displayName: channel.pipe(switchMap((c) => of$(c?.displayName || ''))),
isCallsEnabledInChannel,
isChannelAutotranslateEnabled,
canManageAutotranslations,
isGuestUser,
type,
};

View file

@ -13,7 +13,6 @@ interface ClientConfig {
AndroidMinVersion: string;
AppDownloadLink: string;
AsymmetricSigningPublicKey: string;
AutoTranslationLanguages: string;
AvailableLocales: string;
BannerColor: string;
BannerText: string;
@ -48,7 +47,6 @@ interface ClientConfig {
EmailLoginButtonTextColor: string;
EmailNotificationContentsType: string;
EnableBanner: string;
EnableAutoTranslation: string;
EnableBotAccountCreation: string;
EnableBurnOnRead: string;
EnableChannelViewedMessages: string;
@ -218,6 +216,11 @@ interface ClientConfig {
WebsocketSecurePort: string;
WebsocketURL: string;
BurnOnReadDurationSeconds: string;
// Autotranslations
AutoTranslationLanguages: string;
EnableAutoTranslation: string;
RestrictDMAndGMAutotranslation: string;
}
type SecurityClientConfig = Pick<ClientConfig, 'MobileEnableBiometrics' | 'MobileJailbreakProtection' | 'MobilePreventScreenCapture' | 'SiteName'>