feat(mobile): translation for operation (#3396)

This commit is contained in:
Stephen Zhou 2025-04-03 12:25:54 +08:00 committed by GitHub
parent c471acc2dd
commit 7efa6aa96a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 368 additions and 53 deletions

View File

@ -6,6 +6,7 @@ import { saveToLibraryAsync } from "expo-media-library"
import { shareAsync } from "expo-sharing"
import type { PropsWithChildren } from "react"
import { useRef } from "react"
import { useTranslation } from "react-i18next"
import type { View } from "react-native"
import { findNodeHandle, Image, Pressable } from "react-native"
@ -35,6 +36,7 @@ const getIOSNativeImageActions = () => {
}
export const ImageContextMenu = ({ imageUrl, entryId, children }: ImageContextMenuProps) => {
const { t } = useTranslation()
const entry = useEntry(entryId!)
const feedId = entry?.feedId
const view = useSelectedView()
@ -105,7 +107,9 @@ export const ImageContextMenu = ({ imageUrl, entryId, children }: ImageContextMe
name: isEntryStarred ? "star.slash" : "star",
}}
/>
<ContextMenu.ItemTitle>{isEntryStarred ? "Unstar" : "Star"}</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>
{isEntryStarred ? t("operation.unstar") : t("operation.star")}
</ContextMenu.ItemTitle>
</ContextMenu.Item>
)}
@ -183,7 +187,7 @@ export const ImageContextMenu = ({ imageUrl, entryId, children }: ImageContextMe
name: "square.and.arrow.up",
}}
/>
<ContextMenu.ItemTitle>Share</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.share")}</ContextMenu.ItemTitle>
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>

View File

