diff --git a/app/components/post_list/post/body/content/permalink_preview/index.test.tsx b/app/components/post_list/post/body/content/permalink_preview/index.test.tsx
index ec55c5681..1d1999feb 100644
--- a/app/components/post_list/post/body/content/permalink_preview/index.test.tsx
+++ b/app/components/post_list/post/body/content/permalink_preview/index.test.tsx
@@ -136,6 +136,7 @@ describe('PermalinkPreview Enhanced Component', () => {
expect(permalinkPreview.props.author).toBeDefined();
expect(permalinkPreview.props.currentUser).toBeDefined();
expect(permalinkPreview.props.isMilitaryTime).toBe(false);
+ expect(permalinkPreview.props.autotranslationsEnabled).toBe(false);
});
});
@@ -169,6 +170,7 @@ describe('PermalinkPreview Enhanced Component', () => {
expect(permalinkPreview.props.author).toBeUndefined();
expect(permalinkPreview.props.currentUser).toBeDefined();
expect(permalinkPreview.props.isMilitaryTime).toBe(false);
+ expect(permalinkPreview.props.autotranslationsEnabled).toBe(false);
});
});
@@ -209,6 +211,7 @@ describe('PermalinkPreview Enhanced Component', () => {
expect(permalinkPreview.props.teammateNameDisplay).toBe('username');
expect(permalinkPreview.props.currentUser).toBeDefined();
expect(permalinkPreview.props.isMilitaryTime).toBe(false);
+ expect(permalinkPreview.props.autotranslationsEnabled).toBe(false);
});
});
@@ -241,6 +244,7 @@ describe('PermalinkPreview Enhanced Component', () => {
expect(permalinkPreview.props.author).toBeUndefined();
expect(permalinkPreview.props.currentUser).toBeDefined();
expect(permalinkPreview.props.isMilitaryTime).toBe(false);
+ expect(permalinkPreview.props.autotranslationsEnabled).toBe(false);
});
});
@@ -281,6 +285,7 @@ describe('PermalinkPreview Enhanced Component', () => {
expect(permalinkPreview.props.teammateNameDisplay).toBe('full_name');
expect(permalinkPreview.props.currentUser).toBeDefined();
expect(permalinkPreview.props.isMilitaryTime).toBe(false);
+ expect(permalinkPreview.props.autotranslationsEnabled).toBe(false);
});
});
@@ -319,6 +324,58 @@ describe('PermalinkPreview Enhanced Component', () => {
await waitFor(() => {
const permalinkPreview = getByTestId('permalink-preview');
expect(permalinkPreview.props.isMilitaryTime).toBe(true);
+ expect(permalinkPreview.props.autotranslationsEnabled).toBe(false);
+ });
+ });
+
+ it('should pass autotranslationsEnabled true when channel has autotranslation enabled', async () => {
+ await operator.handleConfigs({
+ configs: [
+ {id: 'EnableAutoTranslation', value: 'true'},
+ {id: 'RestrictDMAndGMAutotranslation', value: 'false'},
+ ],
+ configsToDelete: [],
+ prepareRecordsOnly: false,
+ });
+
+ const channelId = 'channel-123';
+ const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: true});
+ const myChannel = TestHelper.fakeMyChannel({
+ id: channelId,
+ channel_id: channelId,
+ user_id: 'current-user',
+ autotranslation_disabled: false,
+ });
+ await operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
+ await operator.handleMyChannel({channels: [channel], myChannels: [myChannel], prepareRecordsOnly: false});
+
+ const embedData: PermalinkEmbedData = {
+ post_id: 'post-123',
+ post: TestHelper.fakePost({
+ id: 'post-123',
+ user_id: 'author-user',
+ message: 'Test message',
+ channel_id: channelId,
+ }),
+ team_name: 'test-team',
+ channel_display_name: 'Test Channel',
+ channel_type: 'O',
+ channel_id: channelId,
+ };
+
+ await createPostWithPermalinkEmbed(operator, database, embedData);
+
+ const {getByTestId} = renderWithEverything(
+ ,
+ {database, serverUrl},
+ );
+
+ await waitFor(() => {
+ const permalinkPreview = getByTestId('permalink-preview');
+ expect(permalinkPreview.props.autotranslationsEnabled).toBe(true);
});
});
});
diff --git a/app/components/post_list/post/body/content/permalink_preview/index.tsx b/app/components/post_list/post/body/content/permalink_preview/index.tsx
index e67c492f3..cbb74aeb6 100644
--- a/app/components/post_list/post/body/content/permalink_preview/index.tsx
+++ b/app/components/post_list/post/body/content/permalink_preview/index.tsx
@@ -6,6 +6,7 @@ import {of as of$} from 'rxjs';
import {map, switchMap, distinctUntilChanged} from 'rxjs/operators';
import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference';
+import {observeIsChannelAutotranslated} from '@queries/servers/channel';
import {observePost} from '@queries/servers/post';
import {queryDisplayNamePreferences} from '@queries/servers/preference';
import {observeUser, observeTeammateNameDisplay, observeCurrentUser} from '@queries/servers/user';
@@ -27,12 +28,17 @@ const enhance = withObservables(['embedData'], ({database, embedData}: WithDatab
const post = embedData?.post_id ? observePost(database, embedData.post_id) : of$(undefined);
const isOriginPostDeleted = post.pipe(
switchMap((p) => {
- const initialDeleted = Boolean(embedData?.post?.delete_at > 0 || embedData?.post?.state === 'DELETED');
+ const deleteAt = embedData?.post?.delete_at ?? 0;
+ const initialDeleted = Boolean(deleteAt > 0 || embedData?.post?.state === 'DELETED');
return of$(p ? p.deleteAt > 0 : initialDeleted);
}),
distinctUntilChanged(),
);
+ const channelId = embedData?.post?.channel_id;
+
+ const autotranslationsEnabled = channelId ? observeIsChannelAutotranslated(database, channelId) : of$(false);
+
return {
teammateNameDisplay,
currentUser,
@@ -40,6 +46,7 @@ const enhance = withObservables(['embedData'], ({database, embedData}: WithDatab
author,
post,
isOriginPostDeleted,
+ autotranslationsEnabled,
};
});
diff --git a/app/components/post_list/post/body/content/permalink_preview/permalink_preview.test.tsx b/app/components/post_list/post/body/content/permalink_preview/permalink_preview.test.tsx
index 67256df80..161bfdb0b 100644
--- a/app/components/post_list/post/body/content/permalink_preview/permalink_preview.test.tsx
+++ b/app/components/post_list/post/body/content/permalink_preview/permalink_preview.test.tsx
@@ -3,9 +3,11 @@
import {fireEvent} from '@testing-library/react-native';
import React from 'react';
+import {View} from 'react-native';
import {showPermalink} from '@actions/remote/permalink';
import Markdown from '@components/markdown';
+import TranslateIcon from '@components/post_list/post/header/translate_icon';
import {Screens} from '@constants';
import DatabaseManager from '@database/manager';
import {renderWithEverything} from '@test/intl-test-helper';
@@ -28,6 +30,14 @@ jest.mocked(Markdown).mockImplementation((props) =>
React.createElement('Text', {testID: 'markdown'}, props.value),
);
+jest.mock('@components/post_list/post/header/translate_icon', () => ({
+ __esModule: true,
+ default: jest.fn(),
+}));
+jest.mocked(TranslateIcon).mockImplementation(() =>
+ React.createElement(View, {testID: 'translate-icon'}, null),
+);
+
describe('components/post_list/post/body/content/permalink_preview/PermalinkPreview', () => {
let database: Database;
let operator: ServerDataOperator;
@@ -81,7 +91,7 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev
edit_at: 0,
metadata: {
embeds: [{
- type: 'opengraph' as PostEmbedType,
+ type: 'opengraph',
url: 'https://example.com',
data: {
title: 'Example Title',
@@ -118,6 +128,7 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev
isOriginPostDeleted: false,
parentLocation: Screens.CHANNEL,
parentPostId: 'parent-post-123',
+ autotranslationsEnabled: false,
};
it('should render permalink preview correctly', () => {
@@ -141,7 +152,7 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev
...baseProps,
embedData: {
...baseProps.embedData,
- post: undefined as unknown as Post,
+ post: undefined,
},
post: undefined,
};
@@ -214,7 +225,7 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev
message: 'Test message',
metadata: {
embeds: [{
- type: 'opengraph' as PostEmbedType,
+ type: 'opengraph',
url: 'https://example.com',
data: {
title: 'Example Title',
@@ -262,7 +273,7 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev
message: longMessage,
metadata: {
embeds: [{
- type: 'opengraph' as PostEmbedType,
+ type: 'opengraph',
url: 'https://example.com',
data: {
title: 'Example Title',
@@ -290,7 +301,7 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev
message: '',
metadata: {
embeds: [{
- type: 'opengraph' as PostEmbedType,
+ type: 'opengraph',
url: 'https://example.com',
data: {
title: 'Example Title',
@@ -319,7 +330,7 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev
create_at: 1234567890000,
metadata: {
embeds: [{
- type: 'opengraph' as PostEmbedType,
+ type: 'opengraph',
url: 'https://example.com',
data: {
title: 'Example Title',
@@ -366,7 +377,7 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev
metadata: {
files: [fileInfo],
embeds: [{
- type: 'opengraph' as PostEmbedType,
+ type: 'opengraph',
url: 'https://example.com',
data: {
title: 'Example Title',
@@ -412,7 +423,7 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev
metadata: {
files: fileInfos,
embeds: [{
- type: 'opengraph' as PostEmbedType,
+ type: 'opengraph',
url: 'https://example.com',
data: {
title: 'Example Title',
@@ -442,7 +453,7 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev
message: 'Post without files',
metadata: {
embeds: [{
- type: 'opengraph' as PostEmbedType,
+ type: 'opengraph',
url: 'https://example.com',
data: {
title: 'Example Title',
@@ -473,7 +484,7 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev
metadata: {
files: [],
embeds: [{
- type: 'opengraph' as PostEmbedType,
+ type: 'opengraph',
url: 'https://example.com',
data: {
title: 'Example Title',
@@ -492,4 +503,175 @@ describe('components/post_list/post/body/content/permalink_preview/PermalinkPrev
expect(queryByTestId('permalink-files-container')).toBeNull();
});
});
+
+ describe('autotranslationsEnabled', () => {
+ it('should not render TranslateIcon when autotranslationsEnabled is false', () => {
+ const {queryByTestId} = renderPermalinkPreview({
+ ...baseProps,
+ autotranslationsEnabled: false,
+ });
+
+ expect(queryByTestId('translate-icon')).toBeNull();
+ });
+
+ it('should render TranslateIcon when autotranslationsEnabled is true', () => {
+ const {getByTestId} = renderPermalinkPreview({
+ ...baseProps,
+ autotranslationsEnabled: true,
+ });
+
+ expect(getByTestId('translate-icon')).toBeTruthy();
+ });
+
+ it('should display translated message when autotranslationsEnabled is true and translation is ready', () => {
+ const originalMessage = 'Original message';
+ const translatedMessage = 'Translated message';
+ const props = {
+ ...baseProps,
+ autotranslationsEnabled: true,
+ embedData: {
+ ...baseProps.embedData,
+ post: TestHelper.fakePost({
+ id: 'post-123',
+ user_id: 'user-123',
+ message: originalMessage,
+ create_at: 1234567890000,
+ edit_at: 0,
+ metadata: {
+ translations: {
+ en: {
+ state: 'ready',
+ object: {message: translatedMessage},
+ },
+ },
+ embeds: [{
+ type: 'opengraph',
+ url: 'https://example.com',
+ data: {
+ title: 'Example Title',
+ description: 'Example Description',
+ },
+ }],
+ },
+ }),
+ },
+ };
+ const {getByText} = renderPermalinkPreview(props);
+
+ expect(getByText(translatedMessage)).toBeTruthy();
+ });
+
+ it('should display original message when autotranslationsEnabled is true but translation is not ready', () => {
+ const originalMessage = 'Original message only';
+ const props = {
+ ...baseProps,
+ autotranslationsEnabled: true,
+ embedData: {
+ ...baseProps.embedData,
+ post: TestHelper.fakePost({
+ id: 'post-123',
+ user_id: 'user-123',
+ message: originalMessage,
+ create_at: 1234567890000,
+ edit_at: 0,
+ metadata: {
+ translations: {
+ en: {
+ state: 'processing',
+ object: {message: ''},
+ },
+ },
+ embeds: [{
+ type: 'opengraph',
+ url: 'https://example.com',
+ data: {
+ title: 'Example Title',
+ description: 'Example Description',
+ },
+ }],
+ },
+ }),
+ },
+ };
+ const {getByText} = renderPermalinkPreview(props);
+
+ expect(getByText(originalMessage)).toBeTruthy();
+ });
+
+ it('should display original message when autotranslationsEnabled is false regardless of translation', () => {
+ const originalMessage = 'Original message';
+ const props = {
+ ...baseProps,
+ autotranslationsEnabled: false,
+ embedData: {
+ ...baseProps.embedData,
+ post: TestHelper.fakePost({
+ id: 'post-123',
+ user_id: 'user-123',
+ message: originalMessage,
+ create_at: 1234567890000,
+ edit_at: 0,
+ metadata: {
+ translations: {
+ en: {
+ state: 'ready',
+ object: {message: 'Translated message'},
+ },
+ },
+ embeds: [{
+ type: 'opengraph',
+ url: 'https://example.com',
+ data: {
+ title: 'Example Title',
+ description: 'Example Description',
+ },
+ }],
+ },
+ }),
+ },
+ };
+ const {getByText} = renderPermalinkPreview(props);
+
+ expect(getByText(originalMessage)).toBeTruthy();
+ });
+
+ it('should not translate custom posts or show TranslateIcon when autotranslationsEnabled is true', () => {
+ const originalMessage = 'Custom message';
+ const translatedMessage = 'Translated custom message';
+ const props = {
+ ...baseProps,
+ autotranslationsEnabled: true,
+ embedData: {
+ ...baseProps.embedData,
+ post: TestHelper.fakePost({
+ id: 'system-post-123',
+ user_id: 'user-123',
+ type: 'custom_llmbot',
+ message: originalMessage,
+ create_at: 1234567890000,
+ edit_at: 0,
+ metadata: {
+ translations: {
+ en: {
+ state: 'ready',
+ object: {message: translatedMessage},
+ },
+ },
+ embeds: [{
+ type: 'opengraph',
+ url: 'https://example.com',
+ data: {
+ title: 'Example Title',
+ description: 'Example Description',
+ },
+ }],
+ },
+ }),
+ },
+ };
+ const {queryByTestId, getByText} = renderPermalinkPreview(props);
+ expect(queryByTestId('translate-icon')).toBeNull();
+ expect(getByText(originalMessage)).toBeTruthy();
+ });
+ });
});
diff --git a/app/components/post_list/post/body/content/permalink_preview/permalink_preview.tsx b/app/components/post_list/post/body/content/permalink_preview/permalink_preview.tsx
index 4f82205e9..13e3ba366 100644
--- a/app/components/post_list/post/body/content/permalink_preview/permalink_preview.tsx
+++ b/app/components/post_list/post/body/content/permalink_preview/permalink_preview.tsx
@@ -11,6 +11,7 @@ import EditedIndicator from '@components/edited_indicator';
import FormattedText from '@components/formatted_text';
import FormattedTime from '@components/formatted_time';
import Markdown from '@components/markdown';
+import TranslateIcon from '@components/post_list/post/header/translate_icon';
import ProfilePicture from '@components/profile_picture';
import {View as ViewConstants} from '@constants';
import {useServerUrl} from '@context/server';
@@ -18,6 +19,7 @@ import {useTheme} from '@context/theme';
import {useUserLocale} from '@context/user_locale';
import {useIsTablet, useWindowDimensions} from '@hooks/device';
import {usePreventDoubleTap} from '@hooks/utils';
+import {getPostTranslatedMessage, getPostTranslation} from '@utils/post';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
import {displayUsername, getUserTimezone} from '@utils/user';
@@ -46,6 +48,7 @@ type PermalinkPreviewProps = {
location: AvailableScreens;
parentLocation?: string;
parentPostId?: string;
+ autotranslationsEnabled: boolean;
};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
@@ -77,6 +80,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
marginLeft: 12,
flexDirection: 'row',
alignItems: 'center',
+ gap: 8,
},
authorName: {
color: theme.centerChannelColor,
@@ -85,7 +89,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
timestamp: {
color: changeOpacity(theme.centerChannelColor, 0.64),
...typography('Body', 75),
- marginLeft: 8,
},
messageContainer: {
marginBottom: 8,
@@ -130,6 +133,7 @@ const PermalinkPreview = ({
location,
parentLocation,
parentPostId,
+ autotranslationsEnabled,
}: PermalinkPreviewProps) => {
const theme = useTheme();
const serverUrl = useServerUrl();
@@ -170,8 +174,15 @@ const PermalinkPreview = ({
channel_type,
} = embedData;
+ const translation = embedPost ? getPostTranslation(embedPost, locale) : undefined;
+
const truncatedMessage = useMemo(() => {
- const msg = embedPost?.message;
+ let msg = embedPost?.message ?? '';
+ if (autotranslationsEnabled && embedPost?.type === '') {
+ if (translation?.state === 'ready') {
+ msg = getPostTranslatedMessage(msg, translation);
+ }
+ }
if (!msg || typeof msg !== 'string') {
return '';
}
@@ -183,7 +194,7 @@ const PermalinkPreview = ({
}
return cleanMessage;
- }, [embedPost?.message]);
+ }, [autotranslationsEnabled, embedPost?.message, embedPost?.type, translation]);
const isEdited = useMemo(() => embedData && embedData.post && embedData.post.edit_at > 0, [embedData]);
@@ -254,9 +265,14 @@ const PermalinkPreview = ({
+ {autotranslationsEnabled && embedPost?.type === '' && (
+
+ )}
diff --git a/types/api/posts.d.ts b/types/api/posts.d.ts
index 7a25057cc..cffbcb4f6 100644
--- a/types/api/posts.d.ts
+++ b/types/api/posts.d.ts
@@ -51,7 +51,7 @@ type PostPriority = {
type PermalinkEmbedData = {
post_id: string;
- post: Post;
+ post?: Post;
team_name: string;
channel_display_name: string;
channel_type: string;