diff --git a/package-lock.json b/package-lock.json index 56660343a..9971b278c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20410,6 +20410,15 @@ "expo": "*" } }, + "node_modules/expo-document-picker": { + "version": "14.0.8", + "resolved": "https://registry.npmjs.org/expo-document-picker/-/expo-document-picker-14.0.8.tgz", + "integrity": "sha512-3tyQKpPqWWFlI8p9RiMX1+T1Zge5mEKeBuXWp1h8PEItFMUDSiOJbQ112sfdC6Hxt8wSxreV9bCRl/NgBdt+fA==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-eas-client": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/expo-eas-client/-/expo-eas-client-1.0.8.tgz", @@ -37111,6 +37120,7 @@ "expo-constants": "~18.0.9", "expo-crypto": "^15.0.8", "expo-dev-client": "^6.0.15", + "expo-document-picker": "~14.0.8", "expo-file-system": "~19.0.17", "expo-haptics": "~15.0.7", "expo-image": "~3.0.10", diff --git a/packages/app/package.json b/packages/app/package.json index 88fc08046..b87959091 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -66,6 +66,7 @@ "expo-constants": "~18.0.9", "expo-crypto": "^15.0.8", "expo-dev-client": "^6.0.15", + "expo-document-picker": "~14.0.8", "expo-file-system": "~19.0.17", "expo-haptics": "~15.0.7", "expo-image": "~3.0.10", diff --git a/packages/app/src/attachments/file-types.ts b/packages/app/src/attachments/file-types.ts index 9144518ce..caa96733e 100644 --- a/packages/app/src/attachments/file-types.ts +++ b/packages/app/src/attachments/file-types.ts @@ -69,6 +69,11 @@ export function getFileExtension(path: string): string { return normalizedPath.slice(extensionIndex).toLowerCase(); } +export function getFileTypeLabel(path: string): string | null { + const extension = getFileExtension(path).slice(1); + return extension ? extension.toUpperCase() : null; +} + export function getMimeTypeFromPath(path: string): string { return MIME_TYPE_BY_EXTENSION[getFileExtension(path)] ?? "application/octet-stream"; } diff --git a/packages/app/src/components/attachment-pill.tsx b/packages/app/src/components/attachment-pill.tsx index dccd8ad54..86639d387 100644 --- a/packages/app/src/components/attachment-pill.tsx +++ b/packages/app/src/components/attachment-pill.tsx @@ -1,9 +1,16 @@ import { type ReactNode, useCallback, useMemo, useState } from "react"; -import { Pressable, View } from "react-native"; -import { StyleSheet, useUnistyles } from "react-native-unistyles"; +import { Image, Pressable, Text, View } from "react-native"; +import { StyleSheet, withUnistyles } from "react-native-unistyles"; import { X } from "lucide-react-native"; import { isNative } from "@/constants/platform"; import { useIsCompactFormFactor } from "@/constants/layout"; +import type { AttachmentMetadata } from "@/attachments/types"; +import { useAttachmentPreviewUrl } from "@/attachments/use-attachment-preview-url"; +import type { Theme } from "@/styles/theme"; + +// Every attachment pill body — image thumbnail or labelled — renders at this +// height so mixed attachment trays line up. +const ATTACHMENT_CONTENT_HEIGHT = 48; interface AttachmentPillProps { onOpen: () => void; @@ -24,7 +31,6 @@ export function AttachmentPill({ testID, children, }: AttachmentPillProps) { - const { theme } = useUnistyles(); const isCompact = useIsCompactFormFactor(); const [isBodyHovered, setIsBodyHovered] = useState(false); const [isCloseHovered, setIsCloseHovered] = useState(false); @@ -48,7 +54,7 @@ export function AttachmentPill({ onHoverOut={handleBodyHoverOut} accessibilityRole="button" accessibilityLabel={openAccessibilityLabel} - style={styles.body} + style={styles.frame} > {children} @@ -62,22 +68,127 @@ export function AttachmentPill({ accessibilityLabel={removeAccessibilityLabel} style={closeButtonStyle} > - + ); } +interface AttachmentFrameProps { + onPress?: () => void; + accessibilityLabel?: string; + testID?: string; + children: ReactNode; +} + +/** Bare attachment frame for read-only surfaces (sent messages) — no remove button. */ +export function AttachmentFrame({ + onPress, + accessibilityLabel, + testID, + children, +}: AttachmentFrameProps) { + if (!onPress) { + return ( + + {children} + + ); + } + return ( + + {children} + + ); +} + +interface AttachmentLabelProps { + icon?: ReactNode; + title: string; + subtitle: string; +} + +/** Two-line labelled pill body: attachment name over its type. */ +export function AttachmentLabel({ icon, title, subtitle }: AttachmentLabelProps) { + return ( + + {icon ? {icon} : null} + + + {title} + + + {subtitle} + + + + ); +} + +/** Square image preview pill body. */ +export function AttachmentThumbnail({ metadata }: { metadata: AttachmentMetadata }) { + const uri = useAttachmentPreviewUrl(metadata); + const source = useMemo(() => ({ uri: uri ?? "" }), [uri]); + if (!uri) { + return ; + } + return ; +} + +const ThemedX = withUnistyles(X); +const iconForegroundMutedMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted }); + const styles = StyleSheet.create((theme) => ({ wrapper: { position: "relative", }, - body: { + frame: { borderRadius: theme.borderRadius.md, borderWidth: theme.borderWidth[1], borderColor: theme.colors.borderAccent, overflow: "hidden", }, + labelBody: { + height: ATTACHMENT_CONTENT_HEIGHT, + maxWidth: 260, + flexDirection: "row", + alignItems: "center", + gap: theme.spacing[2], + paddingHorizontal: theme.spacing[3], + backgroundColor: theme.colors.surface1, + }, + labelIcon: { + width: 18, + alignItems: "center", + justifyContent: "center", + }, + labelTextColumn: { + minWidth: 0, + flexShrink: 1, + }, + labelTitle: { + color: theme.colors.foreground, + fontSize: theme.fontSize.sm, + }, + labelSubtitle: { + color: theme.colors.foregroundMuted, + fontSize: theme.fontSize.xs, + }, + thumbnail: { + width: ATTACHMENT_CONTENT_HEIGHT, + height: ATTACHMENT_CONTENT_HEIGHT, + }, + thumbnailPlaceholder: { + width: ATTACHMENT_CONTENT_HEIGHT, + height: ATTACHMENT_CONTENT_HEIGHT, + backgroundColor: theme.colors.surface1, + }, closeButton: { position: "absolute", top: -8, diff --git a/packages/app/src/components/message.tsx b/packages/app/src/components/message.tsx index 6befcdbfc..0465ed17c 100644 --- a/packages/app/src/components/message.tsx +++ b/packages/app/src/components/message.tsx @@ -32,6 +32,7 @@ import { useQuery } from "@tanstack/react-query"; import MaskedView from "@react-native-masked-view/masked-view"; import { Circle, + CircleDot, Info, CheckCircle, XCircle, @@ -41,13 +42,15 @@ import { Check, CheckSquare, Copy, + GitPullRequest, + MessageSquareCode, TriangleAlertIcon, Scissors, MicVocal, FileSymlink, } from "lucide-react-native"; import { StyleSheet, withUnistyles } from "react-native-unistyles"; -import type { Theme } from "@/styles/theme"; +import { ICON_SIZE, type Theme } from "@/styles/theme"; import { useIsCompactFormFactor } from "@/constants/layout"; import Animated, { Easing, @@ -101,6 +104,13 @@ import { import { getCompactionMarkerLabel } from "./message-compaction-label"; import { useAttachmentPreviewUrl } from "@/attachments/use-attachment-preview-url"; import { persistAttachmentFromBytes, persistAttachmentFromDataUrl } from "@/attachments/service"; +import { getFileTypeLabel } from "@/attachments/file-types"; +import { + AttachmentFrame, + AttachmentLabel, + AttachmentThumbnail, +} from "@/components/attachment-pill"; +import { AttachmentLightbox } from "@/components/attachment-lightbox"; import type { DaemonClient } from "@getpaseo/client/internal/daemon-client"; import { isWeb, isNative } from "@/constants/platform"; import type { AgentCapabilityFlags } from "@getpaseo/protocol/agent-types"; @@ -160,6 +170,10 @@ const ThemedTodoCheckIcon = withUnistyles(Check); const ThemedFileSymlinkIcon = withUnistyles(FileSymlink); const ThemedTriangleAlertIcon = withUnistyles(TriangleAlertIcon); const ThemedChevronRightIcon = withUnistyles(ChevronRight); +const ThemedAttachmentFileText = withUnistyles(FileText); +const ThemedGitPullRequest = withUnistyles(GitPullRequest); +const ThemedCircleDot = withUnistyles(CircleDot); +const ThemedMessageSquareCode = withUnistyles(MessageSquareCode); const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground }); const foregroundMutedColorMapping = (theme: Theme) => ({ @@ -363,34 +377,6 @@ const userMessageStylesheet = StyleSheet.create((theme) => ({ imagePreviewSpacing: { marginBottom: theme.spacing[2], }, - imagePill: { - borderRadius: theme.borderRadius.md, - borderWidth: 1, - borderColor: theme.colors.borderAccent, - overflow: "hidden", - }, - imageThumbnail: { - width: 48, - height: 48, - }, - imageThumbnailPlaceholder: { - width: 48, - height: 48, - backgroundColor: theme.colors.surface1, - }, - structuredAttachmentPill: { - maxWidth: 220, - borderRadius: theme.borderRadius.md, - borderWidth: 1, - borderColor: theme.colors.borderAccent, - backgroundColor: theme.colors.surface1, - paddingHorizontal: theme.spacing[3], - paddingVertical: theme.spacing[2], - }, - structuredAttachmentText: { - color: theme.colors.foreground, - fontSize: theme.fontSize.sm, - }, copyButton: { alignSelf: "center", padding: theme.spacing[1], @@ -417,36 +403,82 @@ const userMessageStylesheet = StyleSheet.create((theme) => ({ }, })); -function UserMessageAttachmentThumbnail({ image }: { image: UserMessageImageAttachment }) { - const uri = useAttachmentPreviewUrl(image); - const imageSource = useMemo(() => ({ uri: uri ?? "" }), [uri]); - if (!uri) { - return ; - } - return ; +const attachmentReviewIcon = ( + +); +const attachmentGithubPrIcon = ( + +); +const attachmentGithubIssueIcon = ( + +); +const attachmentFileIcon = ( + +); + +interface UserMessageImagePillProps { + image: UserMessageImageAttachment; + onOpen: (image: UserMessageImageAttachment) => void; + accessibilityLabel: string; } -function getUserMessageAttachmentLabel( +function UserMessageImagePill({ image, onOpen, accessibilityLabel }: UserMessageImagePillProps) { + const handlePress = useCallback(() => { + onOpen(image); + }, [onOpen, image]); + return ( + + + + ); +} + +interface UserMessageAttachmentContent { + icon: ReactNode; + title: string; + subtitle: string; +} + +function getUserMessageAttachmentContent( attachment: AgentAttachment, t: ReturnType["t"], -): string { +): UserMessageAttachmentContent { switch (attachment.type) { case "review": { const count = attachment.comments.length; - return count === 1 - ? t("message.attachments.reviewOne") - : t("message.attachments.reviewMany", { count }); + return { + icon: attachmentReviewIcon, + title: t("message.attachments.review"), + subtitle: + count === 1 + ? t("message.attachments.commentsOne") + : t("message.attachments.commentsMany", { count }), + }; } case "github_pr": - return `PR #${attachment.number}`; + return { + icon: attachmentGithubPrIcon, + title: attachment.title, + subtitle: `PR #${attachment.number}`, + }; case "github_issue": - return `Issue #${attachment.number}`; + return { + icon: attachmentGithubIssueIcon, + title: attachment.title, + subtitle: `Issue #${attachment.number}`, + }; case "text": - return attachment.title ?? t("message.attachments.textAttachment"); + return { + icon: attachmentFileIcon, + title: attachment.title ?? t("message.attachments.textAttachment"), + subtitle: t("message.attachments.text"), + }; case "uploaded_file": - return attachment.fileName; - default: - return ""; + return { + icon: attachmentFileIcon, + title: attachment.fileName, + subtitle: getFileTypeLabel(attachment.fileName) ?? t("message.attachments.file"), + }; } } @@ -467,6 +499,8 @@ export const UserMessage = memo(function UserMessage({ const isCompact = useIsCompactFormFactor(); const { t } = useTranslation(); const [isHovered, setIsHovered] = useState(false); + const [lightboxMetadata, setLightboxMetadata] = useState(null); + const handleLightboxClose = useCallback(() => setLightboxMetadata(null), []); const resolvedDisableOuterSpacing = useDisableOuterSpacing(disableOuterSpacing); const hasText = message.trim().length > 0; const hasImages = images.length > 0; @@ -534,24 +568,31 @@ export const UserMessage = memo(function UserMessage({ {hasImages ? ( {images.map((image) => ( - - - + ))} ) : null} {hasAttachments ? ( - {attachments.map((attachment, index) => ( - - - {getUserMessageAttachmentLabel(attachment, t)} - - - ))} + {attachments.map((attachment, index) => { + const content = getUserMessageAttachmentContent(attachment, t); + return ( + + + + ); + })} ) : null} {hasText ? ( @@ -579,6 +620,7 @@ export const UserMessage = memo(function UserMessage({ ) : null} + ); }); diff --git a/packages/app/src/composer/attachments/workspace.tsx b/packages/app/src/composer/attachments/workspace.tsx index 613aee53e..e41424793 100644 --- a/packages/app/src/composer/attachments/workspace.tsx +++ b/packages/app/src/composer/attachments/workspace.tsx @@ -1,14 +1,13 @@ import { useCallback, useEffect, useMemo, useState, type ReactElement } from "react"; -import { Text, View } from "react-native"; import { useTranslation } from "react-i18next"; -import { StyleSheet, withUnistyles } from "react-native-unistyles"; +import { withUnistyles } from "react-native-unistyles"; import { FileText, MessageSquareCode, MousePointer2 } from "lucide-react-native"; import type { ComposerAttachment, UserComposerAttachment, WorkspaceComposerAttachment, } from "@/attachments/types"; -import { AttachmentPill } from "@/components/attachment-pill"; +import { AttachmentLabel, AttachmentPill } from "@/components/attachment-pill"; import { useWorkspaceAttachmentsStore } from "@/attachments/workspace-attachments-store"; import { isWorkspaceAttachment, @@ -112,16 +111,31 @@ function getContextSourceLabel(attachment: WorkspaceComposerAttachment): string return "Review"; } -function getPillLabel(attachment: WorkspaceComposerAttachment, t: TranslationFn): string { +interface PillContent { + title: string; + subtitle: string; +} + +function getPillContent(attachment: WorkspaceComposerAttachment, t: TranslationFn): PillContent { if (attachment.kind === "browser_element") { - return t("composer.attachments.browserElement", { tag: attachment.attachment.tag }); + return { + title: attachment.attachment.tag, + subtitle: t("composer.attachments.element"), + }; } if (isPullRequestContextAttachment(attachment)) { - return `${getContextSourceLabel(attachment)} · ${attachment.title}`; + return { + title: attachment.title, + subtitle: getContextSourceLabel(attachment), + }; } - return attachment.commentCount === 1 - ? t("message.attachments.reviewOne") - : t("message.attachments.reviewMany", { count: attachment.commentCount }); + return { + title: t("message.attachments.review"), + subtitle: + attachment.commentCount === 1 + ? t("message.attachments.commentsOne") + : t("message.attachments.commentsMany", { count: attachment.commentCount }), + }; } function getOpenAccessibilityLabel( @@ -309,7 +323,7 @@ function WorkspaceAttachmentPill({ onRemove, }: WorkspaceAttachmentPillProps) { const { t } = useTranslation(); - const label = getPillLabel(attachment, t); + const content = getPillContent(attachment, t); const handleOpen = useCallback(() => { onOpen(attachment); }, [onOpen, attachment]); @@ -325,12 +339,11 @@ function WorkspaceAttachmentPill({ removeAccessibilityLabel={getRemoveAccessibilityLabel(attachment, t)} disabled={disabled} > - - {renderPillIcon(attachment)} - - {label} - - + ); } @@ -343,30 +356,6 @@ export const composerWorkspaceAttachment = { useBinding: useWorkspaceAttachmentBinding, }; -const styles = StyleSheet.create((theme: Theme) => ({ - pillBody: { - minHeight: 48, - maxWidth: 260, - flexDirection: "row", - alignItems: "center", - gap: theme.spacing[2], - paddingHorizontal: theme.spacing[3], - paddingVertical: theme.spacing[2], - backgroundColor: theme.colors.surface1, - }, - pillIcon: { - width: 18, - alignItems: "center", - justifyContent: "center", - }, - pillText: { - minWidth: 0, - flexShrink: 1, - color: theme.colors.foreground, - fontSize: theme.fontSize.sm, - }, -})) as unknown as Record; - const ThemedMousePointer2 = withUnistyles(MousePointer2); const ThemedMessageSquareCode = withUnistyles(MessageSquareCode); const ThemedFileText = withUnistyles(FileText); diff --git a/packages/app/src/composer/index.tsx b/packages/app/src/composer/index.tsx index 1ee638f82..43df97310 100644 --- a/packages/app/src/composer/index.tsx +++ b/packages/app/src/composer/index.tsx @@ -3,7 +3,6 @@ import { Pressable, Text, ActivityIndicator, - Image, type PressableStateCallbackType, } from "react-native"; import type { TFunction } from "i18next"; @@ -100,9 +99,9 @@ import type { WorkspaceComposerAttachment, } from "@/attachments/types"; import { composerWorkspaceAttachment } from "@/composer/attachments/workspace"; -import { useAttachmentPreviewUrl } from "@/attachments/use-attachment-preview-url"; +import { getFileTypeLabel } from "@/attachments/file-types"; import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox"; -import { AttachmentPill } from "@/components/attachment-pill"; +import { AttachmentLabel, AttachmentPill, AttachmentThumbnail } from "@/components/attachment-pill"; import { AttachmentLightbox } from "@/components/attachment-lightbox"; import { openExternalUrl } from "@/utils/open-external-url"; import { useIsDictationReady } from "@/hooks/use-is-dictation-ready"; @@ -560,15 +559,6 @@ function QueuedMessageRow({ ); } -function ImageAttachmentThumbnail({ image }: { image: ImageAttachment }) { - const uri = useAttachmentPreviewUrl(image); - const source = useMemo(() => ({ uri: uri ?? "" }), [uri]); - if (!uri) { - return ; - } - return ; -} - interface ImageAttachmentPillProps { attachment: Extract; index: number; @@ -603,7 +593,7 @@ function ImageAttachmentPill({ removeAccessibilityLabel={removeLabel} disabled={disabled} > - + ); } @@ -644,18 +634,11 @@ function GithubAttachmentPill({ removeAccessibilityLabel={removeLabel(kindLabel, item.number)} disabled={disabled} > - - - {item.kind === "pr" ? ( - - ) : ( - - )} - - - #{item.number} {item.title} - - + ); } @@ -675,26 +658,25 @@ function FileAttachmentPill({ onRemove, removeLabel, }: FileAttachmentPillProps) { + const { t } = useTranslation(); const handleRemove = useCallback(() => { onRemove(index); }, [onRemove, index]); + const fileName = attachment.attachment.fileName; return ( - - - - - - {attachment.attachment.fileName} - - + ); } @@ -1705,18 +1687,14 @@ export function Composer({ setIsGithubPickerOpen(true); }, }, - ...(!isNative - ? [ - { - id: "file", - label: t("composer.attachments.addFile"), - icon: , - onSelect: () => { - void handlePickFile(); - }, - }, - ] - : []), + { + id: "file", + label: t("composer.attachments.addFile"), + icon: , + onSelect: () => { + void handlePickFile(); + }, + }, ], [handlePickImage, handlePickFile, t], ); @@ -2059,50 +2037,6 @@ const styles = StyleSheet.create((theme: Theme) => ({ gap: theme.spacing[2], flexWrap: "wrap", }, - imageThumbnail: { - width: 32, - height: 32, - }, - imageThumbnailPlaceholder: { - width: 32, - height: 32, - backgroundColor: theme.colors.surface2, - }, - githubPillBody: { - minHeight: 32, - maxWidth: 260, - flexDirection: "row", - alignItems: "center", - gap: theme.spacing[2], - paddingHorizontal: theme.spacing[3], - paddingVertical: theme.spacing[2], - backgroundColor: theme.colors.surface1, - }, - githubPillIcon: { - width: 18, - alignItems: "center", - justifyContent: "center", - }, - filePillBody: { - flexDirection: "row", - alignItems: "center", - gap: theme.spacing[2], - }, - filePillIcon: { - justifyContent: "center", - alignItems: "center", - }, - filePillText: { - color: theme.colors.foreground, - fontSize: theme.fontSize.sm, - flexShrink: 1, - }, - githubPillText: { - minWidth: 0, - flexShrink: 1, - color: theme.colors.foreground, - fontSize: theme.fontSize.sm, - }, tooltipRow: { flexDirection: "row", alignItems: "center", @@ -2173,3 +2107,11 @@ const ThemedGithub = withUnistyles(Github); const iconForegroundMapping = (theme: Theme) => ({ color: theme.colors.foreground }); const iconForegroundMutedMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted }); const iconAccentForegroundMapping = (theme: Theme) => ({ color: theme.colors.accentForeground }); + +const githubPrPillIcon = ( + +); +const githubIssuePillIcon = ( + +); +const filePillIcon = ; diff --git a/packages/app/src/hooks/use-file-picker.ts b/packages/app/src/hooks/use-file-picker.ts index f0cdcc064..110a273ee 100644 --- a/packages/app/src/hooks/use-file-picker.ts +++ b/packages/app/src/hooks/use-file-picker.ts @@ -1,4 +1,6 @@ import { useCallback, useRef } from "react"; +import * as DocumentPicker from "expo-document-picker"; +import { File } from "expo-file-system"; import { getDesktopHost, isElectronRuntime } from "@/desktop/host"; import { copyDesktopAttachmentFile } from "@/desktop/attachments/desktop-file-commands"; import { readDesktopFileBase64 } from "@/desktop/attachments/desktop-preview-url"; @@ -113,6 +115,25 @@ function pickFilesWithWebInput(): Promise { }); } +async function pickFilesWithDocumentPicker(): Promise { + const result = await DocumentPicker.getDocumentAsync({ + multiple: true, + copyToCacheDirectory: true, + }); + + if (result.canceled || result.assets.length === 0) { + return null; + } + + return await Promise.all( + result.assets.map(async (asset) => ({ + fileName: asset.name, + mimeType: asset.mimeType ?? getMimeTypeFromPath(asset.name), + bytes: await new File(asset.uri).bytes(), + })), + ); +} + export function useFilePicker() { const isPickingRef = useRef(false); @@ -131,8 +152,7 @@ export function useFilePicker() { return await pickFilesWithWebInput(); } - // Native not supported yet - return null; + return await pickFilesWithDocumentPicker(); } catch (error) { console.error("[FilePicker] Failed to pick files:", error); throw error; diff --git a/packages/app/src/i18n/resources.test.ts b/packages/app/src/i18n/resources.test.ts index eb22ae67d..570256730 100644 --- a/packages/app/src/i18n/resources.test.ts +++ b/packages/app/src/i18n/resources.test.ts @@ -575,10 +575,11 @@ describe("translation resources", () => { }); it("includes remaining utility chrome keys for the Batch 4T migration", () => { - expect(en.message.attachments.reviewOne).toBe("Review · 1 comment"); - expect(en.message.attachments.reviewMany).toBe("Review · {{count}} comments"); + expect(en.message.attachments.review).toBe("Review"); + expect(en.message.attachments.commentsOne).toBe("1 comment"); + expect(en.message.attachments.commentsMany).toBe("{{count}} comments"); expect(en.message.attachments.textAttachment).toBe("Text attachment"); - expect(en.composer.attachments.browserElement).toBe("Element · {{tag}}"); + expect(en.composer.attachments.element).toBe("Element"); expect(en.workspace.hoverCard.scriptsAccessibility).toBe("Workspace scripts"); expect(en.branchSwitcher.restoreStashTitle).toBe("Restore stashed changes?"); expect(en.branchSwitcher.stashRestored).toBe("Stashed changes restored"); diff --git a/packages/app/src/i18n/resources/ar.ts b/packages/app/src/i18n/resources/ar.ts index f15f5408d..f32c150f8 100644 --- a/packages/app/src/i18n/resources/ar.ts +++ b/packages/app/src/i18n/resources/ar.ts @@ -110,7 +110,7 @@ export const ar: TranslationResources = { removeFile: "Remove file attachment", openGithub: "افتح{{kind}}#{{number}}", removeGithub: "إزالة{{kind}}#{{number}}", - browserElement: "العنصر ·{{tag}}", + element: "عنصر", openBrowserElement: "افتح مرفق عنصر المتصفح", removeBrowserElement: "إزالة مرفق عنصر المتصفح", openReview: "فتح مرفق المراجعة", @@ -255,9 +255,12 @@ export const ar: TranslationResources = { imageUnavailable: "الصورة غير متاحة", imagePreviewUnavailable: "معاينة الصورة غير متاحة.", imagePreviewLoadFailed: "غير قادر على تحميل معاينة الصورة.", - reviewOne: "مراجعة · تعليق واحد", - reviewMany: "مراجعة · تعليقات{{count}}", + review: "مراجعة", + commentsOne: "تعليق واحد", + commentsMany: "{{count}} تعليقات", textAttachment: "مرفق النص", + text: "نص", + file: "ملف", }, speak: { header: "تكلم", diff --git a/packages/app/src/i18n/resources/en.ts b/packages/app/src/i18n/resources/en.ts index 96e192047..864f28b44 100644 --- a/packages/app/src/i18n/resources/en.ts +++ b/packages/app/src/i18n/resources/en.ts @@ -108,7 +108,7 @@ export const en = { removeFile: "Remove file attachment", openGithub: "Open {{kind}} #{{number}}", removeGithub: "Remove {{kind}} #{{number}}", - browserElement: "Element · {{tag}}", + element: "Element", openBrowserElement: "Open browser element attachment", removeBrowserElement: "Remove browser element attachment", openReview: "Open review attachment", @@ -254,9 +254,12 @@ export const en = { imageUnavailable: "Image unavailable", imagePreviewUnavailable: "Image preview unavailable.", imagePreviewLoadFailed: "Unable to load image preview.", - reviewOne: "Review · 1 comment", - reviewMany: "Review · {{count}} comments", + review: "Review", + commentsOne: "1 comment", + commentsMany: "{{count}} comments", textAttachment: "Text attachment", + text: "Text", + file: "File", }, speak: { header: "Spoke", diff --git a/packages/app/src/i18n/resources/es.ts b/packages/app/src/i18n/resources/es.ts index 110a7ff24..3c38068e1 100644 --- a/packages/app/src/i18n/resources/es.ts +++ b/packages/app/src/i18n/resources/es.ts @@ -110,7 +110,7 @@ export const es: TranslationResources = { removeFile: "Remove file attachment", openGithub: "Abrir{{kind}}#{{number}}", removeGithub: "Quitar{{kind}}#{{number}}", - browserElement: "Elemento ·{{tag}}", + element: "Elemento", openBrowserElement: "Abrir archivo adjunto de elemento del navegador", removeBrowserElement: "Eliminar el archivo adjunto del elemento del navegador", openReview: "Abrir archivo adjunto de reseña", @@ -258,9 +258,12 @@ export const es: TranslationResources = { imageUnavailable: "Imagen no disponible", imagePreviewUnavailable: "Vista previa de la imagen no disponible.", imagePreviewLoadFailed: "No se puede cargar la vista previa de la imagen.", - reviewOne: "Reseña · 1 comentario", - reviewMany: "Revisión · Comentarios{{count}}", + review: "Revisión", + commentsOne: "1 comentario", + commentsMany: "{{count}} comentarios", textAttachment: "Adjunto de texto", + text: "Texto", + file: "Archivo", }, speak: { header: "Habló", diff --git a/packages/app/src/i18n/resources/fr.ts b/packages/app/src/i18n/resources/fr.ts index 4cf40b011..fce2175ea 100644 --- a/packages/app/src/i18n/resources/fr.ts +++ b/packages/app/src/i18n/resources/fr.ts @@ -111,7 +111,7 @@ export const fr: TranslationResources = { removeFile: "Remove file attachment", openGithub: "Ouvrir{{kind}}#{{number}}", removeGithub: "Supprimer{{kind}}#{{number}}", - browserElement: "Élément ·{{tag}}", + element: "Élément", openBrowserElement: "Ouvrir la pièce jointe de l'élément de navigateur", removeBrowserElement: "Supprimer la pièce jointe d'un élément de navigateur", openReview: "Ouvrir la pièce jointe de l'avis", @@ -258,9 +258,12 @@ export const fr: TranslationResources = { imageUnavailable: "Image indisponible", imagePreviewUnavailable: "Aperçu de l'image indisponible.", imagePreviewLoadFailed: "Impossible de charger l'aperçu de l'image.", - reviewOne: "Avis · 1 commentaire", - reviewMany: "Révision · Commentaires{{count}}", + review: "Revue", + commentsOne: "1 commentaire", + commentsMany: "{{count}} commentaires", textAttachment: "Texte en pièce jointe", + text: "Texte", + file: "Fichier", }, speak: { header: "Rayon", diff --git a/packages/app/src/i18n/resources/ru.ts b/packages/app/src/i18n/resources/ru.ts index 368b2fb92..8422dd4bb 100644 --- a/packages/app/src/i18n/resources/ru.ts +++ b/packages/app/src/i18n/resources/ru.ts @@ -110,7 +110,7 @@ export const ru: TranslationResources = { removeFile: "Remove file attachment", openGithub: "Открыть{{kind}}#{{number}}", removeGithub: "Удалить{{kind}}#{{number}}", - browserElement: "Элемент ·{{tag}}", + element: "Элемент", openBrowserElement: "Открыть вложение элемента браузера", removeBrowserElement: "Удалить вложение элемента браузера", openReview: "Открыть прикрепленный файл с отзывом", @@ -257,9 +257,12 @@ export const ru: TranslationResources = { imageUnavailable: "Изображение недоступно", imagePreviewUnavailable: "Предварительный просмотр изображения недоступен.", imagePreviewLoadFailed: "Невозможно загрузить предварительный просмотр изображения.", - reviewOne: "Отзыв · 1 комментарий", - reviewMany: "Обзор · Комментарии{{count}}", + review: "Ревью", + commentsOne: "1 комментарий", + commentsMany: "Комментарии: {{count}}", textAttachment: "Текстовое вложение", + text: "Текст", + file: "Файл", }, speak: { header: "Говорил", diff --git a/packages/app/src/i18n/resources/zh-CN.ts b/packages/app/src/i18n/resources/zh-CN.ts index 8d140bc3a..6316f25bf 100644 --- a/packages/app/src/i18n/resources/zh-CN.ts +++ b/packages/app/src/i18n/resources/zh-CN.ts @@ -110,7 +110,7 @@ export const zhCN: TranslationResources = { removeFile: "Remove file attachment", openGithub: "打开 {{kind}} #{{number}}", removeGithub: "移除 {{kind}} #{{number}}", - browserElement: "元素 · {{tag}}", + element: "元素", openBrowserElement: "打开浏览器元素附件", removeBrowserElement: "移除浏览器元素附件", openReview: "打开 review 附件", @@ -255,9 +255,12 @@ export const zhCN: TranslationResources = { imageUnavailable: "图片不可用", imagePreviewUnavailable: "图片预览不可用。", imagePreviewLoadFailed: "无法加载图片预览。", - reviewOne: "Review · 1 条评论", - reviewMany: "Review · {{count}} 条评论", + review: "审查", + commentsOne: "1 条评论", + commentsMany: "{{count}} 条评论", textAttachment: "文本附件", + text: "文本", + file: "文件", }, speak: { header: "已朗读",