@ -1,4 +1,5 @@
import { cn } from "@follow/utils"
import { t } from "i18next"
import type { Dispatch, FC, ReactElement, ReactNode, SetStateAction } from "react"
import {
cloneElement,
@ -213,14 +214,14 @@ class DialogStatic {
<View className="flex-row gap-4 px-6 pb-4">
<DialogDynamicButtonAction
fallbackCaller={handleClose}
text={override?.cancelText ?? props.cancelText ?? "Cancel"}
text={override?.cancelText ?? props.cancelText ?? t("common:words.cancel")}
type="cancel"
textClassName={cn(props.variant === "destructive" && "font-bold")}
/>
<DialogDynamicButtonAction
fallbackCaller={handleConfirm}
text={override?.confirmText ?? props.confirmText ?? "Confirm"}
text={override?.confirmText ?? props.confirmText ?? t("common:words.confirm")}
type="confirm"
className={props.variant === "destructive" ? "bg-red" : "bg-accent"}
textClassName={cn(

View File

@ -1,6 +1,7 @@
import { PortalProvider } from "@gorhom/portal"
import type { PropsWithChildren } from "react"
import { useCallback } from "react"
import { useTranslation } from "react-i18next"
import { Share, Text, View } from "react-native"
import {
@ -19,6 +20,7 @@ import { useEntry } from "@/src/store/entry/hooks"
import { unreadSyncService } from "@/src/store/unread/store"
export const EntryItemContextMenu = ({ id, children }: PropsWithChildren<{ id: string }>) => {
const { t } = useTranslation()
const entry = useEntry(id)
const feedId = entry?.feedId
const view = useSelectedView()
@ -62,7 +64,7 @@ export const EntryItemContextMenu = ({ id, children }: PropsWithChildren<{ id: s
unreadSyncService.markEntryAsRead(id)
}}
>
<ContextMenu.ItemTitle>Mark as Read</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.mark_as_read")}</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon
ios={{
name: "checkmark",
@ -92,7 +94,9 @@ export const EntryItemContextMenu = ({ id, children }: PropsWithChildren<{ id: s
name: isEntryStarred ? "star.slash" : "star",
}}
/>
<ContextMenu.ItemTitle>{isEntryStarred ? "Unstar" : "Star"}</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>
{isEntryStarred ? t("operation.unstar") : t("operation.star")}
</ContextMenu.ItemTitle>
</ContextMenu.Item>
)}
@ -109,7 +113,7 @@ export const EntryItemContextMenu = ({ id, children }: PropsWithChildren<{ id: s
name: "link",
}}
/>
<ContextMenu.ItemTitle>Open Link</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.open_link")}</ContextMenu.ItemTitle>
</ContextMenu.Item>
)}
@ -130,7 +134,7 @@ export const EntryItemContextMenu = ({ id, children }: PropsWithChildren<{ id: s
name: "square.and.arrow.up",
}}
/>
<ContextMenu.ItemTitle>Share</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.share")}</ContextMenu.ItemTitle>
</ContextMenu.Item>
)}
</ContextMenu.Content>

View File

@ -1,6 +1,7 @@
import { FeedViewType } from "@follow/constants"
import type { FC, PropsWithChildren } from "react"
import { useCallback, useMemo } from "react"
import { useTranslation } from "react-i18next"
import type { ListRenderItemInfo } from "react-native"
import { Alert, Clipboard, FlatList, View } from "react-native"
@ -25,6 +26,7 @@ export const SubscriptionFeedItemContextMenu: FC<
view?: FeedViewType
}
> = ({ id, children, view }) => {
const { t } = useTranslation()
const allCategories = useSubscriptionCategory(view)
const navigation = useNavigation()
return (
@ -50,7 +52,7 @@ export const SubscriptionFeedItemContextMenu: FC<
unreadSyncService.markFeedAsRead(id)
}, [id])}
>
<ContextMenu.ItemTitle>Mark All As Read</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.mark_all_as_read")}</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon
ios={{
name: "checklist.checked",
@ -78,7 +80,7 @@ export const SubscriptionFeedItemContextMenu: FC<
<ContextMenu.Sub key="AddToCategory">
<ContextMenu.SubTrigger key="SubTrigger/AddToCategory">
<ContextMenu.ItemTitle>Add To Category</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.add_feeds_to_category")}</ContextMenu.ItemTitle>
</ContextMenu.SubTrigger>
<ContextMenu.SubContent>
@ -126,7 +128,7 @@ export const SubscriptionFeedItemContextMenu: FC<
</ContextMenu.Sub>
<ContextMenu.Item key="Edit">
<ContextMenu.ItemTitle>Edit</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.edit")}</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon
ios={{
name: "square.and.pencil",
@ -152,7 +154,7 @@ export const SubscriptionFeedItemContextMenu: FC<
}
}, [id])}
>
<ContextMenu.ItemTitle>Copy Link</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.copy_link")}</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon
ios={{
name: "link",
@ -171,16 +173,16 @@ export const SubscriptionFeedItemContextMenu: FC<
style: "cancel",
},
{
text: "Unsubscribe",
text: t("operation.unfollow"),
style: "destructive",
onPress: () => {
subscriptionSyncService.unsubscribe(id)
},
},
])
}, [id])}
}, [id, t])}
>
<ContextMenu.ItemTitle>Unsubscribe</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.unfollow")}</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon
ios={{
name: "xmark",
@ -202,6 +204,7 @@ export const SubscriptionFeedCategoryContextMenu = ({
feedIds: string[]
view: FeedViewType
}>) => {
const { t } = useTranslation()
return (
<ContextMenu.Root>
<ContextMenu.Trigger>{children}</ContextMenu.Trigger>
@ -213,7 +216,7 @@ export const SubscriptionFeedCategoryContextMenu = ({
unreadSyncService.markFeedAsRead(feedIds)
}, [feedIds])}
>
<ContextMenu.ItemTitle>Mark All As Read</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.mark_all_as_read")}</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon
ios={{
name: "checklist.checked",
@ -223,7 +226,7 @@ export const SubscriptionFeedCategoryContextMenu = ({
<ContextMenu.Sub key="ChangeToOtherView">
<ContextMenu.SubTrigger key="SubTrigger/ChangeToOtherView">
<ContextMenu.ItemTitle>Change To Other View</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.change_to_other_view")}</ContextMenu.ItemTitle>
</ContextMenu.SubTrigger>
<ContextMenu.SubContent>
@ -243,7 +246,7 @@ export const SubscriptionFeedCategoryContextMenu = ({
</ContextMenu.Sub>
<ContextMenu.Item key="EditCategory">
<ContextMenu.ItemTitle>Edit Category</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.rename_category")}</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon
ios={{
name: "square.and.pencil",
@ -251,7 +254,7 @@ export const SubscriptionFeedCategoryContextMenu = ({
/>
</ContextMenu.Item>
<ContextMenu.Item key="DeleteCategory" destructive>
<ContextMenu.ItemTitle>Delete Category</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.delete_category")}</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon
ios={{
name: "trash",

View File

@ -1,6 +1,7 @@
import { env } from "@follow/shared/src/env"
import type { FC, PropsWithChildren } from "react"
import { useMemo } from "react"
import { useTranslation } from "react-i18next"
import { Alert, Clipboard } from "react-native"
import { ContextMenu } from "@/src/components/ui/context-menu"
@ -14,11 +15,12 @@ export const SubscriptionListItemContextMenu: FC<
id: string
}
> = ({ id, children }) => {
const { t } = useTranslation()
const isOwnList = useIsOwnList(id)
const actions = useMemo(
() => [
isOwnList && {
title: "Edit",
title: t("operation.edit"),
onSelect: () => {
const list = getList(id)
if (!list) return
@ -26,7 +28,7 @@ export const SubscriptionListItemContextMenu: FC<
},
},
{
title: "Copy Link",
title: t("operation.copy_link"),
onSelect: () => {
const list = getList(id)
if (!list) return
@ -35,16 +37,16 @@ export const SubscriptionListItemContextMenu: FC<
},
},
{
title: "Unsubscribe",
title: t("operation.unfollow"),
destructive: true,
onSelect: () => {
Alert.alert("Unsubscribe", "Are you sure you want to unsubscribe?", [
Alert.alert(t("operation.unfollow"), "Are you sure you want to unsubscribe?", [
{
text: "Cancel",
style: "cancel",
},
{
text: "Unsubscribe",
text: t("operation.unfollow"),
style: "destructive",
onPress: () => {
subscriptionSyncService.unsubscribe(id)
@ -54,7 +56,7 @@ export const SubscriptionListItemContextMenu: FC<
},
},
],
[id, isOwnList],
[id, isOwnList, t],
)
return (

View File

@ -1,4 +1,5 @@
import type { PropsWithChildren } from "react"
import { useTranslation } from "react-i18next"
import { Clipboard, Share } from "react-native"
import { ContextMenu } from "@/src/components/ui/context-menu"
@ -14,6 +15,7 @@ type VideoContextMenuProps = PropsWithChildren<{
}>
export const VideoContextMenu = ({ entryId, children }: VideoContextMenuProps) => {
const { t } = useTranslation()
const entry = useEntry(entryId)
const feedId = entry?.feedId
const view = useSelectedView()
@ -50,7 +52,9 @@ export const VideoContextMenu = ({ entryId, children }: VideoContextMenuProps) =
name: isEntryStarred ? "star.slash" : "star",
}}
/>
<ContextMenu.ItemTitle>{isEntryStarred ? "Unstar" : "Star"}</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>
{isEntryStarred ? t("operation.unstar") : t("operation.star")}
</ContextMenu.ItemTitle>
</ContextMenu.Item>
)}
@ -66,7 +70,7 @@ export const VideoContextMenu = ({ entryId, children }: VideoContextMenuProps) =
name: "link",
}}
/>
<ContextMenu.ItemTitle>Open Link</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.open_link")}</ContextMenu.ItemTitle>
</ContextMenu.Item>
<ContextMenu.Item
@ -82,7 +86,7 @@ export const VideoContextMenu = ({ entryId, children }: VideoContextMenuProps) =
name: "document.on.document",
}}
/>
<ContextMenu.ItemTitle>Copy Link</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.copy_link")}</ContextMenu.ItemTitle>
</ContextMenu.Item>
<ContextMenu.Item
key="Share"
@ -101,7 +105,7 @@ export const VideoContextMenu = ({ entryId, children }: VideoContextMenuProps) =
name: "square.and.arrow.up",
}}
/>
<ContextMenu.ItemTitle>Share</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.share")}</ContextMenu.ItemTitle>
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>

View File

@ -1,3 +1,5 @@
import { t } from "i18next"
import { useTranslation } from "react-i18next"
import { Text, View } from "react-native"
import { CheckCircleCuteReIcon } from "@/src/icons/check_circle_cute_re"
@ -8,11 +10,12 @@ import { unreadSyncService } from "@/src/store/unread/store"
import { useSelectedView } from "../screen/atoms"
export const MarkAllAsReadDialog: DialogComponent = () => {
const { t } = useTranslation()
const selectedView = useSelectedView()
const ctx = Dialog.useDialogContext()
return (
<View>
<Text className="text-label">Do you want to mark all items as read?</Text>
<Text className="text-label">{t("operation.mark_all_as_read_confirm")}</Text>
<Dialog.DialogConfirm
onPress={() => {
ctx?.dismiss()
@ -26,8 +29,7 @@ export const MarkAllAsReadDialog: DialogComponent = () => {
)
}
MarkAllAsReadDialog.title = "Mark All as Read"
MarkAllAsReadDialog.title = t("operation.mark_all_as_read")
MarkAllAsReadDialog.id = "mark-all-as-read"
MarkAllAsReadDialog.title = "Mark All as Read"
MarkAllAsReadDialog.headerIcon = <CheckCircleCuteReIcon />

View File

@ -1,5 +1,6 @@
import { useAtom } from "jotai"
import { useCallback, useEffect, useState } from "react"
import { useTranslation } from "react-i18next"
import { Clipboard, Share, TouchableOpacity, View } from "react-native"
import type { SharedValue } from "react-native-reanimated"
import Animated, { interpolate, useAnimatedStyle } from "react-native-reanimated"
@ -56,6 +57,7 @@ const HeaderRightActionsImpl = ({
titleOpacityShareValue,
isHeaderTitleVisible,
}: HeaderRightActionsProps) => {
const { t } = useTranslation()
const labelColor = useColor("label")
const isStarred = useIsEntryStarred(entryId)
const [extraActionContainerWidth, setExtraActionContainerWidth] = useState(0)
@ -150,7 +152,7 @@ const HeaderRightActionsImpl = ({
const actionItems = [
subscription && {
key: "Star",
title: isStarred ? "Unstar" : "Star",
title: isStarred ? t("operation.unstar") : t("operation.star"),
icon: isStarred ? <StarCuteFiIcon /> : <StarCuteReIcon />,
iconIOS: {
name: isStarred ? "star.fill" : "star",
@ -189,7 +191,7 @@ const HeaderRightActionsImpl = ({
},
{
key: "Share",
title: "Share",
title: t("operation.share"),
icon: <Share3CuteReIcon />,
iconIOS: { name: "square.and.arrow.up" },
onPress: handleShare,
@ -257,7 +259,7 @@ const HeaderRightActionsImpl = ({
</DropdownMenu.Group>
)}
<DropdownMenu.Item key="CopyLink" onSelect={handleCopyLink}>
<DropdownMenu.ItemTitle>Copy Link</DropdownMenu.ItemTitle>
<DropdownMenu.ItemTitle>{t("operation.copy_link")}</DropdownMenu.ItemTitle>
<DropdownMenu.ItemIcon ios={{ name: "link" }} />
</DropdownMenu.Item>
<DropdownMenu.Item key="OpenInBrowser" onSelect={handleOpenInBrowser}>

View File

@ -1,5 +1,6 @@
import { env } from "@follow/shared/src/env"
import { useMemo } from "react"
import { useTranslation } from "react-i18next"
import { Share, View } from "react-native"
import { useColor } from "react-native-uikit-colors"
@ -76,12 +77,13 @@ export function TimelineSelectorProvider({
}
function FeedShareAction({ feedId }: { feedId?: string }) {
const { t } = useTranslation()
const label = useColor("label")
if (!feedId) return null
return (
<UIBarButton
label="Share"
label={t("operation.share")}
normalIcon={<Share3CuteReIcon height={24} width={24} color={label} />}
onPress={() => {
const feed = getFeed(feedId)

View File

@ -1,5 +1,6 @@
import type { FeedViewType } from "@follow/constants"
import type { FC, PropsWithChildren } from "react"
import { useTranslation } from "react-i18next"
import { ContextMenu } from "@/src/components/ui/context-menu"
import { unreadSyncService } from "@/src/store/unread/store"
@ -7,6 +8,7 @@ import { unreadSyncService } from "@/src/store/unread/store"
export const TimelineViewSelectorContextMenu: FC<
PropsWithChildren<{ type: string | undefined; viewId: FeedViewType | undefined }>
> = ({ children, type, viewId }) => {
const { t } = useTranslation()
if (type !== "view" || viewId === undefined) return children
return (
@ -19,7 +21,7 @@ export const TimelineViewSelectorContextMenu: FC<
unreadSyncService.markViewAsRead(viewId)
}}
>
<ContextMenu.ItemTitle>Mark as Read</ContextMenu.ItemTitle>
<ContextMenu.ItemTitle>{t("operation.mark_as_read")}</ContextMenu.ItemTitle>
<ContextMenu.ItemIcon
ios={{
name: "checklist.checked",

View File

@ -2,6 +2,7 @@ import { cn } from "@follow/utils"
import * as Haptics from "expo-haptics"
import type { PropsWithChildren } from "react"
import { useCallback } from "react"
import { useTranslation } from "react-i18next"
import { TouchableOpacity, View } from "react-native"
import { setGeneralSetting, useGeneralSettingKey } from "@/src/atoms/settings/general"
@ -54,12 +55,13 @@ export function HomeLeftAction() {
}
export function HomeSharedRightAction(props: PropsWithChildren) {
const { t } = useTranslation()
return (
<ActionGroup>
{props.children}
<UIBarButton
label="Mark All as Read"
label={t("operation.mark_all_as_read")}
normalIcon={<CheckCircleCuteReIcon height={24} width={24} color={accentColor} />}
onPress={() => {
Dialog.show(MarkAllAsReadDialog)
@ -80,17 +82,27 @@ const useButtonVariant = ({ variant = "primary" }: HeaderActionButtonProps) => {
return { size, color }
}
export const UnreadOnlyActionButton = ({ variant = "primary" }: HeaderActionButtonProps) => {
const { t } = useTranslation()
const unreadOnly = useGeneralSettingKey("unreadOnly")
const { size, color } = useButtonVariant({ variant })
return (
<UIBarButton
label={unreadOnly ? "Show All" : "Show Unread Only"}
label={
unreadOnly
? t("operation.toggle_unread_only.show_all.label")
: t("operation.toggle_unread_only.show_unread_only.label")
}
normalIcon={<RoundCuteReIcon height={size} width={size} color={color} />}
selectedIcon={<RoundCuteFiIcon height={size} width={size} color={color} />}
onPress={() => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
setGeneralSetting("unreadOnly", !unreadOnly)
toast.success(`Showing ${unreadOnly ? "all" : "unread"} entries`, { position: "bottom" })
toast.success(
unreadOnly
? t("operation.toggle_unread_only.show_all.success")
: t("operation.toggle_unread_only.show_unread_only.success"),
{ position: "bottom" },
)
}}
selected={unreadOnly}
overlay={false}

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "التغيير إلى عرض آخر",
"operation.copy_link": "نسخ الرابط",
"operation.delete_category": "حذف الفئة",
"operation.edit": "تحرير",
"operation.mark_all_as_read": "تحديد الكل كمقروء",
"operation.mark_all_as_read_confirm": "تأكيد تحديد الكل كمقروء؟",
"operation.mark_all_as_read_which": "هل تريد تحديد <which /> كمقروء؟",
"operation.mark_as_read": "تحديد كمقروء",
"operation.rename_category": "إعادة تسمية الفئة",
"operation.share": "مشاركة",
"operation.star": "تمييز بنجمة",
"operation.unfollow": "إلغاء المتابعة",
"profile.title": "ملف {{name}} الشخصي",
"profile.uncategorized_feeds": "الخلاصات غير المصنفة",
"tabs.discover": "اكتشف",

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "تغيير إلى عرض آخر",
"operation.copy_link": "نسخ الرابط",
"operation.delete_category": "حذف الفئة",
"operation.edit": "تحرير",
"operation.mark_all_as_read": "تحديد الكل كمقروء",
"operation.mark_all_as_read_confirm": "تأكيد تحديد الكل كمقروء؟",
"operation.mark_all_as_read_which": "تحديد <which /> كمقروء؟",
"operation.mark_as_read": "تحديد كمقروء",
"operation.rename_category": "إعادة تسمية الفئة",
"operation.share": "مشاركة",
"operation.star": "إضافة إلى المفضلة",
"operation.unfollow": "إلغاء المتابعة",
"profile.title": "ملف {{name}} الشخصي",
"profile.uncategorized_feeds": "الخلاصات غير المصنفة",
"tabs.discover": "اكتشف",

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "التبديل إلى طريقة عرض أخرى",
"operation.copy_link": "نسخ الرابط",
"operation.delete_category": "حذف الفئة",
"operation.edit": "تحرير",
"operation.mark_all_as_read": "وضع علامة الكل كمقروء",
"operation.mark_all_as_read_confirm": "تأكيد وضع علامة الجميع كمقروء؟",
"operation.mark_all_as_read_which": "وضع علامة <which /> كمقروء؟",
"operation.mark_as_read": "وضع علامة كمقروء",
"operation.rename_category": "إعادة تسمية الفئة",
"operation.share": "مشاركة",
"operation.star": "تفضيل",
"operation.unfollow": "إلغاء المتابعة",
"profile.title": "ملف {{name}} الشخصي",
"profile.uncategorized_feeds": "الخلاصات غير المصنفة",
"tabs.discover": "اكتشف",

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "تغيير إلى عرض آخر",
"operation.copy_link": "نسخ الرابط",
"operation.delete_category": "حذف الفئة",
"operation.edit": "تعديل",
"operation.mark_all_as_read": "وضع علامة على الكل كمقروء",
"operation.mark_all_as_read_confirm": "تأكيد وضع علامة على الكل كمقروء؟",
"operation.mark_all_as_read_which": "وضع علامة على <which /> كمقروء؟",
"operation.mark_as_read": "وضع علامة كمقروء",
"operation.rename_category": "إعادة تسمية الفئة",
"operation.share": "مشاركة",
"operation.star": "إضافة إلى المفضلة",
"operation.unfollow": "إلغاء المتابعة",
"profile.title": "ملف {{name}} الشخصي",
"profile.uncategorized_feeds": "الخلاصات غير المصنفة",
"tabs.discover": "اكتشف",

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "التغيير إلى عرض آخر",
"operation.copy_link": "نسخ الرابط",
"operation.delete_category": "حذف الفئة",
"operation.edit": "تحرير",
"operation.mark_all_as_read": "تمييز الكل كمقروء",
"operation.mark_all_as_read_confirm": "هل ترغب في تأكيد تمييز الكل كمقروء؟",
"operation.mark_all_as_read_which": "تمييز <which /> كمقروء؟",
"operation.mark_as_read": "تمييز كمقروء",
"operation.rename_category": "إعادة تسمية الفئة",
"operation.share": "مشاركة",
"operation.star": "تمييز",
"operation.unfollow": "إلغاء المتابعة",
"profile.title": "ملف {{name}} الشخصي",
"profile.uncategorized_feeds": "الخلاصات غير المصنفة",
"tabs.discover": "اكتشف",

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "التغيير إلى عرض آخر",
"operation.copy_link": "نسخ الرابط",
"operation.delete_category": "حذف الفئة",
"operation.edit": "تحرير",
"operation.mark_all_as_read": "وضع علامة على الكل كمقروء",
"operation.mark_all_as_read_confirm": "تأكيد وضع الجميع كمقروء؟",
"operation.mark_all_as_read_which": "وضع علامة <which /> كمقروء؟",
"operation.mark_as_read": "وضع علامة كمقروء",
"operation.rename_category": "إعادة تسمية الفئة",
"operation.share": "مشاركة",
"operation.star": "إضافة إلى المفضلة",
"operation.unfollow": "إلغاء المتابعة",
"profile.title": "ملف {{name}} الشخصي",
"profile.uncategorized_feeds": "الخلاصات غير المصنفة",
"tabs.discover": "اكتشف",

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "Zu anderer Ansicht wechseln",
"operation.copy_link": "Link kopieren",
"operation.delete_category": "Kategorie löschen",
"operation.edit": "Bearbeiten",
"operation.mark_all_as_read": "Alles als gelesen markieren",
"operation.mark_all_as_read_confirm": "Möchten Sie alle als gelesen markieren?",
"operation.mark_all_as_read_which": "<which /> als gelesen markieren?",
"operation.mark_as_read": "Als gelesen markieren",
"operation.rename_category": "Kategorie umbenennen",
"operation.share": "Teilen",
"operation.star": "Favorisieren",
"operation.unfollow": "Entfolgen",
"profile.title": "{{name}}s Profil",
"profile.uncategorized_feeds": "Unkategorisierte Feeds",
"tabs.discover": "Entdecken",

View File

@ -1,4 +1,23 @@
{
"operation.add_feeds_to_category": "Move to Category",
"operation.change_to_other_view": "Switch to Another View",
"operation.copy_link": "Copy Link",
"operation.delete_category": "Delete Category",
"operation.edit": "Edit",
"operation.mark_all_as_read": "Mark all as read",
"operation.mark_all_as_read_confirm": "Confirm mark all as read?",
"operation.mark_all_as_read_which": "Mark <which /> as read?",
"operation.mark_as_read": "Mark as Read",
"operation.open_link": "Open Link",
"operation.rename_category": "Rename Category",
"operation.share": "Share",
"operation.star": "Star",
"operation.toggle_unread_only.show_all.label": "Show All",
"operation.toggle_unread_only.show_all.success": "Showing all entries",
"operation.toggle_unread_only.show_unread_only.label": "Show Unread Only",
"operation.toggle_unread_only.show_unread_only.success": "Showing unread entries only",
"operation.unfollow": "Unfollow",
"operation.unstar": "Unstar",
"profile.title": "{{name}}'s Profile",
"profile.uncategorized_feeds": "Uncategorized feeds",
"tabs.discover": "Discover",

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "Cambiar a otra vista",
"operation.copy_link": "Copiar enlace",
"operation.delete_category": "Eliminar categoría",
"operation.edit": "Editar",
"operation.mark_all_as_read": "Marcar todo como leído",
"operation.mark_all_as_read_confirm": "¿Confirmar marcar todo como leído?",
"operation.mark_all_as_read_which": "¿Marcar <which /> como leído?",
"operation.mark_as_read": "Marcar como leído",
"operation.rename_category": "Renombrar categoría",
"operation.share": "Compartir",
"operation.star": "Agregar a favoritos",
"operation.unfollow": "Dejar de seguir",
"profile.title": "Perfil de {{name}}",
"profile.uncategorized_feeds": "Feeds sin categorizar",
"tabs.discover": "Descubrir",

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "Vaihda toiseen näkymään",
"operation.copy_link": "Kopioi linkki",
"operation.delete_category": "Poista kategoria",
"operation.edit": "Muokkaa",
"operation.mark_all_as_read": "Merkitse kaikki luetuiksi",
"operation.mark_all_as_read_confirm": "Vahvista kaikki merkinnät luetuiksi?",
"operation.mark_all_as_read_which": "Merkitäänkö <which /> luetuksi?",
"operation.mark_as_read": "Merkitse luetuksi",
"operation.rename_category": "Nimeä kategoria uudelleen",
"operation.share": "Jaa",
"operation.star": "Tähtää",
"operation.unfollow": "Lopeta seuraaminen",
"profile.title": "{{name}}n profiili",
"profile.uncategorized_feeds": "Luokittelemattomat syötteet",
"tabs.discover": "Löydä",

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "Changer pour une autre vue",
"operation.copy_link": "Copier le lien",
"operation.delete_category": "Supprimer la catégorie",
"operation.edit": "Éditer",
"operation.mark_all_as_read": "Tout marquer comme lu",
"operation.mark_all_as_read_confirm": "Confirmez que vous voulez tout marquer comme lu ?",
"operation.mark_all_as_read_which": "Marquer <which /> comme lu ?",
"operation.mark_as_read": "Marquer comme lu",
"operation.rename_category": "Renommer la catégorie",
"operation.share": "Partager",
"operation.star": "Mettre en favori",
"operation.unfollow": "Se désabonner",
"profile.title": "Profil de {{name}}",
"profile.uncategorized_feeds": "Flux non catégorisés",
"tabs.discover": "Découvrir",

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "Cambia ad altra visualizzazione",
"operation.copy_link": "Copia link",
"operation.delete_category": "Elimina categoria",
"operation.edit": "Modifica",
"operation.mark_all_as_read": "Segna tutto come letto",
"operation.mark_all_as_read_confirm": "Confermi di segnare tutto come letto?",
"operation.mark_all_as_read_which": "Segna <which /> come letto?",
"operation.mark_as_read": "Segna come letto",
"operation.rename_category": "Rinomina categoria",
"operation.share": "Condividi",
"operation.star": "Aggiungi ai preferiti",
"operation.unfollow": "Smetti di seguire",
"profile.title": "Profilo di {{name}}",
"profile.uncategorized_feeds": "Feed non categorizzati",
"tabs.discover": "Scopri",

View File

@ -1,4 +1,17 @@
{
"operation.add_feeds_to_category": "カテゴリーに移動",
"operation.change_to_other_view": "他のビューに切り替え",
"operation.copy_link": "リンクをコピー",
"operation.delete_category": "カテゴリーを削除",
"operation.edit": "編集",
"operation.mark_all_as_read": "すべてを既読にする",
"operation.mark_all_as_read_confirm": "すべてを既読にしますか?",
"operation.mark_all_as_read_which": "<which />を既読にしますか?",
"operation.mark_as_read": "既読にする",
"operation.rename_category": "カテゴリーをリネーム",
"operation.share": "共有",
"operation.star": "スター",
"operation.unfollow": "フォロー解除",
"profile.title": "{{name}}のプロフィール",
"profile.uncategorized_feeds": "未分類のフィード",
"tabs.discover": "発見",

View File

@ -1,4 +1,17 @@
{
"operation.add_feeds_to_category": "카테고리로 이동",
"operation.change_to_other_view": "다른 보기로 변경",
"operation.copy_link": "링크 복사",
"operation.delete_category": "카테고리 삭제",
"operation.edit": "편집",
"operation.mark_all_as_read": "모두 읽음으로 표시",
"operation.mark_all_as_read_confirm": "모두 읽음으로 표시하시겠습니까?",
"operation.mark_all_as_read_which": "<which />을 (를) 모두 읽음으로 표시하시겠습니까?",
"operation.mark_as_read": "읽음으로 표시",
"operation.rename_category": "카테고리 이름 변경",
"operation.share": "공유",
"operation.star": "별표",
"operation.unfollow": "언팔로우",
"profile.title": "{{name}}의 프로필",
"profile.uncategorized_feeds": "분류되지 않은 피드",
"tabs.discover": "발견",

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "他のビューに切り替え",
"operation.copy_link": "リンクをコピー",
"operation.delete_category": "カテゴリーを削除",
"operation.edit": "編集",
"operation.mark_all_as_read": "すべてを既読にする",
"operation.mark_all_as_read_confirm": "すべてを既読にしますか?",
"operation.mark_all_as_read_which": "<which />を既読にしますか?",
"operation.mark_as_read": "既読にする",
"operation.rename_category": "カテゴリーの名前を変更",
"operation.share": "共有",
"operation.star": "スター",
"operation.unfollow": "フォロー解除",
"profile.title": "Perfil de {{name}}",
"profile.uncategorized_feeds": "Feeds não categorizados",
"tabs.discover": "Descobrir",

View File

@ -1,4 +1,17 @@
{
"operation.add_feeds_to_category": "Переместить в категорию",
"operation.change_to_other_view": "Изменить на другой вид",
"operation.copy_link": "Копировать ссылку",
"operation.delete_category": "Удалить категорию",
"operation.edit": "Редактировать",
"operation.mark_all_as_read": "Отметить все как прочитанное",
"operation.mark_all_as_read_confirm": "Подтвердить отметку всего как прочитанное?",
"operation.mark_all_as_read_which": "Отметить <which /> как прочитанное?",
"operation.mark_as_read": "Отметить как прочитанное",
"operation.rename_category": "Переименовать категорию",
"operation.share": "Поделиться",
"operation.star": "Добавить в избранное",
"operation.unfollow": "Отписаться",
"profile.title": "Профиль {{name}}",
"profile.uncategorized_feeds": "Некатегоризированные ленты",
"tabs.discover": "Открыть",

View File

@ -1,4 +1,16 @@
{
"operation.change_to_other_view": "Diğer görünüme geç",
"operation.copy_link": "Bağlantıyı kopyala",
"operation.delete_category": "Kategoriyi sil",
"operation.edit": "Düzenle",
"operation.mark_all_as_read": "Tümünü okundu olarak işaretle",
"operation.mark_all_as_read_confirm": "Tümünü okundu olarak işaretlemeyi onaylıyor musunuz?",
"operation.mark_all_as_read_which": "<which /> okundu olarak işaretlensin mi?",
"operation.mark_as_read": "Okundu olarak işaretle",
"operation.rename_category": "Kategoriyi yeniden adlandır",
"operation.share": "Paylaş",
"operation.star": "Yıldızla",
"operation.unfollow": "Takibi bırak",
"profile.title": "{{name}}'nin Profili",
"profile.uncategorized_feeds": "Kategorize edilmemiş beslemeler",
"tabs.discover": "Keşfet",

View File

@ -1,4 +1,23 @@
{
"operation.add_feeds_to_category": "移动至分类",
"operation.change_to_other_view": "更改为其他视图",
"operation.copy_link": "复制链接",
"operation.delete_category": "删除分类",
"operation.edit": "编辑",
"operation.mark_all_as_read": "全部标记为已读",
"operation.mark_all_as_read_confirm": "确认将全部标记为已读?",
"operation.mark_all_as_read_which": "将 <which /> 标记为已读?",
"operation.mark_as_read": "标记为已读",
"operation.open_link": "打开链接",
"operation.rename_category": "重命名分类",
"operation.share": "分享",
"operation.star": "收藏",
"operation.toggle_unread_only.show_all.label": "显示全部",
"operation.toggle_unread_only.show_all.success": "正在显示所有条目",
"operation.toggle_unread_only.show_unread_only.label": "仅显示未读",
"operation.toggle_unread_only.show_unread_only.success": "仅显示未读条目",
"operation.unfollow": "取消订阅",
"operation.unstar": "取消收藏",
"profile.title": "{{name}}的个人资料",
"profile.uncategorized_feeds": "未分类的订阅源",
"tabs.discover": "发现",

View File

@ -1,4 +1,17 @@
{
"operation.add_feeds_to_category": "移至分類",
"operation.change_to_other_view": "切換到其他視圖",
"operation.copy_link": "複製連結",
"operation.delete_category": "刪除分類",
"operation.edit": "編輯",
"operation.mark_all_as_read": "全部標記為已讀",
"operation.mark_all_as_read_confirm": "確定將所有標記為已讀?",
"operation.mark_all_as_read_which": "確定將 <which /> 標記為已讀?",
"operation.mark_as_read": "標記為已讀",
"operation.rename_category": "重新命名分類",
"operation.share": "分享",
"operation.star": "收藏",
"operation.unfollow": "取消追隨",
"profile.title": "{{name}}的個人資料",
"profile.uncategorized_feeds": "未分類的訂閱源",
"tabs.discover": "探索",

View File

@ -1,4 +1,17 @@
{
"operation.add_feeds_to_category": "新增 RSS 摘要到類別",
"operation.change_to_other_view": "切換到其他視圖",
"operation.copy_link": "複製連結",
"operation.delete_category": "刪除類別",
"operation.edit": "編輯",
"operation.mark_all_as_read": "全部標記為已讀",
"operation.mark_all_as_read_confirm": "確認將全部標記為已讀?",
"operation.mark_all_as_read_which": "將 <which /> 標記為已讀?",
"operation.mark_as_read": "標記為已讀",
"operation.rename_category": "重新命名類別",
"operation.share": "分享",
"operation.star": "收藏",
"operation.unfollow": "取消跟隨",
"profile.title": "{{name}} 的個人資料",
"profile.uncategorized_feeds": "未分類的 RSS 摘要",
"tabs.discover": "探索",

View File

@ -13,7 +13,7 @@
},
"scripts": {
"build:web": "turbo run Folo#build:web",
"dedupe:locales": "eslint --fix locales/**/*.json",
"dedupe:locales": "eslint --fix locales/**",
"depcheck": "npx depcheck --quiet",
"format": "prettier --write .",
"format:check": "prettier --check .",

View File

@ -1,20 +1,38 @@
import fs from "node:fs"
import path from "node:path"
const sourceDir = "./locales/common"
const targetDir = "./locales/common"
const sourceDir = "./locales/app"
const targetDir = "./locales/mobile/default"
const keysToCopy: string[] = [
// // kept the duplicate keys in locales/app
// "words.starred",
// "words.inbox",
// "words.feeds",
// "words.lists",
// "words.search",
"cancel",
"close",
"confirm",
"sidebar.feed_column.context_menu.mark_as_read",
"entry_actions.star",
"entry_actions.share",
"mark_all_read_button.mark_all_as_read",
"sidebar.feed_column.context_menu.add_feeds_to_category",
"sidebar.feed_actions.edit",
"entry_actions.copy_link",
"sidebar.feed_actions.unfollow",
"sidebar.feed_column.context_menu.change_to_other_view",
"sidebar.feed_column.context_menu.rename_category",
"sidebar.feed_column.context_menu.delete_category",
"mark_all_read_button.confirm_mark_all",
"mark_all_read_button.confirm_mark_all_info",
]
const keysToPlace = [
"operation.mark_as_read",
"operation.star",
"operation.share",
"operation.mark_all_as_read",
"operation.add_feeds_to_category",
"operation.edit",
"operation.copy_link",
"operation.unfollow",
"operation.change_to_other_view",
"operation.rename_category",
"operation.delete_category",
"operation.mark_all_as_read_which",
"operation.mark_all_as_read_confirm",
]
const keysToPlace = ["words.cancel", "words.close", "words.confirm"]
const copyTranslations = (sourceDir: string, targetDir: string) => {
const sourceFiles = fs.readdirSync(sourceDir)