diff --git a/apps/desktop/layer/renderer/src/modules/entry-column/Items/picture-masonry.tsx b/apps/desktop/layer/renderer/src/modules/entry-column/Items/picture-masonry.tsx
index edf4677d4..213b3224a 100644
--- a/apps/desktop/layer/renderer/src/modules/entry-column/Items/picture-masonry.tsx
+++ b/apps/desktop/layer/renderer/src/modules/entry-column/Items/picture-masonry.tsx
@@ -283,8 +283,13 @@ const MasonryRender: React.ComponentType<
}>
> = ({ data, index }) => {
const firstScreenReady = use(FirstScreenReadyContext)
+ const enableTranslation = useGeneralSettingKey("translation")
const actionLanguage = useActionLanguage()
- const translation = useEntryTranslation(data.entryId, actionLanguage)
+ const translation = useEntryTranslation({
+ entryId: data.entryId,
+ language: actionLanguage,
+ setting: enableTranslation,
+ })
if (data.entryId.startsWith("placeholder")) {
return
diff --git a/apps/desktop/layer/renderer/src/modules/entry-column/Items/video-item.tsx b/apps/desktop/layer/renderer/src/modules/entry-column/Items/video-item.tsx
index afe45c5ed..c520ad416 100644
--- a/apps/desktop/layer/renderer/src/modules/entry-column/Items/video-item.tsx
+++ b/apps/desktop/layer/renderer/src/modules/entry-column/Items/video-item.tsx
@@ -173,13 +173,17 @@ const PreviewVideoModalContent: ModalContentComponent<{
}> = ({ dismiss, src, entryId }) => {
const entry = useEntry(entryId, (state) => ({ content: state.content }))
- const actionLanguage = useActionLanguage()
const enableTranslation = useGeneralSettingKey("translation")
- const translation = useEntryTranslation(entryId, actionLanguage)
+ const actionLanguage = useActionLanguage()
+ const translation = useEntryTranslation({
+ entryId,
+ language: actionLanguage,
+ setting: enableTranslation,
+ })
usePrefetchEntryTranslation({
entryIds: [entryId],
checkLanguage,
- translation: enableTranslation,
+ setting: enableTranslation,
language: actionLanguage,
withContent: true,
})
diff --git a/apps/desktop/layer/renderer/src/modules/entry-column/item.tsx b/apps/desktop/layer/renderer/src/modules/entry-column/item.tsx
index d813c8e42..8c45f2359 100644
--- a/apps/desktop/layer/renderer/src/modules/entry-column/item.tsx
+++ b/apps/desktop/layer/renderer/src/modules/entry-column/item.tsx
@@ -22,13 +22,17 @@ const EntryItemImpl = memo(function EntryItemImpl({
entryId: string
view: FeedViewType
}) {
- const actionLanguage = useActionLanguage()
const enableTranslation = useGeneralSettingKey("translation")
- const translation = useEntryTranslation(entryId, actionLanguage)
+ const actionLanguage = useActionLanguage()
+ const translation = useEntryTranslation({
+ entryId,
+ language: actionLanguage,
+ setting: enableTranslation,
+ })
usePrefetchEntryTranslation({
entryIds: [entryId],
checkLanguage,
- translation: enableTranslation,
+ setting: enableTranslation,
language: actionLanguage,
withContent: view === FeedViewType.SocialMedia,
})
diff --git a/apps/desktop/layer/renderer/src/modules/entry-content/components/EntryTitle.tsx b/apps/desktop/layer/renderer/src/modules/entry-content/components/EntryTitle.tsx
index e5ddcac10..26200ce19 100644
--- a/apps/desktop/layer/renderer/src/modules/entry-content/components/EntryTitle.tsx
+++ b/apps/desktop/layer/renderer/src/modules/entry-content/components/EntryTitle.tsx
@@ -6,6 +6,7 @@ import { useWhoami } from "@follow/store/user/hooks"
import { formatEstimatedMins, formatTimeToSeconds } from "@follow/utils"
import { titleCase } from "title-case"
+import { useShowAITranslation } from "~/atoms/ai-translation"
import { useActionLanguage } from "~/atoms/settings/general"
import { useUISettingKey } from "~/atoms/settings/ui"
import { RelativeTime } from "~/components/ui/datetime"
@@ -57,9 +58,13 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => {
const data = useEntryReadHistory(entryId)
const entryHistory = data?.entryReadHistories
const populatedFullHref = useFeedSafeUrl(entryId)
+ const enableTranslation = useShowAITranslation()
const actionLanguage = useActionLanguage()
-
- const translation = useEntryTranslation(entryId, actionLanguage)
+ const translation = useEntryTranslation({
+ entryId,
+ language: actionLanguage,
+ setting: enableTranslation,
+ })
const dateFormat = useUISettingKey("dateFormat")
diff --git a/apps/desktop/layer/renderer/src/modules/entry-content/hooks.tsx b/apps/desktop/layer/renderer/src/modules/entry-content/hooks.tsx
index 5b898ceac..d5b5946d0 100644
--- a/apps/desktop/layer/renderer/src/modules/entry-content/hooks.tsx
+++ b/apps/desktop/layer/renderer/src/modules/entry-content/hooks.tsx
@@ -5,8 +5,9 @@ import { createElement, useCallback, useMemo } from "react"
import { useTranslation } from "react-i18next"
import { toast } from "sonner"
+import { useShowAITranslation } from "~/atoms/ai-translation"
import { useEntryIsInReadability, useEntryIsInReadabilitySuccess } from "~/atoms/readability"
-import { useActionLanguage, useGeneralSettingKey } from "~/atoms/settings/general"
+import { useActionLanguage } from "~/atoms/settings/general"
import { useModalStack } from "~/components/ui/modal/stacked/hooks"
import { checkLanguage } from "~/lib/translate"
@@ -46,13 +47,17 @@ export const useEntryContent = (entryId: string) => {
const isInReadabilityMode = useEntryIsInReadability(entryId)
const isReadabilitySuccess = useEntryIsInReadabilitySuccess(entryId)
+ const enableTranslation = useShowAITranslation()
const actionLanguage = useActionLanguage()
- const enableTranslation = useGeneralSettingKey("translation")
- const contentTranslated = useEntryTranslation(entryId, actionLanguage)
+ const contentTranslated = useEntryTranslation({
+ entryId,
+ language: actionLanguage,
+ setting: enableTranslation,
+ })
usePrefetchEntryTranslation({
entryIds: [entryId],
checkLanguage,
- translation: enableTranslation,
+ setting: enableTranslation,
language: actionLanguage,
withContent: true,
target: isReadabilitySuccess ? "readabilityContent" : "content",
diff --git a/apps/mobile/src/components/native/webview/EntryContentWebView.tsx b/apps/mobile/src/components/native/webview/EntryContentWebView.tsx
index 519a18fb5..aa9525c2e 100644
--- a/apps/mobile/src/components/native/webview/EntryContentWebView.tsx
+++ b/apps/mobile/src/components/native/webview/EntryContentWebView.tsx
@@ -60,7 +60,7 @@ export function EntryContentWebView(props: EntryContentWebViewProps) {
const { entryId, noMedia, showReadability, showTranslation } = props
const entry = useEntry(entryId, (state) => state)
const language = useActionLanguage()
- const translation = useEntryTranslation(entryId, language)
+ const translation = useEntryTranslation({ entryId, language, setting: showTranslation ?? false })
const [mode, setMode] = React.useState<"normal" | "debug">("normal")
diff --git a/apps/mobile/src/modules/entry-content/EntryGridFooter.tsx b/apps/mobile/src/modules/entry-content/EntryGridFooter.tsx
index 4e8c57950..ebda3b740 100644
--- a/apps/mobile/src/modules/entry-content/EntryGridFooter.tsx
+++ b/apps/mobile/src/modules/entry-content/EntryGridFooter.tsx
@@ -5,7 +5,7 @@ import { useEntryTranslation } from "@follow/store/translation/hooks"
import { cn } from "@follow/utils"
import { Text, View } from "react-native"
-import { useActionLanguage } from "@/src/atoms/settings/general"
+import { useActionLanguage, useGeneralSettingKey } from "@/src/atoms/settings/general"
import { RelativeDateTime } from "@/src/components/ui/datetime/RelativeDateTime"
import { FeedIcon } from "@/src/components/ui/icon/feed-icon"
@@ -27,8 +27,13 @@ export const EntryGridFooter = ({
read: state.read,
translation: state.settings?.translation,
}))
+ const enableTranslation = useGeneralSettingKey("translation")
const actionLanguage = useActionLanguage()
- const translation = useEntryTranslation(entryId, actionLanguage)
+ const translation = useEntryTranslation({
+ entryId,
+ language: actionLanguage,
+ setting: enableTranslation,
+ })
const feed = useFeedById(entry?.feedId || "")
if (!entry) return null
diff --git a/apps/mobile/src/modules/entry-content/EntryTitle.tsx b/apps/mobile/src/modules/entry-content/EntryTitle.tsx
index 319bb7dc8..33e68f67a 100644
--- a/apps/mobile/src/modules/entry-content/EntryTitle.tsx
+++ b/apps/mobile/src/modules/entry-content/EntryTitle.tsx
@@ -1,20 +1,27 @@
import { useEntry } from "@follow/store/entry/hooks"
import { useFeedById } from "@follow/store/feed/hooks"
import { useEntryTranslation } from "@follow/store/translation/hooks"
-import { useSetAtom } from "jotai"
+import { useAtomValue, useSetAtom } from "jotai"
import { use } from "react"
import { Text, View } from "react-native"
-import { useActionLanguage } from "@/src/atoms/settings/general"
+import { useActionLanguage, useGeneralSettingKey } from "@/src/atoms/settings/general"
import { UserAvatar } from "@/src/components/ui/avatar/UserAvatar"
import { FeedIcon } from "@/src/components/ui/icon/feed-icon"
-import { EntryContentContext } from "@/src/modules/entry-content/ctx"
+import { EntryContentContext, useEntryContentContext } from "@/src/modules/entry-content/ctx"
import { EntryTranslation } from "../entry-list/templates/EntryTranslation"
export const EntryTitle = ({ title, entryId }: { title: string; entryId: string }) => {
+ const { showAITranslationAtom } = useEntryContentContext()
+ const showTranslationOnce = useAtomValue(showAITranslationAtom)
+ const enableTranslation = useGeneralSettingKey("translation") || showTranslationOnce
const actionLanguage = useActionLanguage()
- const translation = useEntryTranslation(entryId, actionLanguage)
+ const translation = useEntryTranslation({
+ entryId,
+ language: actionLanguage,
+ setting: enableTranslation,
+ })
const { titleHeightAtom } = use(EntryContentContext)
const setTitleHeight = useSetAtom(titleHeightAtom)
diff --git a/apps/mobile/src/modules/entry-list/EntryListContentArticle.tsx b/apps/mobile/src/modules/entry-list/EntryListContentArticle.tsx
index 55161452f..d5b6eda81 100644
--- a/apps/mobile/src/modules/entry-list/EntryListContentArticle.tsx
+++ b/apps/mobile/src/modules/entry-list/EntryListContentArticle.tsx
@@ -62,7 +62,7 @@ export const EntryListContentArticle = ({
usePrefetchEntryTranslation({
entryIds: active ? viewableItems.map((item) => item.key) : [],
language: actionLanguage,
- translation,
+ setting: translation,
checkLanguage,
})
diff --git a/apps/mobile/src/modules/entry-list/EntryListContentPicture.tsx b/apps/mobile/src/modules/entry-list/EntryListContentPicture.tsx
index 32aafd64d..9f44768f0 100644
--- a/apps/mobile/src/modules/entry-list/EntryListContentPicture.tsx
+++ b/apps/mobile/src/modules/entry-list/EntryListContentPicture.tsx
@@ -40,7 +40,7 @@ export const EntryListContentPicture = ({
entryIds: active ? viewableItems.map((item) => item.key) : [],
language: actionLanguage,
checkLanguage,
- translation,
+ setting: translation,
})
const renderItem = useTypeScriptHappyCallback(({ item }: { item: string }) => {
diff --git a/apps/mobile/src/modules/entry-list/EntryListContentSocial.tsx b/apps/mobile/src/modules/entry-list/EntryListContentSocial.tsx
index 6799ba2d3..f71aabe8e 100644
--- a/apps/mobile/src/modules/entry-list/EntryListContentSocial.tsx
+++ b/apps/mobile/src/modules/entry-list/EntryListContentSocial.tsx
@@ -52,7 +52,7 @@ export const EntryListContentSocial = ({
usePrefetchEntryTranslation({
entryIds: active ? viewableItems.map((item) => item.key) : [],
language: actionLanguage,
- translation,
+ setting: translation,
checkLanguage,
})
diff --git a/apps/mobile/src/modules/entry-list/EntryListContentVideo.tsx b/apps/mobile/src/modules/entry-list/EntryListContentVideo.tsx
index 18c8b7526..958c4d145 100644
--- a/apps/mobile/src/modules/entry-list/EntryListContentVideo.tsx
+++ b/apps/mobile/src/modules/entry-list/EntryListContentVideo.tsx
@@ -38,7 +38,7 @@ export const EntryListContentVideo = ({
usePrefetchEntryTranslation({
entryIds: active ? viewableItems.map((item) => item.key) : [],
language: actionLanguage,
- translation,
+ setting: translation,
checkLanguage,
})
diff --git a/apps/mobile/src/modules/entry-list/templates/EntryNormalItem.tsx b/apps/mobile/src/modules/entry-list/templates/EntryNormalItem.tsx
index 5660efc47..601d4c371 100644
--- a/apps/mobile/src/modules/entry-list/templates/EntryNormalItem.tsx
+++ b/apps/mobile/src/modules/entry-list/templates/EntryNormalItem.tsx
@@ -11,7 +11,7 @@ import { memo, useCallback, useMemo, useRef, useState } from "react"
import type { ImageErrorEventData } from "react-native"
import { Text, View } from "react-native"
-import { useActionLanguage } from "@/src/atoms/settings/general"
+import { useActionLanguage, useGeneralSettingKey } from "@/src/atoms/settings/general"
import { useUISettingKey } from "@/src/atoms/settings/ui"
import { preloadWebViewEntry } from "@/src/components/native/webview/EntryContentWebView"
import { RelativeDateTime } from "@/src/components/ui/datetime/RelativeDateTime"
@@ -52,8 +52,13 @@ export const EntryNormalItem = memo(
title: state.title,
description: state.description,
}))
+ const enableTranslation = useGeneralSettingKey("translation")
const actionLanguage = useActionLanguage()
- const translation = useEntryTranslation(entryId, actionLanguage)
+ const translation = useEntryTranslation({
+ entryId,
+ language: actionLanguage,
+ setting: enableTranslation,
+ })
const from = getInboxFrom(entry)
const feed = useFeedById(entry?.feedId as string)
const navigation = useNavigation()
diff --git a/apps/mobile/src/modules/entry-list/templates/EntrySocialItem.tsx b/apps/mobile/src/modules/entry-list/templates/EntrySocialItem.tsx
index 7a3dbbcf7..83af3a604 100644
--- a/apps/mobile/src/modules/entry-list/templates/EntrySocialItem.tsx
+++ b/apps/mobile/src/modules/entry-list/templates/EntrySocialItem.tsx
@@ -44,8 +44,13 @@ export const EntrySocialItem = memo(
author: state.author,
translation: state.settings?.translation,
}))
+ const enableTranslation = useGeneralSettingKey("translation")
const actionLanguage = useActionLanguage()
- const translation = useEntryTranslation(entryId, actionLanguage)
+ const translation = useEntryTranslation({
+ entryId,
+ language: actionLanguage,
+ setting: enableTranslation,
+ })
const { openLightbox } = useLightboxControls()
const feed = useFeedById(entry?.feedId || "")
diff --git a/apps/mobile/src/modules/entry-list/templates/EntryTranslation.tsx b/apps/mobile/src/modules/entry-list/templates/EntryTranslation.tsx
index 51b909d7e..6a3440f7b 100644
--- a/apps/mobile/src/modules/entry-list/templates/EntryTranslation.tsx
+++ b/apps/mobile/src/modules/entry-list/templates/EntryTranslation.tsx
@@ -20,7 +20,6 @@ export const EntryTranslation = ({
showTranslation?: boolean
bilingual?: boolean
} & TextProps) => {
- const showTranslationFinal = useGeneralSettingKey("translation") || showTranslation
const bilingualFinal = useGeneralSettingKey("translationMode") === "bilingual" || bilingual
const nextSource = useMemo(() => {
@@ -30,15 +29,11 @@ export const EntryTranslation = ({
return source.trim()
}, [source])
const nextTarget = useMemo(() => {
- if (
- !target ||
- !showTranslationFinal ||
- nextSource.replaceAll(/\s/g, "") === target.replaceAll(/\s/g, "")
- ) {
+ if (!target || nextSource.replaceAll(/\s/g, "") === target.replaceAll(/\s/g, "")) {
return ""
}
return target.trim()
- }, [nextSource, target, showTranslationFinal])
+ }, [nextSource, target])
if (!bilingualFinal) {
return (
diff --git a/apps/mobile/src/screens/(stack)/entries/[entryId]/EntryDetailScreen.tsx b/apps/mobile/src/screens/(stack)/entries/[entryId]/EntryDetailScreen.tsx
index 2a753ab81..802ec3c10 100644
--- a/apps/mobile/src/screens/(stack)/entries/[entryId]/EntryDetailScreen.tsx
+++ b/apps/mobile/src/screens/(stack)/entries/[entryId]/EntryDetailScreen.tsx
@@ -130,9 +130,9 @@ export const EntryDetailScreen: NavigationControllerView<{
const EntryContentWebViewWithContext = ({ entryId }: { entryId: string }) => {
const { showReadabilityAtom, showAITranslationAtom } = useEntryContentContext()
- const showReadability = useAtomValue(showReadabilityAtom)
+ const showReadabilityOnce = useAtomValue(showReadabilityAtom)
const translationSetting = useGeneralSettingKey("translation")
- const showTranslation = useAtomValue(showAITranslationAtom)
+ const showTranslationOnce = useAtomValue(showAITranslationAtom)
const actionLanguage = useActionLanguage()
const translation = useGeneralSettingKey("translation")
@@ -144,10 +144,10 @@ const EntryContentWebViewWithContext = ({ entryId }: { entryId: string }) => {
usePrefetchEntryTranslation({
entryIds: [entryId],
withContent: true,
- target: showReadability && entry?.readabilityContent ? "readabilityContent" : "content",
+ target: showReadabilityOnce && entry?.readabilityContent ? "readabilityContent" : "content",
language: actionLanguage,
checkLanguage,
- translation,
+ setting: translation,
})
// Auto toggle readability when content is empty
@@ -160,16 +160,16 @@ const EntryContentWebViewWithContext = ({ entryId }: { entryId: string }) => {
}, [isPending, entry?.content, setShowReadability])
useEffect(() => {
- if (showReadability) {
+ if (showReadabilityOnce) {
entrySyncServices.fetchEntryReadabilityContent(entryId)
}
- }, [showReadability, entryId])
+ }, [showReadabilityOnce, entryId])
return (
)
}
diff --git a/packages/internal/store/src/translation/hooks.ts b/packages/internal/store/src/translation/hooks.ts
index 98a72c2bc..3bd955787 100644
--- a/packages/internal/store/src/translation/hooks.ts
+++ b/packages/internal/store/src/translation/hooks.ts
@@ -3,7 +3,7 @@ import type { SupportedActionLanguage } from "@follow/shared"
import { useQueries } from "@tanstack/react-query"
import { useCallback } from "react"
-import { useEntryList } from "../entry/hooks"
+import { useEntry, useEntryList } from "../entry/hooks"
import type { EntryModel } from "../entry/types"
import { translationSyncService, useTranslationStore } from "./store"
@@ -11,19 +11,19 @@ export const usePrefetchEntryTranslation = ({
entryIds,
withContent,
target = "content",
- translation,
+ setting,
language,
checkLanguage,
}: {
entryIds: string[]
withContent?: boolean
target?: "content" | "readabilityContent"
- translation: boolean
+ setting: boolean
language: SupportedActionLanguage
checkLanguage: (params: { content: string; language: SupportedActionLanguage }) => boolean
}) => {
const entryList = (useEntryList(entryIds)?.filter(
- (entry) => entry !== null && (translation || !!entry?.settings?.translation),
+ (entry) => entry !== null && (setting || !!entry?.settings?.translation),
) || []) as EntryModel[]
return useQueries({
@@ -48,13 +48,24 @@ export const usePrefetchEntryTranslation = ({
})
}
-export const useEntryTranslation = (entryId: string, language: SupportedLanguages) => {
+export const useEntryTranslation = ({
+ entryId,
+ language,
+ setting,
+}: {
+ entryId: string
+ language: SupportedLanguages
+ setting: boolean
+}) => {
+ const actionSetting = useEntry(entryId, (state) => state.settings?.translation)
+
return useTranslationStore(
useCallback(
(state) => {
+ if (!setting && !actionSetting) return
return state.data[entryId]?.[language]
},
- [entryId, language],
+ [actionSetting, entryId, language, setting],
),
)
}