fix: toggle translation settings, close #4187

This commit is contained in:
Stephen Zhou 2025-07-16 21:02:57 +08:00
parent 1fdf482f35
commit 51a243eeea
No known key found for this signature in database
17 changed files with 99 additions and 48 deletions

View File

@ -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 <LoadingSkeletonItem />

View File

@ -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,
})

View File

@ -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,
})

View File

@ -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")

View File

@ -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",

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -62,7 +62,7 @@ export const EntryListContentArticle = ({
usePrefetchEntryTranslation({
entryIds: active ? viewableItems.map((item) => item.key) : [],
language: actionLanguage,
translation,
setting: translation,
checkLanguage,
})

View File

@ -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 }) => {

View File

@ -52,7 +52,7 @@ export const EntryListContentSocial = ({
usePrefetchEntryTranslation({
entryIds: active ? viewableItems.map((item) => item.key) : [],
language: actionLanguage,
translation,
setting: translation,
checkLanguage,
})

View File

@ -38,7 +38,7 @@ export const EntryListContentVideo = ({
usePrefetchEntryTranslation({
entryIds: active ? viewableItems.map((item) => item.key) : [],
language: actionLanguage,
translation,
setting: translation,
checkLanguage,
})

View File

@ -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()

View File

@ -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 || "")

View File

@ -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 (

View File

@ -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 (
<EntryContentWebView
entryId={entryId}
showReadability={showReadability}
showTranslation={translationSetting || showTranslation}
showReadability={showReadabilityOnce}
showTranslation={translationSetting || showTranslationOnce}
/>
)
}

View File

@ -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],
),
)
}