refactor: translation language -> action language (#3154)

* refactor: action language -> action language

* type

* mobile

* type

* fix

* prefer local settings, drop backward compatibility

* update

* update

* update

* chore: auto-fix linting and formatting issues

* update

* update

* update

* follow ui

* update

* add description

* fix build

* update

* update

* update
This commit is contained in:
Stephen Zhou 2025-03-24 10:10:48 +08:00 committed by GitHub
parent 6f5f9e8dd0
commit 8a5f92d0e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 240 additions and 225 deletions

View File

@ -187,7 +187,9 @@
"general.startup_screen.timeline": "Timeline",
"general.startup_screen.title": "Startup Screen",
"general.timeline": "Timeline",
"general.translation_language": "Translation Language",
"general.action_language.label": "Action Language",
"general.action_language.default": "Default (UI Language)",
"general.action_language.description": "The language used for translation and summary",
"general.unread": "Unread",
"general.voices": "Voices",
"integration.eagle.enable.description": "Display 'Save media to Eagle' button when available.",

View File

@ -167,7 +167,6 @@
"general.startup_screen.timeline": "タイムライン",
"general.startup_screen.title": "スタートアップスクリーン",
"general.timeline": "タイムライン",
"general.translation_language": "翻訳する言語",
"general.unread": "未読",
"general.voices": "音声",
"integration.eagle.enable.description": "利用可能な場合、'Eagle にメディアを保存' ボタンを表示します。",

View File

@ -172,7 +172,6 @@
"general.startup_screen.timeline": "타임라인",
"general.startup_screen.title": "시작 화면",
"general.timeline": "타임라인",
"general.translation_language": "번역 언어",
"general.unread": "읽지 않음",
"general.voices": "음성",
"integration.eagle.enable.description": "가능할 때 '미디어를 Eagle 에 저장' 버튼 표시.",

View File

@ -187,7 +187,6 @@
"general.startup_screen.timeline": "时间线",
"general.startup_screen.title": "起始界面",
"general.timeline": "时间线",
"general.translation_language": "翻译语言",
"general.unread": "未读",
"general.voices": "声音",
"integration.eagle.enable.description": "显示「保存到 Eagle」按钮如果可用。",

View File

@ -187,7 +187,6 @@
"general.startup_screen.timeline": "時間軸",
"general.startup_screen.title": "啟動畫面",
"general.timeline": "時間軸",
"general.translation_language": "翻譯語言",
"general.unread": "未讀",
"general.voices": "語音",
"integration.eagle.enable.description": "顯示「將媒體儲存到 Eagle」按鈕如果可用",

View File

@ -172,7 +172,6 @@
"general.startup_screen.timeline": "時間軸",
"general.startup_screen.title": "開始畫面",
"general.timeline": "時間軸",
"general.translation_language": "翻譯語言",
"general.unread": "未讀",
"general.voices": "聲音",
"integration.eagle.enable.description": "顯示「將媒體儲存到 Eagle」按鈕如果可用。",

View File

@ -1,13 +1,16 @@
import { createSettingAtom } from "@follow/atoms/helper/setting.js"
import type { SupportedLanguages } from "@follow/models"
import type { GeneralSettings } from "@follow/shared/interface/settings"
import { jotaiStore } from "~/lib/jotai"
export const DEFAULT_ACTION_LANGUAGE = "default"
const createDefaultSettings = (): GeneralSettings => ({
// App
appLaunchOnStartup: false,
language: "en",
translationLanguage: "zh-CN",
actionLanguage: DEFAULT_ACTION_LANGUAGE,
// mobile app
startupScreen: "timeline",
@ -47,6 +50,14 @@ export const {
settingAtom: __generalSettingAtom,
} = createSettingAtom("general", createDefaultSettings)
export function useActionLanguage() {
const actionLanguage = useGeneralSettingSelector((s) => s.actionLanguage)
const language = useGeneralSettingSelector((s) => s.language)
return (
actionLanguage === DEFAULT_ACTION_LANGUAGE ? language : actionLanguage
) as SupportedLanguages
}
export const subscribeShouldUseIndexedDB = (callback: (value: boolean) => void) =>
jotaiStore.sub(__generalSettingAtom, () => callback(getGeneralSettings().dataPersist))

View File

@ -36,11 +36,9 @@ export function immersiveTranslate({
return
}
const translation = entry.settings?.translation ?? targetLanguage
const immersiveTranslateMark = html.querySelectorAll("[data-immersive-translate-mark=true]")
if (immersiveTranslateMark.length > 0) {
if (translation) {
if (targetLanguage) {
return
}
@ -49,7 +47,7 @@ export function immersiveTranslate({
}
}
if (!translation) {
if (!targetLanguage) {
return
}
@ -61,7 +59,7 @@ export function immersiveTranslate({
translate({
entry,
language: translation,
language: targetLanguage,
part: textNode.textContent,
extraFields: ["content"],
}).then((transformed) => {
@ -96,7 +94,7 @@ export function immersiveTranslate({
if (tag.textContent) {
const isLanguageMatch = checkLanguage({
content: tag.textContent,
language: translation,
language: targetLanguage,
})
if (isLanguageMatch) {
continue
@ -151,7 +149,7 @@ export function immersiveTranslate({
translate({
entry,
language: translation,
language: targetLanguage,
part: textContent,
extraFields: ["content"],
}).then((transformed) => {

View File

@ -1,42 +1,13 @@
import { parseHtml } from "@follow/components/ui/markdown/parse-html.js"
import { views } from "@follow/constants"
import type { SupportedLanguages } from "@follow/models/types"
import { LANGUAGE_MAP } from "@follow/shared"
import { franc } from "franc-min"
import type { FlatEntryModel } from "~/store/entry"
import { apiClient } from "./api-fetch"
export const LanguageMap: Record<
SupportedLanguages,
{
label: string
value: string
code: string
}
> = {
en: {
value: "en",
label: "English",
code: "eng",
},
ja: {
value: "ja",
label: "Japanese",
code: "jpn",
},
"zh-CN": {
value: "zh-CN",
label: "Simplified Chinese",
code: "cmn",
},
"zh-TW": {
value: "zh-TW",
label: "Traditional Chinese (Taiwan)",
code: "cmn",
},
}
export const checkLanguage = ({
content,
language,
@ -48,10 +19,12 @@ export const checkLanguage = ({
const pureContent = parseHtml(content)
.toText()
.replaceAll(/https?:\/\/\S+|www\.\S+/g, " ")
const { code } = LANGUAGE_MAP[language]
if (!code) return false
const sourceLanguage = franc(pureContent, {
only: [LanguageMap[language].code],
only: [code],
})
if (sourceLanguage === LanguageMap[language].code) {
if (sourceLanguage === code) {
return true
} else {
return false

View File

@ -1,7 +1,6 @@
import { ActionButton, Button } from "@follow/components/ui/button/index.js"
import { Divider } from "@follow/components/ui/divider/index.js"
import { Input } from "@follow/components/ui/input/index.js"
import { ResponsiveSelect } from "@follow/components/ui/select/responsive.js"
import {
Table,
TableBody,
@ -10,7 +9,7 @@ import {
TableHeader,
TableRow,
} from "@follow/components/ui/table/index.jsx"
import type { ActionModel, SupportedLanguages } from "@follow/models/types"
import type { ActionModel } from "@follow/models/types"
import { Fragment, useMemo } from "react"
import { useTranslation } from "react-i18next"
@ -31,28 +30,6 @@ type Action = {
onRemove: (data: ActionModel) => void
}
const TransitionOptions: {
label: string
value: SupportedLanguages
}[] = [
{
label: "English",
value: "en",
},
{
label: "日本語",
value: "ja",
},
{
label: "简体中文",
value: "zh-CN",
},
{
label: "繁體中文",
value: "zh-TW",
},
]
const AddTableRow = ({ onClick, disabled }: { onClick?: () => void; disabled?: boolean }) => {
const { t } = useTranslation("settings")
return (
@ -108,26 +85,11 @@ export const TargetActionList = ({ index }: { index: number }) => {
title: t("actions.action_card.translate_into"),
enabled: !!translation,
onInit: (data) => {
data.result.translation = "zh-CN"
data.result.translation = true
},
onRemove: (data) => {
delete data.result.translation
},
config: () => (
<ResponsiveSelect
disabled={disabled}
size="sm"
value={translation}
onValueChange={(value) => {
onChange((data) => {
data.result.translation = value
})
}}
items={TransitionOptions}
triggerClassName="w-fit max-w-44 text-sm"
/>
),
configInline: true,
},
{
title: t("actions.action_card.enable_readability"),

View File

@ -27,7 +27,7 @@ import {
} from "react"
import { useEventCallback } from "usehooks-ts"
import { useGeneralSettingKey } from "~/atoms/settings/general"
import { useActionLanguage, useGeneralSettingKey } from "~/atoms/settings/general"
import { MediaContainerWidthProvider } from "~/components/ui/media"
import { useAuthQuery } from "~/hooks/common/useBizQuery"
import { Queries } from "~/queries"
@ -248,11 +248,12 @@ const MasonryRender: React.ComponentType<
> = ({ data, index }) => {
const firstScreenReady = useContext(FirstScreenReadyContext)
const entry = useEntry(data.entryId)
const actionLanguage = useActionLanguage()
const translation = useAuthQuery(
Queries.ai.translation({
entry: entry!,
view: entry?.view,
language: entry?.settings?.translation,
language: actionLanguage,
}),
{
enabled: !!entry?.settings?.translation,

View File

@ -5,6 +5,7 @@ import { cn } from "@follow/utils/utils"
import type { FC } from "react"
import { memo } from "react"
import { useActionLanguage } from "~/atoms/settings/general"
import { useAuthQuery } from "~/hooks/common"
import { Queries } from "~/queries"
import type { FlatEntryModel } from "~/store/entry"
@ -20,11 +21,12 @@ interface EntryItemProps {
view?: number
}
function EntryItemImpl({ entry, view }: { entry: FlatEntryModel; view?: number }) {
const actionLanguage = useActionLanguage()
const translation = useAuthQuery(
Queries.ai.translation({
entry,
view,
language: entry.settings?.translation,
language: actionLanguage,
}),
{
enabled: !!entry.settings?.translation,

View File

@ -1,10 +1,9 @@
import type { SupportedLanguages } from "@follow/models/types"
import { cn } from "@follow/utils/utils"
import dayjs from "dayjs"
import { useMemo } from "react"
import { useShowAITranslation } from "~/atoms/ai-translation"
import { useGeneralSettingSelector } from "~/atoms/settings/general"
import { useActionLanguage } from "~/atoms/settings/general"
import { useUISettingKey } from "~/atoms/settings/ui"
import { useWhoami } from "~/atoms/user"
import { RelativeTime } from "~/components/ui/datetime"
@ -49,12 +48,12 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => {
}, [entry?.entries.authorUrl, entry?.entries.url, feed?.siteUrl, feed?.type, inbox])
const showAITranslation = useShowAITranslation() || !!entry?.settings?.translation
const translationLanguage = useGeneralSettingSelector((s) => s.translationLanguage)
const actionLanguage = useActionLanguage()
const translation = useAuthQuery(
Queries.ai.translation({
entry: entry!,
language: entry?.settings?.translation || (translationLanguage as SupportedLanguages),
language: actionLanguage,
extraFields: ["title"],
}),
{

View File

@ -1,7 +1,7 @@
import { MemoedDangerousHTMLStyle } from "@follow/components/common/MemoedDangerousHTMLStyle.js"
import { ScrollArea } from "@follow/components/ui/scroll-area/index.js"
import { useTitle } from "@follow/hooks"
import type { FeedModel, InboxModel, SupportedLanguages } from "@follow/models/types"
import type { FeedModel, InboxModel } from "@follow/models/types"
import { IN_ELECTRON } from "@follow/shared/constants"
import { stopPropagation } from "@follow/utils/dom"
import { cn } from "@follow/utils/utils"
@ -11,7 +11,7 @@ import { useEffect, useMemo, useRef } from "react"
import { useShowAITranslation } from "~/atoms/ai-translation"
import { useEntryIsInReadability } from "~/atoms/readability"
import { useGeneralSettingSelector } from "~/atoms/settings/general"
import { useActionLanguage } from "~/atoms/settings/general"
import { useUISettingKey } from "~/atoms/settings/ui"
import { ShadowDOM } from "~/components/common/ShadowDOM"
import { useInPeekModal } from "~/components/ui/modal/inspire/PeekModal"
@ -117,9 +117,7 @@ export const EntryContent: Component<EntryContentProps> = ({
)
const customCSS = useUISettingKey("customCSS")
const showAITranslation = useShowAITranslation()
const translationLanguage = useGeneralSettingSelector(
(s) => s.translationLanguage,
) as SupportedLanguages
const actionLanguage = useActionLanguage()
if (!entry) return null
@ -131,8 +129,7 @@ export const EntryContent: Component<EntryContentProps> = ({
const fullText = html.textContent ?? ""
if (!fullText) return
const translation =
entry.settings?.translation ?? (showAITranslation ? translationLanguage : undefined)
const translation = showAITranslation ? actionLanguage : undefined
if (translation) {
const isLanguageMatch = checkLanguage({
@ -148,7 +145,7 @@ export const EntryContent: Component<EntryContentProps> = ({
immersiveTranslate({
html,
entry,
targetLanguage: translation as SupportedLanguages,
targetLanguage: translation,
cache: {
get: (key: string) => getTranslationCache()[key],
set: (key: string, value: string) =>

View File

@ -1,7 +1,7 @@
import { MemoedDangerousHTMLStyle } from "@follow/components/common/MemoedDangerousHTMLStyle.js"
import { ScrollElementContext } from "@follow/components/ui/scroll-area/ctx.js"
import { useTitle } from "@follow/hooks"
import type { FeedModel, InboxModel, SupportedLanguages } from "@follow/models/types"
import type { FeedModel, InboxModel } from "@follow/models/types"
import { nextFrame, stopPropagation } from "@follow/utils/dom"
import { cn } from "@follow/utils/utils"
import { ErrorBoundary } from "@sentry/react"
@ -9,7 +9,7 @@ import { useEffect, useMemo, useState } from "react"
import { useShowAITranslation } from "~/atoms/ai-translation"
import { useAudioPlayerAtomSelector } from "~/atoms/player"
import { useGeneralSettingSelector } from "~/atoms/settings/general"
import { useActionLanguage } from "~/atoms/settings/general"
import { useUISettingKey } from "~/atoms/settings/ui"
import { ShadowDOM } from "~/components/common/ShadowDOM"
import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry"
@ -104,9 +104,7 @@ export const EntryContent: Component<{
const customCSS = useUISettingKey("customCSS")
const showAITranslation = useShowAITranslation()
const translationLanguage = useGeneralSettingSelector(
(s) => s.translationLanguage,
) as SupportedLanguages
const actionLanguage = useActionLanguage()
const contentLineHeight = useUISettingKey("contentLineHeight")
const contentFontSize = useUISettingKey("contentFontSize")
@ -134,8 +132,7 @@ export const EntryContent: Component<{
const fullText = html.textContent ?? ""
if (!fullText) return
const translation =
entry.settings?.translation ?? (showAITranslation ? translationLanguage : undefined)
const translation = showAITranslation ? actionLanguage : undefined
if (translation) {
const isLanguageMatch = checkLanguage({
@ -151,7 +148,7 @@ export const EntryContent: Component<{
immersiveTranslate({
html,
entry,
targetLanguage: translation as SupportedLanguages,
targetLanguage: translation,
cache: {
get: (key: string) => getTranslationCache()[key],
set: (key: string, value: string) =>

View File

@ -21,6 +21,7 @@ import {
useEntryInReadabilityStatus,
useEntryReadabilityContent,
} from "~/atoms/readability"
import { useActionLanguage } from "~/atoms/settings/general"
import { enableShowSourceContent } from "~/atoms/source-content"
import { CopyButton } from "~/components/ui/button/CopyButton"
import { Toc } from "~/components/ui/markdown/components/Toc"
@ -319,10 +320,11 @@ export function AISummary({ entryId }: { entryId: string }) {
const { t } = useTranslation()
const entry = useEntry(entryId)
const showAISummary = useShowAISummary() || !!entry?.settings?.summary
const actionLanguage = useActionLanguage()
const summary = useAuthQuery(
Queries.ai.summary({
entryId,
language: entry?.settings?.translation,
language: actionLanguage,
}),
{
enabled: showAISummary,

View File

@ -1,6 +1,7 @@
import { useMobile } from "@follow/components/hooks/useMobile.js"
import { ResponsiveSelect } from "@follow/components/ui/select/responsive.js"
import { useTypeScriptHappyCallback } from "@follow/hooks"
import { LANGUAGE_MAP } from "@follow/shared"
import { IN_ELECTRON } from "@follow/shared/constants"
import { cn } from "@follow/utils/utils"
import { useQuery } from "@tanstack/react-query"
@ -13,6 +14,7 @@ import { currentSupportedLanguages } from "~/@types/constants"
import { defaultResources } from "~/@types/default-resource"
import { langLoadingLockMapAtom } from "~/atoms/lang"
import {
DEFAULT_ACTION_LANGUAGE,
setGeneralSetting,
useGeneralSettingKey,
useGeneralSettingSelector,
@ -22,7 +24,6 @@ import { useProxyValue, useSetProxy } from "~/hooks/biz/useProxySetting"
import { useMinimizeToTrayValue, useSetMinimizeToTray } from "~/hooks/biz/useTraySetting"
import { fallbackLanguage } from "~/i18n"
import { tipcClient } from "~/lib/client"
import { LanguageMap } from "~/lib/translate"
import { setTranslationCache } from "~/modules/entry-content/atoms"
import { SettingDescription, SettingInput, SettingSwitch } from "../control"
@ -68,7 +69,7 @@ export const SettingGeneral = () => {
IN_ELECTRON && MinimizeToTraySetting,
isMobile && StartupScreenSelector,
LanguageSelector,
TranslateLanguageSelector,
ActionLanguageSelector,
{
type: "title",
@ -243,23 +244,31 @@ export const LanguageSelector = ({
)
}
const TranslateLanguageSelector = () => {
const ActionLanguageSelector = () => {
const { t } = useTranslation("settings")
const translationLanguage = useGeneralSettingKey("translationLanguage")
const actionLanguage = useGeneralSettingKey("actionLanguage")
return (
<div className="mb-3 mt-4 flex items-center justify-between">
<span className="shrink-0 text-sm font-medium">{t("general.translation_language")}</span>
<div>
<span className="shrink-0 text-sm font-medium">{t("general.action_language.label")}</span>
<SettingDescription className="w-auto">
{t("general.action_language.description")}
</SettingDescription>
</div>
<ResponsiveSelect
size="sm"
triggerClassName="w-48"
defaultValue={translationLanguage}
value={translationLanguage}
defaultValue={actionLanguage}
value={actionLanguage}
onValueChange={(value) => {
setGeneralSetting("translationLanguage", value)
setGeneralSetting("actionLanguage", value)
setTranslationCache({})
}}
items={Object.values(LanguageMap)}
items={[
{ label: t("general.action_language.default"), value: DEFAULT_ACTION_LANGUAGE },
...Object.values(LANGUAGE_MAP),
]}
/>
</div>
)

View File

@ -6,7 +6,7 @@ const createDefaultSettings = (): GeneralSettings => ({
// App
language: "en",
translationLanguage: "zh-CN",
actionLanguage: "zh-CN",
// Data control

View File

@ -1,6 +1,6 @@
export interface GeneralSettings {
language: string
translationLanguage: string
actionLanguage: string
sendAnonymousData: boolean
unreadOnly: boolean

View File

@ -2,32 +2,3 @@ import type { languageSchema } from "@follow/shared/src/hono"
import type { z } from "zod"
export type SupportedLanguages = z.infer<typeof languageSchema>
export const LanguageMap: Record<
SupportedLanguages,
{
label: string
value: string
code: string
}
> = {
en: {
value: "en",
label: "English",
code: "eng",
},
ja: {
value: "ja",
label: "Japanese",
code: "jpn",
},
"zh-CN": {
value: "zh-CN",
label: "Simplified Chinese",
code: "cmn",
},
"zh-TW": {
value: "zh-TW",
label: "Traditional Chinese (Taiwan)",
code: "cmn",
},
}

View File

@ -1,28 +0,0 @@
import { Select } from "@/src/components/ui/form/Select"
import { GroupedInsetListCell } from "@/src/components/ui/grouped/GroupedList"
import { actionActions } from "@/src/store/action/store"
import type { ActionRule } from "@/src/store/action/types"
import { translationOptions } from "./constant"
export const ActionFormTranslation: React.FC<{ rule: ActionRule }> = ({ rule }) => {
const currentTranslation = translationOptions.find(
(translation) => translation.value === rule.result?.translation,
)
return (
<GroupedInsetListCell
label="Translate into"
leftClassName="flex-none"
rightClassName="flex-1 flex-row items-center gap-4 justify-end"
>
<Select
options={translationOptions}
value={currentTranslation?.value}
onValueChange={(value) => {
actionActions.patchRule(rule.index, { result: { translation: value } })
}}
wrapperClassName="min-w-40"
/>
</GroupedInsetListCell>
)
}

View File

@ -5,7 +5,6 @@ import type { ActionId, ActionRule } from "@/src/store/action/types"
import { EditRewriteRulesScreen } from "../routes/EditRewriteRules"
import { EditWebhooksScreen } from "../routes/EditWebhooks"
import { ActionFormTranslation } from "./components"
export const filterFieldOptions = [
{
@ -104,10 +103,6 @@ export const availableActionList: Array<{
{
value: "translation",
label: "Translate into",
onEnable: (index) => {
actionActions.patchRule(index, { result: { translation: "zh-CN" } })
},
component: ActionFormTranslation,
},
{
value: "readability",

View File

@ -1,3 +1,4 @@
import { LANGUAGE_MAP } from "@follow/shared"
import { useLocales } from "expo-localization"
import { Text, View } from "react-native"
@ -14,12 +15,11 @@ import {
GroupedInsetListSectionHeader,
} from "@/src/components/ui/grouped/GroupedList"
import { Switch } from "@/src/components/ui/switch/Switch"
import { LanguageMap } from "@/src/lib/language"
import type { NavigationControllerView } from "@/src/lib/navigation/types"
export const GeneralScreen: NavigationControllerView = () => {
const locales = useLocales()
const translationLanguage = useGeneralSettingKey("translationLanguage")
const actionLanguage = useGeneralSettingKey("actionLanguage")
const autoGroup = useGeneralSettingKey("autoGroup")
const showUnreadOnLaunch = useGeneralSettingKey("unreadOnly")
// const groupByDate = useGeneralSettingKey("groupByDate")
@ -42,15 +42,15 @@ export const GeneralScreen: NavigationControllerView = () => {
</GroupedInsetListBaseCell>
<GroupedInsetListBaseCell>
<Text className="text-label">Translation Language</Text>
<Text className="text-label">Action Language</Text>
<View className="w-[150px]">
<Select
value={translationLanguage}
value={actionLanguage}
onValueChange={(value) => {
setGeneralSetting("translationLanguage", value)
setGeneralSetting("actionLanguage", value)
}}
options={Object.values(LanguageMap)}
options={Object.values(LANGUAGE_MAP)}
/>
</View>
</GroupedInsetListBaseCell>

View File

@ -1,3 +1,4 @@
import { getGeneralSettings } from "@/src/atoms/settings/general"
import type { SummarySchema } from "@/src/database/schemas/types"
import { apiClient } from "@/src/lib/api-fetch"
import { summaryService } from "@/src/services/summary"
@ -103,14 +104,14 @@ class SummarySyncService {
state.generatingStatus[entryId] = SummaryGeneratingStatus.Pending
})
const language = entry.settings?.translation
const { actionLanguage } = getGeneralSettings()
// Use Our AI to generate summary
const summary = await apiClient.ai.summary
.$get({
query: {
id: entryId,
language,
language: actionLanguage as any,
},
})
.then((summary) => {
@ -121,7 +122,7 @@ class SummarySyncService {
}
state.data[entryId] = {
lang: language,
lang: actionLanguage,
summary: summary.data,
lastAccessed: Date.now(),
}
@ -142,7 +143,7 @@ class SummarySyncService {
{
entryId,
summary,
language: language ?? null,
language: actionLanguage ?? null,
},
])
}

View File

@ -125,7 +125,7 @@ export type ActionModel = {
}[][]
result: {
disabled?: boolean
translation?: string
translation?: boolean | string
summary?: boolean
readability?: boolean
silence?: boolean

View File

@ -1,3 +1,4 @@
export * from "./src/bridge"
export * from "./src/env"
export { type AppType } from "./src/hono"
export * from "./src/language"

View File

@ -359,7 +359,7 @@ declare const achievementsOpenAPISchema: zod.ZodObject<{
tx: string | null;
}>;
declare const languageSchema: z.ZodEnum<["en", "ja", "zh-CN", "zh-TW"]>;
declare const languageSchema: z.ZodEnum<["ar-DZ", "ar-IQ", "ar-KW", "ar-MA", "ar-SA", "ar-TN", "de", "en", "es", "fi", "fr", "it", "ja", "ko", "pt", "ru", "tr", "zh-CN", "zh-HK", "zh-TW"]>;
declare const ruleFieldSchema: z.ZodEnum<["all", "title", "content", "author", "url", "order"]>;
declare const ruleOperatorSchema: z.ZodEnum<["contains", "not_contains", "eq", "not_eq", "gt", "lt", "regex"]>;
declare const conditionItemSchema: z.ZodObject<{
@ -407,7 +407,7 @@ declare const actions: drizzle_orm_pg_core.PgTableWithColumns<{
condition: ConditionItem[] | ConditionItem[][];
result: {
disabled?: boolean;
translation?: z.infer<typeof languageSchema>;
translation?: z.infer<typeof languageSchema> | boolean;
summary?: boolean;
readability?: boolean;
sourceContent?: boolean;
@ -442,7 +442,7 @@ declare const actions: drizzle_orm_pg_core.PgTableWithColumns<{
condition: ConditionItem[] | ConditionItem[][];
result: {
disabled?: boolean;
translation?: z.infer<typeof languageSchema>;
translation?: z.infer<typeof languageSchema> | boolean;
summary?: boolean;
readability?: boolean;
sourceContent?: boolean;
@ -494,7 +494,7 @@ declare const actionsItemOpenAPISchema: z.ZodObject<{
}>, "many">, "many">]>;
result: z.ZodObject<{
disabled: z.ZodOptional<z.ZodBoolean>;
translation: z.ZodOptional<z.ZodEnum<["en", "ja", "zh-CN", "zh-TW"]>>;
translation: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["ar-DZ", "ar-IQ", "ar-KW", "ar-MA", "ar-SA", "ar-TN", "de", "en", "es", "fi", "fr", "it", "ja", "ko", "pt", "ru", "tr", "zh-CN", "zh-HK", "zh-TW"]>, z.ZodBoolean]>>;
summary: z.ZodOptional<z.ZodBoolean>;
readability: z.ZodOptional<z.ZodBoolean>;
sourceContent: z.ZodOptional<z.ZodBoolean>;
@ -527,7 +527,7 @@ declare const actionsItemOpenAPISchema: z.ZodObject<{
webhooks: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
}, "strip", z.ZodTypeAny, {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -546,7 +546,7 @@ declare const actionsItemOpenAPISchema: z.ZodObject<{
webhooks?: string[] | undefined;
}, {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -577,7 +577,7 @@ declare const actionsItemOpenAPISchema: z.ZodObject<{
}[][];
result: {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -608,7 +608,7 @@ declare const actionsItemOpenAPISchema: z.ZodObject<{
}[][];
result: {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -708,7 +708,7 @@ declare const actionsOpenAPISchema: z.ZodObject<z.objectUtil.extendShape<Omit<{
}>, "many">, "many">]>;
result: z.ZodObject<{
disabled: z.ZodOptional<z.ZodBoolean>;
translation: z.ZodOptional<z.ZodEnum<["en", "ja", "zh-CN", "zh-TW"]>>;
translation: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["ar-DZ", "ar-IQ", "ar-KW", "ar-MA", "ar-SA", "ar-TN", "de", "en", "es", "fi", "fr", "it", "ja", "ko", "pt", "ru", "tr", "zh-CN", "zh-HK", "zh-TW"]>, z.ZodBoolean]>>;
summary: z.ZodOptional<z.ZodBoolean>;
readability: z.ZodOptional<z.ZodBoolean>;
sourceContent: z.ZodOptional<z.ZodBoolean>;
@ -741,7 +741,7 @@ declare const actionsOpenAPISchema: z.ZodObject<z.objectUtil.extendShape<Omit<{
webhooks: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
}, "strip", z.ZodTypeAny, {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -760,7 +760,7 @@ declare const actionsOpenAPISchema: z.ZodObject<z.objectUtil.extendShape<Omit<{
webhooks?: string[] | undefined;
}, {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -791,7 +791,7 @@ declare const actionsOpenAPISchema: z.ZodObject<z.objectUtil.extendShape<Omit<{
}[][];
result: {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -822,7 +822,7 @@ declare const actionsOpenAPISchema: z.ZodObject<z.objectUtil.extendShape<Omit<{
}[][];
result: {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -856,7 +856,7 @@ declare const actionsOpenAPISchema: z.ZodObject<z.objectUtil.extendShape<Omit<{
}[][];
result: {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -890,7 +890,7 @@ declare const actionsOpenAPISchema: z.ZodObject<z.objectUtil.extendShape<Omit<{
}[][];
result: {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -7233,7 +7233,7 @@ declare const auth: {
signInSocial: {
<C extends [{
body: {
provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk";
provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "kick";
scopes?: string[] | undefined;
idToken?: {
token: string;
@ -7303,7 +7303,7 @@ declare const auth: {
callbackURL: zod.ZodOptional<zod.ZodString>;
newUserCallbackURL: zod.ZodOptional<zod.ZodString>;
errorCallbackURL: zod.ZodOptional<zod.ZodString>;
provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk")[]]>;
provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "kick")[]]>;
disableRedirect: zod.ZodOptional<zod.ZodBoolean>;
idToken: zod.ZodOptional<zod.ZodObject<{
token: zod.ZodString;
@ -7327,7 +7327,7 @@ declare const auth: {
scopes: zod.ZodOptional<zod.ZodArray<zod.ZodString, "many">>;
requestSignUp: zod.ZodOptional<zod.ZodBoolean>;
}, "strip", zod.ZodTypeAny, {
provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk";
provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "kick";
scopes?: string[] | undefined;
idToken?: {
token: string;
@ -7342,7 +7342,7 @@ declare const auth: {
newUserCallbackURL?: string | undefined;
disableRedirect?: boolean | undefined;
}, {
provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk";
provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "kick";
scopes?: string[] | undefined;
idToken?: {
token: string;
@ -9244,7 +9244,7 @@ declare const auth: {
linkSocialAccount: {
<C extends [{
body: {
provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk";
provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "kick";
callbackURL?: string | undefined;
};
headers: HeadersInit;
@ -9275,12 +9275,12 @@ declare const auth: {
requireHeaders: true;
body: zod.ZodObject<{
callbackURL: zod.ZodOptional<zod.ZodString>;
provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk")[]]>;
provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "kick")[]]>;
}, "strip", zod.ZodTypeAny, {
provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk";
provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "kick";
callbackURL?: string | undefined;
}, {
provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk";
provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "vk" | "kick";
callbackURL?: string | undefined;
}>;
use: ((inputContext: {
@ -9431,6 +9431,21 @@ declare const auth: {
provider: {
type: string;
};
createdAt: {
type: string;
};
updatedAt: {
type: string;
};
accountId: {
type: string;
};
scopes: {
type: string;
items: {
type: string;
};
};
};
};
};
@ -9504,6 +9519,7 @@ declare const auth: {
<C extends [{
body: {
providerId: string;
accountId: string;
};
method?: "POST" | undefined;
query?: Record<string, any> | undefined;
@ -9530,10 +9546,13 @@ declare const auth: {
method: "POST";
body: zod.ZodObject<{
providerId: zod.ZodString;
accountId: zod.ZodString;
}, "strip", zod.ZodTypeAny, {
providerId: string;
accountId: string;
}, {
providerId: string;
accountId: string;
}>;
use: ((inputContext: {
body?: any;
@ -12918,7 +12937,7 @@ declare const _routes: hono_hono_base.HonoBase<Env, ({
}[][];
result: {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -12961,7 +12980,7 @@ declare const _routes: hono_hono_base.HonoBase<Env, ({
}[][];
result: {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -12995,7 +13014,7 @@ declare const _routes: hono_hono_base.HonoBase<Env, ({
input: {
query: {
id: string;
language: "en" | "ja" | "zh-CN" | "zh-TW";
language: "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW";
fields: string;
part?: string | undefined;
};
@ -13018,7 +13037,7 @@ declare const _routes: hono_hono_base.HonoBase<Env, ({
input: {
query: {
id: string;
language?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
language?: "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
};
};
output: {
@ -13424,7 +13443,7 @@ declare const _routes: hono_hono_base.HonoBase<Env, ({
} | undefined;
settings?: {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;
@ -13691,7 +13710,7 @@ declare const _routes: hono_hono_base.HonoBase<Env, ({
} | undefined;
settings?: {
disabled?: boolean | undefined;
translation?: "en" | "ja" | "zh-CN" | "zh-TW" | undefined;
translation?: boolean | "ar-DZ" | "ar-IQ" | "ar-KW" | "ar-MA" | "ar-SA" | "ar-TN" | "de" | "en" | "es" | "fi" | "fr" | "it" | "ja" | "ko" | "pt" | "ru" | "tr" | "zh-CN" | "zh-HK" | "zh-TW" | undefined;
summary?: boolean | undefined;
readability?: boolean | undefined;
sourceContent?: boolean | undefined;

View File

@ -1,3 +1,4 @@
export * from "./constants"
export * from "./env"
export type { AppType } from "./hono"
export * from "./language"

View File

@ -1,7 +1,7 @@
export interface GeneralSettings {
appLaunchOnStartup: boolean
language: string
translationLanguage: string
actionLanguage: string
startupScreen: "subscription" | "timeline"
dataPersist: boolean
sendAnonymousData: boolean

View File

@ -0,0 +1,107 @@
import type { z } from "zod"
import type { languageSchema } from "./hono"
export const LANGUAGE_MAP: Record<
z.infer<typeof languageSchema>,
{
label: string
value: string
code?: string
}
> =
/// keep-sorted
{
"ar-DZ": {
label: "Arabic (Algeria)",
value: "ar-DZ",
},
"ar-IQ": {
label: "Arabic (Iraq)",
value: "ar-IQ",
},
"ar-KW": {
label: "Arabic (Kuwait)",
value: "ar-KW",
},
"ar-MA": {
label: "Arabic (Morocco)",
value: "ar-MA",
},
"ar-SA": {
label: "Arabic (Saudi Arabia)",
value: "ar-SA",
},
"ar-TN": {
label: "Arabic (Tunisia)",
value: "ar-TN",
},
"zh-CN": {
label: "Simplified Chinese",
value: "zh-CN",
code: "cmn",
},
"zh-HK": {
label: "Traditional Chinese (Hong Kong)",
value: "zh-HK",
},
"zh-TW": {
label: "Traditional Chinese (Taiwan)",
value: "zh-TW",
},
de: {
label: "German",
value: "de",
code: "deu",
},
en: {
label: "English",
value: "en",
code: "eng",
},
es: {
label: "Spanish",
value: "es",
code: "spa",
},
fi: {
label: "Finnish",
value: "fi",
code: "fin",
},
fr: {
label: "French",
value: "fr",
code: "fra",
},
it: {
label: "Italian",
value: "it",
code: "ita",
},
ja: {
label: "Japanese",
value: "ja",
code: "jpn",
},
ko: {
label: "Korean",
value: "ko",
code: "kor",
},
pt: {
label: "Portuguese",
value: "pt",
code: "por",
},
ru: {
label: "Russian",
value: "ru",
code: "rus",
},
tr: {
label: "Turkish",
value: "tr",
code: "tur",
},
}