From 1a5b8b2c94ce1e66bff7e7295b799ae5b0613988 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Thu, 18 Apr 2024 20:37:22 +0100 Subject: [PATCH 01/47] Add layout transitions to the composer photo gallery on iOS (#3609) * add layoutanimation + transition to images * make iOS-only * use useLayoutEffect * make iOS only (again) --- src/view/com/composer/Composer.tsx | 17 ++++++++++++++++- src/view/com/composer/photos/Gallery.tsx | 20 +++++++++++--------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index f8af6ce1b6..f90bdbee2f 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -1,9 +1,17 @@ -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react' +import React, { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react' import { ActivityIndicator, BackHandler, Keyboard, KeyboardAvoidingView, + LayoutAnimation, Platform, Pressable, ScrollView, @@ -122,6 +130,13 @@ export const ComposePost = observer(function ComposePost({ () => new GalleryModel(initImageUris), [initImageUris], ) + + useLayoutEffect(() => { + if (isIOS) { + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) + } + }, [gallery.size]) + const onClose = useCallback(() => { closeComposer() }, [closeComposer]) diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx index 69c8debb02..88b649e5d8 100644 --- a/src/view/com/composer/photos/Gallery.tsx +++ b/src/view/com/composer/photos/Gallery.tsx @@ -1,19 +1,20 @@ import React, {useState} from 'react' import {ImageStyle, Keyboard, LayoutChangeEvent} from 'react-native' -import {GalleryModel} from 'state/models/media/gallery' -import {observer} from 'mobx-react-lite' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {s, colors} from 'lib/styles' import {StyleSheet, TouchableOpacity, View} from 'react-native' import {Image} from 'expo-image' -import {Text} from 'view/com/util/text/Text' -import {Dimensions} from 'lib/media/types' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {observer} from 'mobx-react-lite' + +import {useModalControls} from '#/state/modals' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' +import {Dimensions} from 'lib/media/types' +import {colors, s} from 'lib/styles' import {isNative} from 'platform/detection' +import {GalleryModel} from 'state/models/media/gallery' +import {Text} from 'view/com/util/text/Text' const IMAGE_GAP = 8 @@ -197,6 +198,7 @@ const GalleryInner = observer(function GalleryImpl({ }} accessible={true} accessibilityIgnoresInvertColors + transition={200} /> ))} From 7e72a73bee42eecdd66265377625105fbb896730 Mon Sep 17 00:00:00 2001 From: Minseo Lee Date: Fri, 19 Apr 2024 05:44:14 +0900 Subject: [PATCH 02/47] Update Korean localization (#3551) * Update messages.po * Update messages.po * Update messages.po --- src/locale/locales/ko/messages.po | 190 +++++++++++++----------------- 1 file changed, 85 insertions(+), 105 deletions(-) diff --git a/src/locale/locales/ko/messages.po b/src/locale/locales/ko/messages.po index 1a2878bf77..ca36a2a882 100644 --- a/src/locale/locales/ko/messages.po +++ b/src/locale/locales/ko/messages.po @@ -17,7 +17,7 @@ msgstr "" msgid "(no email)" msgstr "(이메일 없음)" -#: src/components/ProfileHoverCard/index.web.tsx:323 +#: src/components/ProfileHoverCard/index.web.tsx:438 #: src/screens/Profile/Header/Metrics.tsx:44 msgid "{following} following" msgstr "{following} 팔로우 중" @@ -34,11 +34,11 @@ msgstr "<0/>의 멤버" msgid "<0>{0} following" msgstr "<0>{0} 팔로우 중" -#: src/components/ProfileHoverCard/index.web.tsx:314 +#: src/components/ProfileHoverCard/index.web.tsx:429 msgid "<0>{followers} <1>{pluralizedFollowers}" -msgstr "" +msgstr "<0>{followers} <1>팔로워" -#: src/components/ProfileHoverCard/index.web.tsx:326 +#: src/components/ProfileHoverCard/index.web.tsx:441 #: src/screens/Profile/Header/Metrics.tsx:45 msgid "<0>{following} <1>following" msgstr "<0>{following} <1>팔로우 중" @@ -159,14 +159,6 @@ msgstr "대체 텍스트 추가하기" msgid "Add App Password" msgstr "앱 비밀번호 추가" -#: src/view/com/composer/Composer.tsx:467 -msgid "Add link card" -msgstr "링크 카드 추가" - -#: src/view/com/composer/Composer.tsx:472 -msgid "Add link card:" -msgstr "링크 카드 추가:" - #: src/components/dialogs/MutedWords.tsx:157 msgid "Add mute word for configured settings" msgstr "구성 설정에 뮤트 단어 추가" @@ -330,7 +322,7 @@ msgstr "앱 비밀번호 \"{name}\"을(를) 삭제하시겠습니까?" msgid "Are you sure you want to remove {0} from your feeds?" msgstr "피드에서 {0}을(를) 제거하시겠습니까?" -#: src/view/com/composer/Composer.tsx:509 +#: src/view/com/composer/Composer.tsx:489 msgid "Are you sure you'd like to discard this draft?" msgstr "이 초안을 삭제하시겠습니까?" @@ -535,8 +527,8 @@ msgstr "글자, 숫자, 공백, 대시, 밑줄만 포함할 수 있습니다. #: src/components/Prompt.tsx:113 #: src/components/Prompt.tsx:115 #: src/components/TagMenu/index.tsx:268 -#: src/view/com/composer/Composer.tsx:317 -#: src/view/com/composer/Composer.tsx:322 +#: src/view/com/composer/Composer.tsx:321 +#: src/view/com/composer/Composer.tsx:326 #: src/view/com/modals/ChangeEmail.tsx:218 #: src/view/com/modals/ChangeEmail.tsx:220 #: src/view/com/modals/ChangeHandle.tsx:154 @@ -747,7 +739,7 @@ msgstr "이미지 닫기" msgid "Close image viewer" msgstr "이미지 뷰어 닫기" -#: src/view/shell/index.web.tsx:57 +#: src/view/shell/index.web.tsx:61 msgid "Close navigation footer" msgstr "탐색 푸터 닫기" @@ -756,7 +748,7 @@ msgstr "탐색 푸터 닫기" msgid "Close this dialog" msgstr "이 대화 상자 닫기" -#: src/view/shell/index.web.tsx:58 +#: src/view/shell/index.web.tsx:62 msgid "Closes bottom navigation bar" msgstr "하단 탐색 막대를 닫습니다" @@ -764,7 +756,7 @@ msgstr "하단 탐색 막대를 닫습니다" msgid "Closes password update alert" msgstr "비밀번호 변경 알림을 닫습니다" -#: src/view/com/composer/Composer.tsx:319 +#: src/view/com/composer/Composer.tsx:323 msgid "Closes post composer and discards post draft" msgstr "게시물 작성 상자를 닫고 게시물 초안을 삭제합니다" @@ -797,7 +789,7 @@ msgstr "온보딩 완료 후 계정 사용 시작" msgid "Complete the challenge" msgstr "챌린지 완료하기" -#: src/view/com/composer/Composer.tsx:438 +#: src/view/com/composer/Composer.tsx:441 msgid "Compose posts up to {MAX_GRAPHEME_LENGTH} characters in length" msgstr "최대 {MAX_GRAPHEME_LENGTH}자 길이까지 글을 작성할 수 있습니다" @@ -953,7 +945,7 @@ msgstr "클립보드에 복사됨" #: src/components/dialogs/Embed.tsx:134 msgid "Copied!" -msgstr "" +msgstr "복사했습니다!" #: src/view/com/modals/AddAppPasswords.tsx:190 msgid "Copies app password" @@ -970,19 +962,19 @@ msgstr "{0} 복사" #: src/components/dialogs/Embed.tsx:120 #: src/components/dialogs/Embed.tsx:139 msgid "Copy code" -msgstr "" +msgstr "코드 복사" #: src/view/screens/ProfileList.tsx:390 msgid "Copy link to list" msgstr "리스트 링크 복사" -#: src/view/com/util/forms/PostDropdownBtn.tsx:236 -#: src/view/com/util/forms/PostDropdownBtn.tsx:245 +#: src/view/com/util/forms/PostDropdownBtn.tsx:240 +#: src/view/com/util/forms/PostDropdownBtn.tsx:249 msgid "Copy link to post" msgstr "게시물 링크 복사" -#: src/view/com/util/forms/PostDropdownBtn.tsx:228 #: src/view/com/util/forms/PostDropdownBtn.tsx:230 +#: src/view/com/util/forms/PostDropdownBtn.tsx:232 msgid "Copy post text" msgstr "게시물 텍스트 복사" @@ -1015,7 +1007,7 @@ msgstr "계정 만들기" #: src/components/dialogs/Signin.tsx:86 #: src/components/dialogs/Signin.tsx:88 msgid "Create an account" -msgstr "" +msgstr "계정 만들기" #: src/view/com/modals/AddAppPasswords.tsx:227 msgid "Create App Password" @@ -1034,10 +1026,6 @@ msgstr "{0}에 대한 신고 작성하기" msgid "Created {0}" msgstr "{0}에 생성됨" -#: src/view/com/composer/Composer.tsx:469 -msgid "Creates a card with a thumbnail. The card links to {url}" -msgstr "미리보기 이미지가 있는 카드를 만듭니다. 카드가 {url}(으)로 연결됩니다" - #: src/screens/Onboarding/index.tsx:29 msgid "Culture" msgstr "문화" @@ -1085,7 +1073,7 @@ msgstr "검토 디버그" msgid "Debug panel" msgstr "디버그 패널" -#: src/view/com/util/forms/PostDropdownBtn.tsx:341 +#: src/view/com/util/forms/PostDropdownBtn.tsx:345 #: src/view/screens/AppPasswords.tsx:268 #: src/view/screens/ProfileList.tsx:615 msgid "Delete" @@ -1119,8 +1107,8 @@ msgstr "내 계정 삭제" msgid "Delete My Account…" msgstr "내 계정 삭제…" -#: src/view/com/util/forms/PostDropdownBtn.tsx:322 -#: src/view/com/util/forms/PostDropdownBtn.tsx:324 +#: src/view/com/util/forms/PostDropdownBtn.tsx:326 +#: src/view/com/util/forms/PostDropdownBtn.tsx:328 msgid "Delete post" msgstr "게시물 삭제" @@ -1128,7 +1116,7 @@ msgstr "게시물 삭제" msgid "Delete this list?" msgstr "이 리스트를 삭제하시겠습니까?" -#: src/view/com/util/forms/PostDropdownBtn.tsx:336 +#: src/view/com/util/forms/PostDropdownBtn.tsx:340 msgid "Delete this post?" msgstr "이 게시물을 삭제하시겠습니까?" @@ -1147,7 +1135,7 @@ msgstr "삭제된 게시물." msgid "Description" msgstr "설명" -#: src/view/com/composer/Composer.tsx:218 +#: src/view/com/composer/Composer.tsx:222 msgid "Did you want to say anything?" msgstr "하고 싶은 말이 있나요?" @@ -1170,11 +1158,11 @@ msgstr "진동 끄기" msgid "Disabled" msgstr "비활성화됨" -#: src/view/com/composer/Composer.tsx:511 +#: src/view/com/composer/Composer.tsx:491 msgid "Discard" msgstr "삭제" -#: src/view/com/composer/Composer.tsx:508 +#: src/view/com/composer/Composer.tsx:488 msgid "Discard draft?" msgstr "초안 삭제" @@ -1260,7 +1248,7 @@ msgstr "완료{extraText}" msgid "Download CAR file" msgstr "CAR 파일 다운로드" -#: src/view/com/composer/text-input/TextInput.web.tsx:249 +#: src/view/com/composer/text-input/TextInput.web.tsx:270 msgid "Drop to add images" msgstr "드롭하여 이미지 추가" @@ -1400,17 +1388,17 @@ msgstr "이메일:" #: src/components/dialogs/Embed.tsx:112 msgid "Embed HTML code" -msgstr "" +msgstr "임베드 HTML 코드" #: src/components/dialogs/Embed.tsx:97 -#: src/view/com/util/forms/PostDropdownBtn.tsx:253 -#: src/view/com/util/forms/PostDropdownBtn.tsx:255 +#: src/view/com/util/forms/PostDropdownBtn.tsx:257 +#: src/view/com/util/forms/PostDropdownBtn.tsx:259 msgid "Embed post" -msgstr "" +msgstr "게시물 임베드" #: src/components/dialogs/Embed.tsx:101 msgid "Embed this post in your website. Simply copy the following snippet and paste it into the HTML code of your website." -msgstr "" +msgstr "웹사이트에 이 게시물을 임베드하세요. 다음 코드를 복사하여 웹사이트에 HTML 코드로 붙여넣기만 하면 됩니다." #: src/components/dialogs/EmbedConsent.tsx:101 msgid "Enable {0} only" @@ -1744,7 +1732,7 @@ msgstr "선택한 계정을 팔로우하고 다음 단계를 계속 진행합니 msgid "Follow some users to get started. We can recommend you more users based on who you find interesting." msgstr "시작하려면 사용자 몇 명을 팔로우해 보세요. 누구에게 관심이 있는지를 기반으로 더 많은 사용자를 추천해 드릴 수 있습니다." -#: src/view/com/profile/ProfileCard.tsx:219 +#: src/view/com/profile/ProfileCard.tsx:231 msgid "Followed by {0}" msgstr "{0} 님이 팔로우함" @@ -1792,7 +1780,7 @@ msgstr "팔로우 중 피드 설정" msgid "Follows you" msgstr "나를 팔로우함" -#: src/view/com/profile/ProfileCard.tsx:144 +#: src/view/com/profile/ProfileCard.tsx:156 msgid "Follows You" msgstr "나를 팔로우함" @@ -1948,7 +1936,7 @@ msgstr "앱 비밀번호입니다." #: src/lib/moderation/useLabelBehaviorDescription.ts:30 #: src/screens/Onboarding/StepModeration/ModerationOption.tsx:52 #: src/screens/Onboarding/StepModeration/ModerationOption.tsx:76 -#: src/view/com/util/forms/PostDropdownBtn.tsx:350 +#: src/view/com/util/forms/PostDropdownBtn.tsx:354 msgid "Hide" msgstr "숨기기" @@ -1957,8 +1945,8 @@ msgctxt "action" msgid "Hide" msgstr "숨기기" -#: src/view/com/util/forms/PostDropdownBtn.tsx:294 -#: src/view/com/util/forms/PostDropdownBtn.tsx:296 +#: src/view/com/util/forms/PostDropdownBtn.tsx:298 +#: src/view/com/util/forms/PostDropdownBtn.tsx:300 msgid "Hide post" msgstr "게시물 숨기기" @@ -1967,7 +1955,7 @@ msgstr "게시물 숨기기" msgid "Hide the content" msgstr "콘텐츠 숨기기" -#: src/view/com/util/forms/PostDropdownBtn.tsx:347 +#: src/view/com/util/forms/PostDropdownBtn.tsx:351 msgid "Hide this post?" msgstr "이 게시물을 숨기시겠습니까?" @@ -2054,7 +2042,7 @@ msgstr "해당 국가의 법률에 따라 아직 성인이 아닌 경우, 부모 msgid "If you delete this list, you won't be able to recover it." msgstr "이 리스트를 삭제하면 다시 복구할 수 없습니다." -#: src/view/com/util/forms/PostDropdownBtn.tsx:338 +#: src/view/com/util/forms/PostDropdownBtn.tsx:342 msgid "If you remove this post, you won't be able to recover it." msgstr "이 게시물을 삭제하면 다시 복구할 수 없습니다." @@ -2553,13 +2541,13 @@ msgstr "게시물 글 및 태그에서 이 단어 뮤트하기" msgid "Mute this word in tags only" msgstr "태그에서만 이 단어 뮤트하기" -#: src/view/com/util/forms/PostDropdownBtn.tsx:269 -#: src/view/com/util/forms/PostDropdownBtn.tsx:275 +#: src/view/com/util/forms/PostDropdownBtn.tsx:273 +#: src/view/com/util/forms/PostDropdownBtn.tsx:279 msgid "Mute thread" msgstr "스레드 뮤트" -#: src/view/com/util/forms/PostDropdownBtn.tsx:285 -#: src/view/com/util/forms/PostDropdownBtn.tsx:287 +#: src/view/com/util/forms/PostDropdownBtn.tsx:289 +#: src/view/com/util/forms/PostDropdownBtn.tsx:291 msgid "Mute words & tags" msgstr "단어 및 태그 뮤트" @@ -2816,7 +2804,7 @@ msgid "Not right now" msgstr "나중에 하기" #: src/view/com/profile/ProfileMenu.tsx:368 -#: src/view/com/util/forms/PostDropdownBtn.tsx:364 +#: src/view/com/util/forms/PostDropdownBtn.tsx:368 #: src/view/com/util/post-ctrls/PostCtrls.tsx:248 msgid "Note about sharing" msgstr "공유 관련 참고 사항" @@ -2876,7 +2864,7 @@ msgstr "오래된 순" msgid "Onboarding reset" msgstr "온보딩 재설정" -#: src/view/com/composer/Composer.tsx:392 +#: src/view/com/composer/Composer.tsx:396 msgid "One or more images is missing alt text." msgstr "하나 이상의 이미지에 대체 텍스트가 누락되었습니다." @@ -2902,8 +2890,8 @@ msgstr "이런!" msgid "Open" msgstr "공개성" -#: src/view/com/composer/Composer.tsx:491 -#: src/view/com/composer/Composer.tsx:492 +#: src/view/com/composer/Composer.tsx:471 +#: src/view/com/composer/Composer.tsx:472 msgid "Open emoji picker" msgstr "이모티콘 선택기 열기" @@ -3199,7 +3187,7 @@ msgstr "{0} 님이 이 라벨을 잘못 적용했다고 생각하는 이유를 msgid "Please Verify Your Email" msgstr "이메일 인증하기" -#: src/view/com/composer/Composer.tsx:222 +#: src/view/com/composer/Composer.tsx:226 msgid "Please wait for your link card to finish loading" msgstr "링크 카드를 완전히 불러올 때까지 기다려주세요" @@ -3211,8 +3199,8 @@ msgstr "정치" msgid "Porn" msgstr "음란물" -#: src/view/com/composer/Composer.tsx:367 -#: src/view/com/composer/Composer.tsx:375 +#: src/view/com/composer/Composer.tsx:371 +#: src/view/com/composer/Composer.tsx:379 msgctxt "action" msgid "Post" msgstr "게시하기" @@ -3356,11 +3344,11 @@ msgstr "일괄 뮤트하거나 차단할 수 있는 공개적이고 공유 가 msgid "Public, shareable lists which can drive feeds." msgstr "피드를 탐색할 수 있는 공개적이고 공유 가능한 목록입니다." -#: src/view/com/composer/Composer.tsx:352 +#: src/view/com/composer/Composer.tsx:356 msgid "Publish post" msgstr "게시물 게시하기" -#: src/view/com/composer/Composer.tsx:352 +#: src/view/com/composer/Composer.tsx:356 msgid "Publish reply" msgstr "답글 게시하기" @@ -3442,7 +3430,7 @@ msgstr "내 피드에서 제거하시겠습니까?" msgid "Remove image" msgstr "이미지 제거" -#: src/view/com/composer/ExternalEmbed.tsx:70 +#: src/view/com/composer/ExternalEmbed.tsx:86 msgid "Remove image preview" msgstr "이미지 미리보기 제거" @@ -3471,7 +3459,7 @@ msgstr "내 피드에서 제거됨" msgid "Removed from your feeds" msgstr "내 피드에서 제거됨" -#: src/view/com/composer/ExternalEmbed.tsx:71 +#: src/view/com/composer/ExternalEmbed.tsx:87 msgid "Removes default thumbnail from {0}" msgstr "{0}에서 기본 미리보기 이미지를 제거합니다" @@ -3483,7 +3471,7 @@ msgstr "답글" msgid "Replies to this thread are disabled" msgstr "이 스레드에 대한 답글이 비활성화됩니다." -#: src/view/com/composer/Composer.tsx:365 +#: src/view/com/composer/Composer.tsx:369 msgctxt "action" msgid "Reply" msgstr "답글" @@ -3492,11 +3480,11 @@ msgstr "답글" msgid "Reply Filters" msgstr "답글 필터" -#: src/view/com/post/Post.tsx:177 +#: src/view/com/post/Post.tsx:178 #: src/view/com/posts/FeedItem.tsx:285 msgctxt "description" -msgid "Reply to <0/>" -msgstr "<0/> 님에게 보내는 답글" +msgid "Reply to <0><1/>" +msgstr "<0><1/> 님에게 보내는 답글" #: src/view/com/profile/ProfileMenu.tsx:319 #: src/view/com/profile/ProfileMenu.tsx:322 @@ -3516,8 +3504,8 @@ msgstr "피드 신고" msgid "Report List" msgstr "리스트 신고" -#: src/view/com/util/forms/PostDropdownBtn.tsx:312 -#: src/view/com/util/forms/PostDropdownBtn.tsx:314 +#: src/view/com/util/forms/PostDropdownBtn.tsx:316 +#: src/view/com/util/forms/PostDropdownBtn.tsx:318 msgid "Report post" msgstr "게시물 신고" @@ -3566,13 +3554,9 @@ msgstr "재게시한 사용자" msgid "Reposted by {0}" msgstr "{0} 님이 재게시함" -#: src/view/com/posts/FeedItem.tsx:214 -#~ msgid "Reposted by <0/>" -#~ msgstr "<0/> 님이 재게시함" - #: src/view/com/posts/FeedItem.tsx:216 msgid "Reposted by <0><1/>" -msgstr "" +msgstr "<0><1/> 님이 재게시함" #: src/view/com/notifications/FeedItem.tsx:168 msgid "reposted your post" @@ -3794,16 +3778,12 @@ msgstr "이 사용자의 <0>{displayTag} 게시물 보기" #: src/view/com/notifications/FeedItem.tsx:419 #: src/view/com/util/UserAvatar.tsx:381 msgid "See profile" -msgstr "" +msgstr "프로필 보기" #: src/view/screens/SavedFeeds.tsx:164 msgid "See this guide" msgstr "이 가이드" -#: src/view/com/auth/HomeLoggedOutCTA.tsx:40 -#~ msgid "See what's next" -#~ msgstr "See what's next" - #: src/view/com/util/Selector.tsx:106 msgid "Select {item}" msgstr "{item} 선택" @@ -4007,15 +3987,15 @@ msgstr "공유" #: src/view/com/profile/ProfileMenu.tsx:215 #: src/view/com/profile/ProfileMenu.tsx:224 -#: src/view/com/util/forms/PostDropdownBtn.tsx:236 -#: src/view/com/util/forms/PostDropdownBtn.tsx:245 +#: src/view/com/util/forms/PostDropdownBtn.tsx:240 +#: src/view/com/util/forms/PostDropdownBtn.tsx:249 #: src/view/com/util/post-ctrls/PostCtrls.tsx:237 #: src/view/screens/ProfileList.tsx:390 msgid "Share" msgstr "공유" #: src/view/com/profile/ProfileMenu.tsx:373 -#: src/view/com/util/forms/PostDropdownBtn.tsx:369 +#: src/view/com/util/forms/PostDropdownBtn.tsx:373 #: src/view/com/util/post-ctrls/PostCtrls.tsx:253 msgid "Share anyway" msgstr "무시하고 공유" @@ -4065,8 +4045,8 @@ msgid "Show follows similar to {0}" msgstr "{0} 님과 비슷한 팔로우 표시" #: src/view/com/post-thread/PostThreadItem.tsx:501 -#: src/view/com/post/Post.tsx:212 -#: src/view/com/posts/FeedItem.tsx:360 +#: src/view/com/post/Post.tsx:215 +#: src/view/com/posts/FeedItem.tsx:362 msgid "Show More" msgstr "더 보기" @@ -4154,9 +4134,9 @@ msgstr "피드에 {0} 님의 게시물을 표시합니다" #: src/view/shell/bottom-bar/BottomBarWeb.tsx:178 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:179 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:181 -#: src/view/shell/NavSignupCard.tsx:63 -#: src/view/shell/NavSignupCard.tsx:64 -#: src/view/shell/NavSignupCard.tsx:66 +#: src/view/shell/NavSignupCard.tsx:69 +#: src/view/shell/NavSignupCard.tsx:70 +#: src/view/shell/NavSignupCard.tsx:72 msgid "Sign in" msgstr "로그인" @@ -4170,11 +4150,11 @@ msgstr "로그인" #: src/components/dialogs/Signin.tsx:75 msgid "Sign in or create your account to join the conversation!" -msgstr "" +msgstr "대화에 참여하려면 로그인하거나 계정을 만드세요!" #: src/components/dialogs/Signin.tsx:46 msgid "Sign into Bluesky or create a new account" -msgstr "" +msgstr "Bluesky에 로그인하거나 새 계정 만들기" #: src/view/screens/Settings/index.tsx:118 #: src/view/screens/Settings/index.tsx:121 @@ -4187,9 +4167,9 @@ msgstr "로그아웃" #: src/view/shell/bottom-bar/BottomBarWeb.tsx:168 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:169 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:171 -#: src/view/shell/NavSignupCard.tsx:54 -#: src/view/shell/NavSignupCard.tsx:55 -#: src/view/shell/NavSignupCard.tsx:57 +#: src/view/shell/NavSignupCard.tsx:60 +#: src/view/shell/NavSignupCard.tsx:61 +#: src/view/shell/NavSignupCard.tsx:63 msgid "Sign up" msgstr "가입하기" @@ -4633,12 +4613,12 @@ msgstr "이 이름은 이미 사용 중입니다" msgid "This post has been deleted." msgstr "이 게시물은 삭제되었습니다." -#: src/view/com/util/forms/PostDropdownBtn.tsx:366 +#: src/view/com/util/forms/PostDropdownBtn.tsx:370 #: src/view/com/util/post-ctrls/PostCtrls.tsx:250 msgid "This post is only visible to logged-in users. It won't be visible to people who aren't logged in." msgstr "이 게시물은 로그인한 사용자에게만 표시됩니다. 로그인하지 않은 사용자에게는 표시되지 않습니다." -#: src/view/com/util/forms/PostDropdownBtn.tsx:348 +#: src/view/com/util/forms/PostDropdownBtn.tsx:352 msgid "This post will be hidden from feeds." msgstr "이 게시물을 피드에서 숨깁니다." @@ -4730,8 +4710,8 @@ msgstr "변형" #: src/view/com/post-thread/PostThreadItem.tsx:638 #: src/view/com/post-thread/PostThreadItem.tsx:640 -#: src/view/com/util/forms/PostDropdownBtn.tsx:220 #: src/view/com/util/forms/PostDropdownBtn.tsx:222 +#: src/view/com/util/forms/PostDropdownBtn.tsx:224 msgid "Translate" msgstr "번역" @@ -4835,8 +4815,8 @@ msgstr "계정 언뮤트" msgid "Unmute all {displayTag} posts" msgstr "모든 {tag} 게시물 언뮤트" -#: src/view/com/util/forms/PostDropdownBtn.tsx:269 -#: src/view/com/util/forms/PostDropdownBtn.tsx:274 +#: src/view/com/util/forms/PostDropdownBtn.tsx:273 +#: src/view/com/util/forms/PostDropdownBtn.tsx:278 msgid "Unmute thread" msgstr "스레드 언뮤트" @@ -5069,8 +5049,8 @@ msgstr "전체 스레드 보기" msgid "View information about these labels" msgstr "이 라벨에 대한 정보 보기" -#: src/components/ProfileHoverCard/index.web.tsx:264 -#: src/components/ProfileHoverCard/index.web.tsx:293 +#: src/components/ProfileHoverCard/index.web.tsx:379 +#: src/components/ProfileHoverCard/index.web.tsx:408 #: src/view/com/posts/FeedErrorMessage.tsx:166 msgid "View profile" msgstr "프로필 보기" @@ -5186,7 +5166,7 @@ msgstr "어떤 관심사가 있으신가요?" #: src/view/com/auth/SplashScreen.tsx:40 #: src/view/com/auth/SplashScreen.web.tsx:81 -#: src/view/com/composer/Composer.tsx:296 +#: src/view/com/composer/Composer.tsx:300 msgid "What's up?" msgstr "무슨 일이 일어나고 있나요?" @@ -5227,11 +5207,11 @@ msgstr "이 사용자를 검토해야 하는 이유는 무엇인가요?" msgid "Wide" msgstr "가로" -#: src/view/com/composer/Composer.tsx:436 +#: src/view/com/composer/Composer.tsx:439 msgid "Write post" msgstr "게시물 작성" -#: src/view/com/composer/Composer.tsx:295 +#: src/view/com/composer/Composer.tsx:299 #: src/view/com/composer/Prompt.tsx:37 msgid "Write your reply" msgstr "답글 작성하기" @@ -5460,7 +5440,7 @@ msgstr "뮤트한 단어" msgid "Your password has been changed successfully!" msgstr "비밀번호를 성공적으로 변경했습니다." -#: src/view/com/composer/Composer.tsx:284 +#: src/view/com/composer/Composer.tsx:288 msgid "Your post has been published" msgstr "게시물을 게시했습니다" @@ -5474,7 +5454,7 @@ msgstr "게시물, 좋아요, 차단 목록은 공개됩니다. 뮤트 목록은 msgid "Your profile" msgstr "내 프로필" -#: src/view/com/composer/Composer.tsx:283 +#: src/view/com/composer/Composer.tsx:287 msgid "Your reply has been published" msgstr "내 답글을 게시했습니다" From 91190e11e276248453306da7962f345282f88f32 Mon Sep 17 00:00:00 2001 From: Dovgonosyk <159719660+Dovgonosyk@users.noreply.github.com> Date: Thu, 18 Apr 2024 23:53:54 +0300 Subject: [PATCH 03/47] Update Ukrainian translation (#3552) Co-authored-by: Ukrainians --- src/locale/locales/uk/messages.po | 515 +++++++++++++++--------------- 1 file changed, 258 insertions(+), 257 deletions(-) diff --git a/src/locale/locales/uk/messages.po b/src/locale/locales/uk/messages.po index 935d1f799d..dcdacfa06b 100644 --- a/src/locale/locales/uk/messages.po +++ b/src/locale/locales/uk/messages.po @@ -8,7 +8,7 @@ msgstr "" "Language: uk\n" "Project-Id-Version: bsky-app-ua\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2024-04-13 13:57\n" +"PO-Revision-Date: 2024-04-14 10:31\n" "Last-Translator: \n" "Language-Team: Ukrainian\n" "Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" @@ -37,11 +37,11 @@ msgstr "<0/> учасників" #: src/view/shell/Drawer.tsx:97 msgid "<0>{0} following" -msgstr "" +msgstr "<0>{0} підписок" #: src/components/ProfileHoverCard/index.web.tsx:314 msgid "<0>{followers} <1>{pluralizedFollowers}" -msgstr "" +msgstr "<0>{followers} <1>{pluralizedFollowers}" #: src/components/ProfileHoverCard/index.web.tsx:326 #: src/screens/Profile/Header/Metrics.tsx:45 @@ -94,7 +94,7 @@ msgstr "Обліковий запис заблоковано" #: src/view/com/profile/ProfileMenu.tsx:153 msgid "Account followed" -msgstr "" +msgstr "Ви підписалися на обліковий запис" #: src/view/com/profile/ProfileMenu.tsx:113 msgid "Account muted" @@ -124,7 +124,7 @@ msgstr "Обліковий запис розблоковано" #: src/view/com/profile/ProfileMenu.tsx:166 msgid "Account unfollowed" -msgstr "" +msgstr "Ви відписалися від облікового запису" #: src/view/com/profile/ProfileMenu.tsx:102 msgid "Account unmuted" @@ -260,7 +260,7 @@ msgstr "Було надіслано лист на вашу попередню а #: src/lib/moderation/useReportOptions.ts:26 msgid "An issue not included in these options" -msgstr "" +msgstr "Проблема не включена до цих варіантів" #: src/components/hooks/useFollowMethods.ts:35 #: src/components/hooks/useFollowMethods.ts:50 @@ -313,15 +313,15 @@ msgstr "Паролі для застосунків" #: src/components/moderation/LabelsOnMeDialog.tsx:133 #: src/components/moderation/LabelsOnMeDialog.tsx:136 msgid "Appeal" -msgstr "" +msgstr "Звернення" #: src/components/moderation/LabelsOnMeDialog.tsx:201 msgid "Appeal \"{0}\" label" -msgstr "" +msgstr "Оскаржити мітку \"{0}\"" #: src/components/moderation/LabelsOnMeDialog.tsx:192 msgid "Appeal submitted." -msgstr "" +msgstr "Звернення надіслано." #: src/view/screens/Settings/index.tsx:436 msgid "Appearance" @@ -333,7 +333,7 @@ msgstr "Ви дійсно хочете видалити пароль для за #: src/view/com/feeds/FeedSourceCard.tsx:280 msgid "Are you sure you want to remove {0} from your feeds?" -msgstr "" +msgstr "Ви впевнені, що бажаєте видалити {0} зі стрічки?" #: src/view/com/composer/Composer.tsx:509 msgid "Are you sure you'd like to discard this draft?" @@ -446,7 +446,7 @@ msgstr "Заблокований пост." #: src/screens/Profile/Sections/Labels.tsx:163 msgid "Blocking does not prevent this labeler from placing labels on your account." -msgstr "" +msgstr "Блокування не заважає цьому маркувальнику додавати мітку до вашого облікового запису." #: src/view/screens/ProfileList.tsx:633 msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." @@ -454,7 +454,7 @@ msgstr "Блокування - це відкрита інформація. За #: src/view/com/profile/ProfileMenu.tsx:353 msgid "Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you." -msgstr "" +msgstr "Блокування не завадить додавання міток до вашого облікового запису, але це зупинить можливість цього облікового запису від коментування ваших постів чи взаємодії з вами." #: src/view/com/auth/SplashScreen.web.tsx:149 msgid "Blog" @@ -514,7 +514,7 @@ msgstr "від {0}" #: src/components/LabelingServiceCard/index.tsx:57 msgid "By {0}" -msgstr "" +msgstr "Від {0}" #: src/view/com/profile/ProfileSubpageHeader.tsx:161 msgid "by <0/>" @@ -522,7 +522,7 @@ msgstr "від <0/>" #: src/screens/Signup/StepInfo/Policies.tsx:74 msgid "By creating an account you agree to the {els}." -msgstr "" +msgstr "Створюючи обліковий запис, ви даєте згоду з {els}." #: src/view/com/profile/ProfileSubpageHeader.tsx:159 msgid "by you" @@ -820,7 +820,7 @@ msgstr "Налаштувати фільтрування вмісту для ка #: src/components/moderation/LabelPreference.tsx:244 msgid "Configured in <0>moderation settings." -msgstr "" +msgstr "Налаштовано <0>у налаштуваннях модерації." #: src/components/Prompt.tsx:153 #: src/components/Prompt.tsx:156 @@ -870,7 +870,7 @@ msgstr "Служба підтримки" #: src/components/moderation/LabelsOnMe.tsx:42 msgid "content" -msgstr "" +msgstr "вміст" #: src/lib/moderation/useGlobalLabelStrings.ts:18 msgid "Content Blocked" @@ -878,7 +878,7 @@ msgstr "Заблокований вміст" #: src/screens/Moderation/index.tsx:285 msgid "Content filters" -msgstr "Відсів вмісту" +msgstr "Фільтри контенту" #: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:74 #: src/view/screens/LanguageSettings.tsx:278 @@ -903,7 +903,7 @@ msgstr "Попередження про вміст" #: src/components/Menu/index.web.tsx:84 msgid "Context menu backdrop, click to close the menu." -msgstr "" +msgstr "Тло контекстного меню натисніть, щоб закрити меню." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:161 #: src/screens/Onboarding/StepFollowingFeed.tsx:154 @@ -918,7 +918,7 @@ msgstr "Далі" #: src/components/AccountList.tsx:108 msgid "Continue as {0} (currently signed in)" -msgstr "" +msgstr "Продовжити як {0} (поточний користувач)" #: src/screens/Onboarding/StepFollowingFeed.tsx:151 #: src/screens/Onboarding/StepInterests/index.tsx:249 @@ -958,7 +958,7 @@ msgstr "Скопійовано" #: src/components/dialogs/Embed.tsx:134 msgid "Copied!" -msgstr "" +msgstr "Скопійовано!" #: src/view/com/modals/AddAppPasswords.tsx:190 msgid "Copies app password" @@ -970,12 +970,12 @@ msgstr "Скопіювати" #: src/view/com/modals/ChangeHandle.tsx:480 msgid "Copy {0}" -msgstr "" +msgstr "Копіювати {0}" #: src/components/dialogs/Embed.tsx:120 #: src/components/dialogs/Embed.tsx:139 msgid "Copy code" -msgstr "" +msgstr "Скопіювати код" #: src/view/screens/ProfileList.tsx:390 msgid "Copy link to list" @@ -1020,7 +1020,7 @@ msgstr "Створити обліковий запис" #: src/components/dialogs/Signin.tsx:86 #: src/components/dialogs/Signin.tsx:88 msgid "Create an account" -msgstr "" +msgstr "Створити обліковий запис" #: src/view/com/modals/AddAppPasswords.tsx:227 msgid "Create App Password" @@ -1033,7 +1033,7 @@ msgstr "Створити новий обліковий запис" #: src/components/ReportDialog/SelectReportOptionView.tsx:94 msgid "Create report for {0}" -msgstr "" +msgstr "Створити звіт для {0}" #: src/view/screens/AppPasswords.tsx:246 msgid "Created {0}" @@ -1084,7 +1084,7 @@ msgstr "Дата народження" #: src/view/screens/Settings/index.tsx:805 msgid "Debug Moderation" -msgstr "" +msgstr "Налагодження модерації" #: src/view/screens/Debug.tsx:83 msgid "Debug panel" @@ -1094,7 +1094,7 @@ msgstr "Панель налагодження" #: src/view/screens/AppPasswords.tsx:268 #: src/view/screens/ProfileList.tsx:615 msgid "Delete" -msgstr "Видаліть" +msgstr "Видалити" #: src/view/screens/Settings/index.tsx:760 msgid "Delete account" @@ -1110,7 +1110,7 @@ msgstr "Видалити пароль для застосунку" #: src/view/screens/AppPasswords.tsx:263 msgid "Delete app password?" -msgstr "Видалити пароль застосунку?" +msgstr "Видалити пароль для застосунку?" #: src/view/screens/ProfileList.tsx:417 msgid "Delete List" @@ -1162,11 +1162,11 @@ msgstr "Тьмяний" #: src/view/screens/Settings/index.tsx:697 msgid "Disable haptics" -msgstr "" +msgstr "Вимкнути тактильні ефекти" #: src/view/screens/Settings/index.tsx:697 msgid "Disable vibrations" -msgstr "" +msgstr "Вимкнути вібрацію" #: src/lib/moderation/useLabelBehaviorDescription.ts:32 #: src/lib/moderation/useLabelBehaviorDescription.ts:42 @@ -1219,7 +1219,7 @@ msgstr "Не починається або закінчується дефісо #: src/view/com/modals/ChangeHandle.tsx:481 msgid "Domain Value" -msgstr "" +msgstr "Значення домену" #: src/view/com/modals/ChangeHandle.tsx:488 msgid "Domain verified!" @@ -1291,7 +1291,7 @@ msgstr "напр. Художниця, собачниця та завзята ч #: src/lib/moderation/useGlobalLabelStrings.ts:43 msgid "E.g. artistic nudes." -msgstr "Напр. художня голота." +msgstr "Напр. художня оголеність." #: src/view/com/modals/CreateOrEditList.tsx:284 msgid "e.g. Great Posters" @@ -1405,17 +1405,17 @@ msgstr "Ел. адреса:" #: src/components/dialogs/Embed.tsx:112 msgid "Embed HTML code" -msgstr "" +msgstr "Вбудований HTML код" #: src/components/dialogs/Embed.tsx:97 #: src/view/com/util/forms/PostDropdownBtn.tsx:253 #: src/view/com/util/forms/PostDropdownBtn.tsx:255 msgid "Embed post" -msgstr "" +msgstr "Вбудований пост" #: src/components/dialogs/Embed.tsx:101 msgid "Embed this post in your website. Simply copy the following snippet and paste it into the HTML code of your website." -msgstr "" +msgstr "Вставте цей пост у Ваш сайт. Просто скопіюйте цей скрипт і вставте його в HTML код вашого сайту." #: src/components/dialogs/EmbedConsent.tsx:101 msgid "Enable {0} only" @@ -1437,7 +1437,7 @@ msgstr "Увімкнути вміст для дорослих у ваших ст #: src/components/dialogs/EmbedConsent.tsx:82 #: src/components/dialogs/EmbedConsent.tsx:89 msgid "Enable external media" -msgstr "" +msgstr "Увімкнути зовнішні медіа" #: src/view/screens/PreferencesExternalEmbeds.tsx:75 msgid "Enable media players for" @@ -1449,7 +1449,7 @@ msgstr "Увімкніть цей параметр, щоб бачити відп #: src/components/dialogs/EmbedConsent.tsx:94 msgid "Enable this source only" -msgstr "" +msgstr "Увімкнути лише джерело" #: src/screens/Moderation/index.tsx:339 msgid "Enabled" @@ -1523,11 +1523,11 @@ msgstr "Усі" #: src/lib/moderation/useReportOptions.ts:66 msgid "Excessive mentions or replies" -msgstr "" +msgstr "Спам; надмірні згадки або відповіді" #: src/view/com/modals/DeleteAccount.tsx:230 msgid "Exits account deletion process" -msgstr "" +msgstr "Виходить з процесу видалення облікового запису" #: src/view/com/modals/ChangeHandle.tsx:151 msgid "Exits handle change process" @@ -1535,7 +1535,7 @@ msgstr "Вихід з процесу зміни псевдоніму корис #: src/view/com/modals/crop-image/CropImage.web.tsx:136 msgid "Exits image cropping process" -msgstr "" +msgstr "Виходить з процесу обрізання зображень" #: src/view/com/lightbox/Lightbox.web.tsx:130 msgid "Exits image view" @@ -1557,7 +1557,7 @@ msgstr "Розгорнути або згорнути весь пост, на я #: src/lib/moderation/useGlobalLabelStrings.ts:47 msgid "Explicit or potentially disturbing media." -msgstr "Відвертий чи потенційно тривожний засіб." +msgstr "Відверто або потенційно проблемний вміст." #: src/lib/moderation/useGlobalLabelStrings.ts:35 msgid "Explicit sexual images." @@ -1612,7 +1612,7 @@ msgstr "Не вдалося завантажити рекомендації ст #: src/view/com/lightbox/Lightbox.tsx:83 msgid "Failed to save image: {0}" -msgstr "Провалено збереження зображення: {0}" +msgstr "Не вдалося зберегти зображення: {0}" #: src/Navigation.tsx:196 msgid "Feed" @@ -1660,7 +1660,7 @@ msgstr "Вміст файлу" #: src/lib/moderation/useLabelBehaviorDescription.ts:66 msgid "Filter from feeds" -msgstr "Відсів від стрічки" +msgstr "Фільтрувати зі стрічок" #: src/screens/Onboarding/StepFinished.tsx:155 msgid "Finalizing" @@ -1686,7 +1686,7 @@ msgstr "Пошук подібних облікових записів..." #: src/view/screens/PreferencesFollowingFeed.tsx:111 msgid "Fine-tune the content you see on your Following feed." -msgstr "Оберіть, що хочете бачити в своїй стрічці підписок." +msgstr "Оберіть, що ви хочете бачити у своїй стрічці підписок." #: src/view/screens/PreferencesThreads.tsx:60 msgid "Fine-tune the discussion threads." @@ -1731,7 +1731,7 @@ msgstr "Підписатися на {0}" #: src/view/com/profile/ProfileMenu.tsx:242 #: src/view/com/profile/ProfileMenu.tsx:253 msgid "Follow Account" -msgstr "" +msgstr "Підписатися на обліковий запис" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:187 msgid "Follow All" @@ -1763,7 +1763,7 @@ msgstr "Тільки ваші підписки" #: src/view/com/notifications/FeedItem.tsx:172 msgid "followed you" -msgstr "підписалися на вас" +msgstr "підписка на вас" #: src/view/com/profile/ProfileFollowers.tsx:104 #: src/view/screens/ProfileFollowers.tsx:25 @@ -1824,11 +1824,11 @@ msgstr "Забули пароль?" #: src/screens/Login/LoginForm.tsx:212 msgid "Forgot?" -msgstr "" +msgstr "Забули пароль?" #: src/lib/moderation/useReportOptions.ts:52 msgid "Frequently Posts Unwanted Content" -msgstr "" +msgstr "Часто публікує неприйнятний контент" #: src/screens/Hashtag.tsx:109 #: src/screens/Hashtag.tsx:149 @@ -1851,7 +1851,7 @@ msgstr "Почати" #: src/lib/moderation/useReportOptions.ts:37 msgid "Glaring violations of law or terms of service" -msgstr "Грубі порушення закону чи умов послугу" +msgstr "Грубі порушення закону чи умов використання" #: src/components/moderation/ScreenHider.tsx:151 #: src/components/moderation/ScreenHider.tsx:160 @@ -1879,15 +1879,15 @@ msgstr "Назад" #: src/screens/Onboarding/Layout.tsx:191 #: src/screens/Signup/index.tsx:174 msgid "Go back to previous step" -msgstr "Назад до попереднього кроку" +msgstr "Повернутися до попереднього кроку" #: src/view/screens/NotFound.tsx:55 msgid "Go home" -msgstr "На головну" +msgstr "Повернутися на головну" #: src/view/screens/NotFound.tsx:54 msgid "Go Home" -msgstr "На головну" +msgstr "Повернутися на головну" #: src/view/screens/Search/Search.tsx:896 #: src/view/shell/desktop/Search.tsx:263 @@ -1913,15 +1913,15 @@ msgstr "Домагання, тролінг або нетерпимість" #: src/Navigation.tsx:282 msgid "Hashtag" -msgstr "Мітка" +msgstr "Хештег" #: src/components/RichText.tsx:197 msgid "Hashtag: #{tag}" -msgstr "Мітка: #{tag}" +msgstr "Хештег: #{tag}" #: src/screens/Signup/index.tsx:221 msgid "Having trouble?" -msgstr "Маєте проблеми?" +msgstr "Виникли проблеми?" #: src/view/shell/desktop/RightNav.tsx:90 #: src/view/shell/Drawer.tsx:330 @@ -1930,19 +1930,19 @@ msgstr "Довідка" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:140 msgid "Here are some accounts for you to follow" -msgstr "Ось деякі облікові записи до ваших підписок" +msgstr "Ось деякі облікові записи, на які ви підписані" #: src/screens/Onboarding/StepTopicalFeeds.tsx:89 msgid "Here are some popular topical feeds. You can choose to follow as many as you like." -msgstr "Ось декілька популярних стрічок за темами. Можете підписуватися скільки завгодно." +msgstr "Ось декілька популярних тематичних стрічок. Ви можете підписатися на скільки забажаєте з них." #: src/screens/Onboarding/StepTopicalFeeds.tsx:84 msgid "Here are some topical feeds based on your interests: {interestsText}. You can choose to follow as many as you like." -msgstr "Ось декілька стрічок за темами на основі ваших інтересів: {interestsText}. Можете підписуватися скільки завгодно." +msgstr "Ось декілька тематичних стрічок на основі ваших інтересів: {interestsText}. Ви можете підписатися на скільки забажаєте з них." #: src/view/com/modals/AddAppPasswords.tsx:154 msgid "Here is your app password." -msgstr "Ось ваш пароль для застосунків." +msgstr "Це ваш пароль для застосунків." #: src/components/moderation/ContentHider.tsx:115 #: src/components/moderation/LabelPreference.tsx:134 @@ -1960,17 +1960,17 @@ msgstr "Приховати" #: src/view/com/notifications/FeedItem.tsx:331 msgctxt "action" msgid "Hide" -msgstr "Сховай" +msgstr "Сховати" #: src/view/com/util/forms/PostDropdownBtn.tsx:294 #: src/view/com/util/forms/PostDropdownBtn.tsx:296 msgid "Hide post" -msgstr "Сховай пост" +msgstr "Сховати пост" #: src/components/moderation/ContentHider.tsx:67 #: src/components/moderation/PostHider.tsx:64 msgid "Hide the content" -msgstr "Сховай вміст" +msgstr "Приховати вміст" #: src/view/com/util/forms/PostDropdownBtn.tsx:347 msgid "Hide this post?" @@ -1978,7 +1978,7 @@ msgstr "Сховати цей пост?" #: src/view/com/notifications/FeedItem.tsx:321 msgid "Hide user list" -msgstr "Сховай список користувачів" +msgstr "Сховати список користувачів" #: src/view/com/posts/FeedErrorMessage.tsx:111 msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." @@ -2002,11 +2002,11 @@ msgstr "Хм, ми не можемо знайти цю стрічку. Можл #: src/screens/Moderation/index.tsx:59 msgid "Hmmmm, it seems we're having trouble loading this data. See below for more details. If this issue persists, please contact us." -msgstr "" +msgstr "Здається, у нас виникли проблеми з завантаженням цих даних. Перегляньте деталі нижче. Якщо проблема не зникне, будь ласка, зв'яжіться з нами." #: src/screens/Profile/ErrorState.tsx:31 msgid "Hmmmm, we couldn't load that moderation service." -msgstr "" +msgstr "Хм, ми не змогли завантажити цей сервіс модерації." #: src/Navigation.tsx:446 #: src/view/shell/bottom-bar/BottomBar.tsx:148 @@ -2014,7 +2014,7 @@ msgstr "" #: src/view/shell/Drawer.tsx:407 #: src/view/shell/Drawer.tsx:408 msgid "Home" -msgstr "Домівка" +msgstr "Головна" #: src/view/com/modals/ChangeHandle.tsx:420 msgid "Host:" @@ -2029,19 +2029,19 @@ msgstr "Хостинг-провайдер" #: src/view/com/modals/InAppBrowserConsent.tsx:44 msgid "How should we open this link?" -msgstr "Як хочете відкрити цю ланку?" +msgstr "Як ви хочете відкрити це посилання?" #: src/view/com/modals/VerifyEmail.tsx:214 msgid "I have a code" -msgstr "Маю код" +msgstr "У мене є код" #: src/view/com/modals/VerifyEmail.tsx:216 msgid "I have a confirmation code" -msgstr "Маю код підтвердження" +msgstr "У мене є код підтвердження" #: src/view/com/modals/ChangeHandle.tsx:284 msgid "I have my own domain" -msgstr "Маю свій домен" +msgstr "Я маю власний домен" #: src/view/com/lightbox/Lightbox.web.tsx:185 msgid "If alt text is long, toggles alt text expanded state" @@ -2053,15 +2053,15 @@ msgstr "Якщо не вибрано жодного варіанту - підх #: src/screens/Signup/StepInfo/Policies.tsx:83 msgid "If you are not yet an adult according to the laws of your country, your parent or legal guardian must read these Terms on your behalf." -msgstr "" +msgstr "Якщо ви ще не досягли повноліття відповідно до законів вашої країни, ваш батьківський або юридичний опікун повинен прочитати ці Умови від вашого імені." #: src/view/screens/ProfileList.tsx:612 msgid "If you delete this list, you won't be able to recover it." -msgstr "Якщо видалите цей список, то його не відновите." +msgstr "Якщо ви видалите цей список, ви не зможете його відновити." #: src/view/com/util/forms/PostDropdownBtn.tsx:338 msgid "If you remove this post, you won't be able to recover it." -msgstr "Якщо видалите цей пост, то його не відновите." +msgstr "Якщо ви видалите цей пост, ви не зможете його відновити." #: src/view/com/modals/ChangePassword.tsx:148 msgid "If you want to change your password, we will send you a code to verify that this is your account." @@ -2069,7 +2069,7 @@ msgstr "Якщо ви хочете змінити пароль, ми надіш #: src/lib/moderation/useReportOptions.ts:36 msgid "Illegal and Urgent" -msgstr "" +msgstr "Незаконний та невідкладний" #: src/view/com/util/images/Gallery.tsx:38 msgid "Image" @@ -2081,7 +2081,7 @@ msgstr "Опис зображення" #: src/lib/moderation/useReportOptions.ts:47 msgid "Impersonation or false claims about identity or affiliation" -msgstr "Самозванство чи брехливі заяви про особу чи партнера" +msgstr "Видавання себе за іншу особу або неправдиві твердження про особу чи приналежність" #: src/screens/Login/SetNewPasswordForm.tsx:127 msgid "Input code sent to your email for password reset" @@ -2117,7 +2117,7 @@ msgstr "Введіть ваш пароль" #: src/view/com/modals/ChangeHandle.tsx:389 msgid "Input your preferred hosting provider" -msgstr "" +msgstr "Введіть бажаного хостинг-провайдера" #: src/screens/Signup/StepHandle.tsx:63 msgid "Input your user handle" @@ -2165,35 +2165,35 @@ msgstr "Журналістика" #: src/components/moderation/LabelsOnMe.tsx:59 msgid "label has been placed on this {labelTarget}" -msgstr "" +msgstr "мітка була розміщена на цьому {labelTarget}" #: src/components/moderation/ContentHider.tsx:144 msgid "Labeled by {0}." -msgstr "" +msgstr "Помічений {0}." #: src/components/moderation/ContentHider.tsx:142 msgid "Labeled by the author." -msgstr "" +msgstr "Мітку додано автором." #: src/view/screens/Profile.tsx:193 msgid "Labels" -msgstr "" +msgstr "Мітки" #: src/screens/Profile/Sections/Labels.tsx:153 msgid "Labels are annotations on users and content. They can be used to hide, warn, and categorize the network." -msgstr "" +msgstr "Мітки є анотаціями для користувачів і контенту. Вони можуть використовуватися для приховування, попередження та категоризації мережі." #: src/components/moderation/LabelsOnMe.tsx:61 msgid "labels have been placed on this {labelTarget}" -msgstr "" +msgstr "мітка була розміщена на {labelTarget}" #: src/components/moderation/LabelsOnMeDialog.tsx:62 msgid "Labels on your account" -msgstr "" +msgstr "Мітки на вашому обліковому записі" #: src/components/moderation/LabelsOnMeDialog.tsx:64 msgid "Labels on your content" -msgstr "" +msgstr "Мітки на вашому контенті" #: src/view/com/composer/select-language/SelectLangBtn.tsx:104 msgid "Language selection" @@ -2214,7 +2214,7 @@ msgstr "Мови" #: src/view/screens/Search/Search.tsx:437 msgid "Latest" -msgstr "" +msgstr "Нещодавні" #: src/components/moderation/ScreenHider.tsx:136 msgid "Learn More" @@ -2223,7 +2223,7 @@ msgstr "Дізнатися більше" #: src/components/moderation/ContentHider.tsx:65 #: src/components/moderation/ContentHider.tsx:128 msgid "Learn more about the moderation applied to this content." -msgstr "" +msgstr "Дізнайтеся більше про те, яка модерація застосована до цього вмісту." #: src/components/moderation/PostHider.tsx:85 #: src/components/moderation/ScreenHider.tsx:125 @@ -2236,7 +2236,7 @@ msgstr "Дізнатися більше про те, що є публічним #: src/components/moderation/ContentHider.tsx:152 msgid "Learn more." -msgstr "" +msgstr "Дізнатися більше." #: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:82 msgid "Leave them all unchecked to see any language." @@ -2286,7 +2286,7 @@ msgstr "Сподобалося" #: src/view/screens/PostLikedBy.tsx:27 #: src/view/screens/ProfileFeedLikedBy.tsx:27 msgid "Liked By" -msgstr "Уподобано" +msgstr "Сподобався користувачу" #: src/view/com/feeds/FeedSourceCard.tsx:268 msgid "Liked by {0} {1}" @@ -2294,7 +2294,7 @@ msgstr "Вподобано {0} {1}" #: src/components/LabelingServiceCard/index.tsx:72 msgid "Liked by {count} {0}" -msgstr "" +msgstr "Вподобано {count} {0}" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:284 #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:298 @@ -2308,7 +2308,7 @@ msgstr "вподобав(-ла) вашу стрічку" #: src/view/com/notifications/FeedItem.tsx:161 msgid "liked your post" -msgstr "уподобали ваш пост" +msgstr "сподобався ваш пост" #: src/view/screens/Profile.tsx:198 msgid "Likes" @@ -2432,7 +2432,7 @@ msgstr "Повідомлення від сервера: {0}" #: src/lib/moderation/useReportOptions.ts:45 msgid "Misleading Account" -msgstr "" +msgstr "Оманливий обліковий запис" #: src/Navigation.tsx:119 #: src/screens/Moderation/index.tsx:104 @@ -2445,7 +2445,7 @@ msgstr "Модерація" #: src/components/moderation/ModerationDetailsDialog.tsx:112 msgid "Moderation details" -msgstr "" +msgstr "Деталі модерації" #: src/view/com/lists/ListCard.tsx:93 #: src/view/com/modals/UserAddRemoveLists.tsx:206 @@ -2485,11 +2485,11 @@ msgstr "Налаштування модерації" #: src/Navigation.tsx:216 msgid "Moderation states" -msgstr "" +msgstr "Статус модерації" #: src/screens/Moderation/index.tsx:215 msgid "Moderation tools" -msgstr "" +msgstr "Інструменти модерації" #: src/components/moderation/ModerationDetailsDialog.tsx:48 #: src/lib/moderation/useModerationCauseDescription.ts:40 @@ -2498,7 +2498,7 @@ msgstr "Модератор вирішив встановити загальне #: src/view/com/post-thread/PostThreadItem.tsx:535 msgid "More" -msgstr "" +msgstr "Більше" #: src/view/shell/desktop/Feeds.tsx:65 msgid "More feeds" @@ -2535,11 +2535,11 @@ msgstr "Ігнорувати всі пости {displayTag}" #: src/components/dialogs/MutedWords.tsx:148 msgid "Mute in tags only" -msgstr "Лише в тегах" +msgstr "Ігнорувати лише в тегах" #: src/components/dialogs/MutedWords.tsx:133 msgid "Mute in text & tags" -msgstr "В тексті і тегах" +msgstr "Ігнорувати в тексті та тегах" #: src/view/screens/ProfileList.tsx:463 #: src/view/screens/ProfileList.tsx:626 @@ -2587,7 +2587,7 @@ msgstr "Ігноровані облікові записи автоматичн #: src/lib/moderation/useModerationCauseDescription.ts:85 msgid "Muted by \"{0}\"" -msgstr "" +msgstr "Проігноровано списком \"{0}\"" #: src/screens/Moderation/index.tsx:231 msgid "Muted words & tags" @@ -2612,7 +2612,7 @@ msgstr "Мій профіль" #: src/view/screens/Settings/index.tsx:547 msgid "My saved feeds" -msgstr "" +msgstr "Мої збережені стрічки" #: src/view/screens/Settings/index.tsx:553 msgid "My Saved Feeds" @@ -2631,7 +2631,7 @@ msgstr "Необхідна назва" #: src/lib/moderation/useReportOptions.ts:78 #: src/lib/moderation/useReportOptions.ts:86 msgid "Name or Description Violates Community Standards" -msgstr "" +msgstr "Ім'я чи Опис порушують стандарти спільноти" #: src/screens/Onboarding/index.tsx:25 msgid "Nature" @@ -2649,7 +2649,7 @@ msgstr "Переходить до вашого профілю" #: src/components/ReportDialog/SelectReportOptionView.tsx:123 msgid "Need to report a copyright violation?" -msgstr "" +msgstr "Хочете повідомити про порушення авторських прав?" #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:72 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:74 @@ -2662,7 +2662,7 @@ msgstr "Ніколи не втрачайте доступ до ваших під #: src/view/com/modals/ChangeHandle.tsx:519 msgid "Nevermind, create a handle for me" -msgstr "Неважливо, створіть псевдо мені" +msgstr "Неважливо, створіть для мене псевдонім" #: src/view/screens/Lists.tsx:76 msgctxt "action" @@ -2755,7 +2755,7 @@ msgstr "Опис відсутній" #: src/view/com/modals/ChangeHandle.tsx:405 msgid "No DNS Panel" -msgstr "" +msgstr "Немає панелі DNS" #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:118 msgid "No longer following {0}" @@ -2800,11 +2800,11 @@ msgstr "Ніхто" #: src/components/LikedByList.tsx:79 #: src/components/LikesDialog.tsx:99 msgid "Nobody has liked this yet. Maybe you should be the first!" -msgstr "" +msgstr "Поки що це нікому не сподобалося. Можливо, ви повинні бути першим!" #: src/lib/moderation/useGlobalLabelStrings.ts:42 msgid "Non-sexual Nudity" -msgstr "" +msgstr "Несексуальна оголеність" #: src/view/com/modals/SelfLabel.tsx:135 msgid "Not Applicable." @@ -2824,7 +2824,7 @@ msgstr "Пізніше" #: src/view/com/util/forms/PostDropdownBtn.tsx:364 #: src/view/com/util/post-ctrls/PostCtrls.tsx:248 msgid "Note about sharing" -msgstr "" +msgstr "Примітка щодо поширення" #: src/screens/Moderation/index.tsx:540 msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites." @@ -2850,11 +2850,11 @@ msgstr "Нагота чи матеріали для дорослих не поз #: src/screens/Signup/index.tsx:143 msgid "of" -msgstr "" +msgstr "з" #: src/lib/moderation/useLabelBehaviorDescription.ts:11 msgid "Off" -msgstr "Вимк." +msgstr "Вимкнено" #: src/view/com/util/ErrorBoundary.tsx:49 msgid "Oh no!" @@ -2914,7 +2914,7 @@ msgstr "Емоджі" #: src/view/screens/ProfileFeed.tsx:311 msgid "Open feed options menu" -msgstr "" +msgstr "Відкрити меню налаштувань стрічки" #: src/view/screens/Settings/index.tsx:685 msgid "Open links with in-app browser" @@ -2922,7 +2922,7 @@ msgstr "Вбудований браузер" #: src/screens/Moderation/index.tsx:227 msgid "Open muted words and tags settings" -msgstr "" +msgstr "Відкрити налаштування ігнорування слів і тегів" #: src/view/com/home/HomeHeaderLayoutMobile.tsx:52 msgid "Open navigation" @@ -2939,7 +2939,7 @@ msgstr "Відкрити storybook сторінку" #: src/view/screens/Settings/index.tsx:780 msgid "Open system log" -msgstr "" +msgstr "Відкрити системний журнал" #: src/view/com/util/forms/DropdownButton.tsx:154 msgid "Opens {numItems} options" @@ -2976,12 +2976,12 @@ msgstr "Відкриває налаштування зовнішніх вбуд #: src/view/com/auth/SplashScreen.tsx:50 #: src/view/com/auth/SplashScreen.web.tsx:94 msgid "Opens flow to create a new Bluesky account" -msgstr "" +msgstr "Відкриває процес створення нового облікового запису Bluesky" #: src/view/com/auth/SplashScreen.tsx:65 #: src/view/com/auth/SplashScreen.web.tsx:109 msgid "Opens flow to sign into your existing Bluesky account" -msgstr "" +msgstr "Відкриває процес входу в існуючий обліковий запис Bluesky" #: src/view/com/modals/InviteCodes.tsx:173 msgid "Opens list of invite codes" @@ -2989,23 +2989,23 @@ msgstr "Відкриває список кодів запрошення" #: src/view/screens/Settings/index.tsx:762 msgid "Opens modal for account deletion confirmation. Requires email code" -msgstr "" +msgstr "Відкриває модальне вікно для підтвердження видалення облікового запису. Потребує код з електронної пошти" #: src/view/screens/Settings/index.tsx:720 msgid "Opens modal for changing your Bluesky password" -msgstr "" +msgstr "Відкриває модальне вікно для зміни паролю в Bluesky" #: src/view/screens/Settings/index.tsx:669 msgid "Opens modal for choosing a new Bluesky handle" -msgstr "" +msgstr "Відкриває модальне вікно для вибору псевдоніму в Bluesky" #: src/view/screens/Settings/index.tsx:743 msgid "Opens modal for downloading your Bluesky account data (repository)" -msgstr "" +msgstr "Відкриває модальне вікно для завантаження даних з вашого облікового запису Bluesky (репозиторій)" #: src/view/screens/Settings/index.tsx:932 msgid "Opens modal for email verification" -msgstr "" +msgstr "Відкриває модальне вікно для перевірки електронної пошти" #: src/view/com/modals/ChangeHandle.tsx:282 msgid "Opens modal for using custom domain" @@ -3030,15 +3030,15 @@ msgstr "Відкриває сторінку з усіма збереженими #: src/view/screens/Settings/index.tsx:647 msgid "Opens the app password settings" -msgstr "" +msgstr "Відкриває налаштування паролів для застосунків" #: src/view/screens/Settings/index.tsx:505 msgid "Opens the Following feed preferences" -msgstr "" +msgstr "Відкриває налаштування стрічки підписок" #: src/view/com/modals/LinkWarning.tsx:93 msgid "Opens the linked website" -msgstr "" +msgstr "Відкриває посилання" #: src/view/screens/Settings/index.tsx:793 #: src/view/screens/Settings/index.tsx:803 @@ -3059,7 +3059,7 @@ msgstr "Опція {0} з {numItems}" #: src/components/ReportDialog/SubmitView.tsx:160 msgid "Optionally provide additional information below:" -msgstr "" +msgstr "За бажанням надайте додаткову інформацію нижче:" #: src/view/com/modals/Threadgate.tsx:89 msgid "Or combine these options:" @@ -3067,7 +3067,7 @@ msgstr "Або якісь із наступних варіантів:" #: src/lib/moderation/useReportOptions.ts:25 msgid "Other" -msgstr "" +msgstr "Інше" #: src/components/AccountList.tsx:73 msgid "Other account" @@ -3095,7 +3095,7 @@ msgstr "Пароль" #: src/view/com/modals/ChangePassword.tsx:142 msgid "Password Changed" -msgstr "" +msgstr "Пароль змінено" #: src/screens/Login/index.tsx:157 msgid "Password updated" @@ -3108,7 +3108,7 @@ msgstr "Пароль змінено!" #: src/view/screens/Search/Search.tsx:447 #: src/view/screens/Search/Search.tsx:456 msgid "People" -msgstr "" +msgstr "Люди" #: src/Navigation.tsx:164 msgid "People followed by @{0}" @@ -3137,11 +3137,11 @@ msgstr "Зображення, призначені для дорослих." #: src/view/screens/ProfileFeed.tsx:303 #: src/view/screens/ProfileList.tsx:565 msgid "Pin to home" -msgstr "Закріпити до головної" +msgstr "Закріпити" #: src/view/screens/ProfileFeed.tsx:306 msgid "Pin to Home" -msgstr "Закріп до головної" +msgstr "Закріпити на головній" #: src/view/screens/SavedFeeds.tsx:89 msgid "Pinned Feeds" @@ -3198,7 +3198,7 @@ msgstr "Будь ласка, також введіть ваш пароль:" #: src/components/moderation/LabelsOnMeDialog.tsx:221 msgid "Please explain why you think this label was incorrectly applied by {0}" -msgstr "" +msgstr "Будь ласка, поясніть, чому ви вважаєте, що ця позначка була помилково додана до {0}" #: src/view/com/modals/VerifyEmail.tsx:101 msgid "Please Verify Your Email" @@ -3248,12 +3248,12 @@ msgstr "Пост приховано" #: src/components/moderation/ModerationDetailsDialog.tsx:97 #: src/lib/moderation/useModerationCauseDescription.ts:99 msgid "Post Hidden by Muted Word" -msgstr "Пост схований вимкненим словом" +msgstr "Пост приховано через ігнороване слово" #: src/components/moderation/ModerationDetailsDialog.tsx:100 #: src/lib/moderation/useModerationCauseDescription.ts:108 msgid "Post Hidden by You" -msgstr "Пост схований вами" +msgstr "Ви приховали цей пост" #: src/view/com/composer/select-language/SelectLangBtn.tsx:87 msgid "Post language" @@ -3297,7 +3297,7 @@ msgstr "Змінити хостинг-провайдера" #: src/components/Lists.tsx:80 #: src/screens/Signup/index.tsx:187 msgid "Press to retry" -msgstr "" +msgstr "Натисніть, щоб повторити спробу" #: src/view/com/lightbox/Lightbox.web.tsx:150 msgid "Previous image" @@ -3331,7 +3331,7 @@ msgstr "Обробка..." #: src/view/screens/DebugMod.tsx:888 #: src/view/screens/Profile.tsx:361 msgid "profile" -msgstr "" +msgstr "профіль" #: src/view/shell/bottom-bar/BottomBar.tsx:261 #: src/view/shell/desktop/LeftNav.tsx:419 @@ -3393,7 +3393,7 @@ msgstr "Співвідношення сторін" #: src/view/screens/Search/Search.tsx:924 msgid "Recent Searches" -msgstr "" +msgstr "Останні запити" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:117 msgid "Recommended Feeds" @@ -3418,11 +3418,11 @@ msgstr "Видалити обліковий запис" #: src/view/com/util/UserAvatar.tsx:360 msgid "Remove Avatar" -msgstr "Вилучити аватар" +msgstr "Видалити аватар" #: src/view/com/util/UserBanner.tsx:148 msgid "Remove Banner" -msgstr "Вилуч шапку" +msgstr "Видалити банер" #: src/view/com/posts/FeedErrorMessage.tsx:160 msgid "Remove feed" @@ -3430,7 +3430,7 @@ msgstr "Видалити стрічку" #: src/view/com/posts/FeedErrorMessage.tsx:201 msgid "Remove feed?" -msgstr "Вилучити стрічку?" +msgstr "Видалити стрічку?" #: src/view/com/feeds/FeedSourceCard.tsx:173 #: src/view/com/feeds/FeedSourceCard.tsx:233 @@ -3441,7 +3441,7 @@ msgstr "Вилучити з моїх стрічок" #: src/view/com/feeds/FeedSourceCard.tsx:278 msgid "Remove from my feeds?" -msgstr "Вилучити з моїх стрічок?" +msgstr "Видалити з моїх стрічок?" #: src/view/com/composer/photos/Gallery.tsx:167 msgid "Remove image" @@ -3461,7 +3461,7 @@ msgstr "Видалити репост" #: src/view/com/posts/FeedErrorMessage.tsx:202 msgid "Remove this feed from your saved feeds" -msgstr "Вилуч цю стрічку з ваших збережених" +msgstr "Вилучити цю стрічку зі збережених стрічок" #: src/view/com/modals/ListAddRemoveUsers.tsx:199 #: src/view/com/modals/UserAddRemoveLists.tsx:152 @@ -3474,7 +3474,7 @@ msgstr "Вилучено з моїх стрічок" #: src/view/screens/ProfileFeed.tsx:210 msgid "Removed from your feeds" -msgstr "Вилучено з ваших стрічок" +msgstr "Видалено з моїх стрічок" #: src/view/com/composer/ExternalEmbed.tsx:71 msgid "Removes default thumbnail from {0}" @@ -3510,7 +3510,7 @@ msgstr "Поскаржитись на обліковий запис" #: src/components/ReportDialog/index.tsx:49 msgid "Report dialog" -msgstr "" +msgstr "Діалогове вікно для скарг" #: src/view/screens/ProfileFeed.tsx:363 #: src/view/screens/ProfileFeed.tsx:365 @@ -3528,23 +3528,23 @@ msgstr "Поскаржитись на пост" #: src/components/ReportDialog/SelectReportOptionView.tsx:42 msgid "Report this content" -msgstr "Повідом про цей вміст" +msgstr "Повідомити про цей вміст" #: src/components/ReportDialog/SelectReportOptionView.tsx:55 msgid "Report this feed" -msgstr "Повідом про цю стрічку" +msgstr "Повідомити про цю стрічку" #: src/components/ReportDialog/SelectReportOptionView.tsx:52 msgid "Report this list" -msgstr "Повідом про цей список" +msgstr "Поскаржитись на цей список" #: src/components/ReportDialog/SelectReportOptionView.tsx:49 msgid "Report this post" -msgstr "Повідом про цей пост" +msgstr "Поскаржитись на цей пост" #: src/components/ReportDialog/SelectReportOptionView.tsx:46 msgid "Report this user" -msgstr "Повідом про цього ужитковця" +msgstr "Поскаржитись на цього користувача" #: src/view/com/modals/Repost.tsx:44 #: src/view/com/modals/Repost.tsx:49 @@ -3573,11 +3573,11 @@ msgstr "{0} зробив(-ла) репост" #: src/view/com/posts/FeedItem.tsx:214 #~ msgid "Reposted by <0/>" -#~ msgstr "<0/> зробив(-ла) репост" +#~ msgstr "" #: src/view/com/posts/FeedItem.tsx:216 msgid "Reposted by <0><1/>" -msgstr "" +msgstr "Зроблено репост від <0><1/>" #: src/view/com/notifications/FeedItem.tsx:168 msgid "reposted your post" @@ -3663,12 +3663,12 @@ msgstr "Повернутися до попередньої сторінки" #: src/view/screens/NotFound.tsx:59 msgid "Returns to home page" -msgstr "" +msgstr "Повертає до головної сторінки" #: src/view/screens/NotFound.tsx:58 #: src/view/screens/ProfileFeed.tsx:113 msgid "Returns to previous page" -msgstr "" +msgstr "Повертає до попередньої сторінки" #: src/components/dialogs/BirthDateSettings.tsx:125 #: src/view/com/modals/ChangeHandle.tsx:174 @@ -3689,7 +3689,7 @@ msgstr "Зберегти опис" #: src/components/dialogs/BirthDateSettings.tsx:119 msgid "Save birthday" -msgstr "Збережи уродини" +msgstr "Зберегти день народження" #: src/view/com/modals/EditProfile.tsx:233 msgid "Save Changes" @@ -3706,7 +3706,7 @@ msgstr "Обрізати зображення" #: src/view/screens/ProfileFeed.tsx:347 #: src/view/screens/ProfileFeed.tsx:353 msgid "Save to my feeds" -msgstr "Збережи до мої стрічок" +msgstr "Зберегти до моїх стрічок" #: src/view/screens/SavedFeeds.tsx:123 msgid "Saved Feeds" @@ -3714,7 +3714,7 @@ msgstr "Збережені стрічки" #: src/view/com/lightbox/Lightbox.tsx:81 msgid "Saved to your camera roll." -msgstr "" +msgstr "Збережено до галереї." #: src/view/screens/ProfileFeed.tsx:214 msgid "Saved to your feeds" @@ -3730,7 +3730,7 @@ msgstr "Зберігає зміню псевдоніму на {handle}" #: src/view/com/modals/crop-image/CropImage.web.tsx:146 msgid "Saves image crop settings" -msgstr "" +msgstr "Зберігає налаштування обрізання зображення" #: src/screens/Onboarding/index.tsx:36 msgid "Science" @@ -3799,7 +3799,7 @@ msgstr "Переглянути пости цього користувача з < #: src/view/com/notifications/FeedItem.tsx:419 #: src/view/com/util/UserAvatar.tsx:381 msgid "See profile" -msgstr "" +msgstr "Переглянути профіль" #: src/view/screens/SavedFeeds.tsx:164 msgid "See this guide" @@ -3807,7 +3807,7 @@ msgstr "Перегляньте цей посібник" #: src/view/com/auth/HomeLoggedOutCTA.tsx:40 #~ msgid "See what's next" -#~ msgstr "Що далі?" +#~ msgstr "" #: src/view/com/util/Selector.tsx:106 msgid "Select {item}" @@ -3823,11 +3823,11 @@ msgstr "Вибрати існуючий обліковий запис" #: src/view/screens/LanguageSettings.tsx:299 msgid "Select languages" -msgstr "Обери мови" +msgstr "Вибрати мови" #: src/components/ReportDialog/SelectLabelerView.tsx:30 msgid "Select moderator" -msgstr "" +msgstr "Оберіть модератора" #: src/view/com/util/Selector.tsx:107 msgid "Select option {i} of {numItems}" @@ -3839,7 +3839,7 @@ msgstr "Оберіть деякі облікові записи, щоб підп #: src/components/ReportDialog/SubmitView.tsx:133 msgid "Select the moderation service(s) to report to" -msgstr "" +msgstr "Оберіть сервіс модерації для скарги" #: src/view/com/auth/server-input/index.tsx:82 msgid "Select the service that hosts your data." @@ -3859,11 +3859,11 @@ msgstr "Оберіть мови постів, які ви хочете бачи #: src/view/screens/LanguageSettings.tsx:98 msgid "Select your app language for the default text to display in the app." -msgstr "" +msgstr "Оберіть мову застосунку для відображення тексту за замовчуванням." #: src/screens/Signup/StepInfo/index.tsx:135 msgid "Select your date of birth" -msgstr "" +msgstr "Оберіть дату народження" #: src/screens/Onboarding/StepInterests/index.tsx:200 msgid "Select your interests from the options below" @@ -3903,11 +3903,11 @@ msgstr "Надіслати відгук" #: src/components/ReportDialog/SubmitView.tsx:213 #: src/components/ReportDialog/SubmitView.tsx:217 msgid "Send report" -msgstr "" +msgstr "Поскаржитись" #: src/components/ReportDialog/SelectLabelerView.tsx:44 msgid "Send report to {0}" -msgstr "" +msgstr "Надіслати скаргу до {0}" #: src/view/com/modals/DeleteAccount.tsx:132 msgid "Sends email with confirmation code for account deletion" @@ -3919,7 +3919,7 @@ msgstr "Адреса сервера" #: src/screens/Moderation/index.tsx:304 msgid "Set birthdate" -msgstr "" +msgstr "Додати дату народження" #: src/screens/Login/SetNewPasswordForm.tsx:102 msgid "Set new password" @@ -3955,23 +3955,23 @@ msgstr "Встановлює псевдонім Bluesky" #: src/view/screens/Settings/index.tsx:458 msgid "Sets color theme to dark" -msgstr "" +msgstr "Встановлює темну тему" #: src/view/screens/Settings/index.tsx:451 msgid "Sets color theme to light" -msgstr "" +msgstr "Встановлює світлу тему" #: src/view/screens/Settings/index.tsx:445 msgid "Sets color theme to system setting" -msgstr "" +msgstr "Встановлює тему відповідно до системних налаштувань" #: src/view/screens/Settings/index.tsx:484 msgid "Sets dark theme to the dark theme" -msgstr "" +msgstr "Встановлює чорний колір для темної теми" #: src/view/screens/Settings/index.tsx:477 msgid "Sets dark theme to the dim theme" -msgstr "" +msgstr "Встановлює тьмяний колір для темної теми" #: src/screens/Login/ForgotPasswordForm.tsx:113 msgid "Sets email for password reset" @@ -3979,15 +3979,15 @@ msgstr "Встановлює ел. адресу для скидання паро #: src/view/com/modals/crop-image/CropImage.web.tsx:124 msgid "Sets image aspect ratio to square" -msgstr "" +msgstr "Встановлює квадратне співвідношення сторін зображення" #: src/view/com/modals/crop-image/CropImage.web.tsx:114 msgid "Sets image aspect ratio to tall" -msgstr "" +msgstr "Встановлює співвідношення сторін зображення до висоти" #: src/view/com/modals/crop-image/CropImage.web.tsx:104 msgid "Sets image aspect ratio to wide" -msgstr "" +msgstr "Встановлює співвідношення сторін зображення до ширини" #: src/Navigation.tsx:139 #: src/view/screens/Settings/index.tsx:316 @@ -4003,7 +4003,7 @@ msgstr "Сексуальна активність або еротична ого #: src/lib/moderation/useGlobalLabelStrings.ts:38 msgid "Sexually Suggestive" -msgstr "" +msgstr "З сексуальним підтекстом" #: src/view/com/lightbox/Lightbox.tsx:141 msgctxt "action" @@ -4023,7 +4023,7 @@ msgstr "Поширити" #: src/view/com/util/forms/PostDropdownBtn.tsx:369 #: src/view/com/util/post-ctrls/PostCtrls.tsx:253 msgid "Share anyway" -msgstr "" +msgstr "Все одно поширити" #: src/view/screens/ProfileFeed.tsx:373 #: src/view/screens/ProfileFeed.tsx:375 @@ -4037,7 +4037,7 @@ msgstr "Поділитись посиланням" #: src/view/com/modals/LinkWarning.tsx:92 msgid "Shares the linked website" -msgstr "" +msgstr "Поширює посилання" #: src/components/moderation/ContentHider.tsx:115 #: src/components/moderation/LabelPreference.tsx:136 @@ -4059,11 +4059,11 @@ msgstr "Всеодно показати" #: src/lib/moderation/useLabelBehaviorDescription.ts:27 #: src/lib/moderation/useLabelBehaviorDescription.ts:63 msgid "Show badge" -msgstr "" +msgstr "Показати значок" #: src/lib/moderation/useLabelBehaviorDescription.ts:61 msgid "Show badge and filter from feeds" -msgstr "" +msgstr "Показати значок і фільтри зі стрічки" #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:200 msgid "Show follows similar to {0}" @@ -4134,11 +4134,11 @@ msgstr "Показати користувачів" #: src/lib/moderation/useLabelBehaviorDescription.ts:58 msgid "Show warning" -msgstr "" +msgstr "Показувати попередження" #: src/lib/moderation/useLabelBehaviorDescription.ts:56 msgid "Show warning and filter from feeds" -msgstr "" +msgstr "Показувати попередження і фільтрувати зі стрічки" #: src/view/com/post-thread/PostThreadFollowBtn.tsx:130 msgid "Shows posts from {0} in your feed" @@ -4175,11 +4175,11 @@ msgstr "Увійти як..." #: src/components/dialogs/Signin.tsx:75 msgid "Sign in or create your account to join the conversation!" -msgstr "" +msgstr "Увійдіть або створіть обліковий запис, щоб приєднатися до розмови!" #: src/components/dialogs/Signin.tsx:46 msgid "Sign into Bluesky or create a new account" -msgstr "" +msgstr "Увійдіть у Bluesky або створіть новий обліковий запис" #: src/view/screens/Settings/index.tsx:118 #: src/view/screens/Settings/index.tsx:121 @@ -4196,7 +4196,7 @@ msgstr "Вийти" #: src/view/shell/NavSignupCard.tsx:55 #: src/view/shell/NavSignupCard.tsx:57 msgid "Sign up" -msgstr "Зареєструватися" +msgstr "Реєстрація" #: src/view/shell/NavSignupCard.tsx:47 msgid "Sign up or sign in to join the conversation" @@ -4233,7 +4233,7 @@ msgstr "Розробка П/З" #: src/screens/Moderation/index.tsx:114 #: src/screens/Profile/Sections/Labels.tsx:87 msgid "Something went wrong, please try again." -msgstr "" +msgstr "Щось пішло не так. Будь ласка, спробуйте ще раз." #: src/App.native.tsx:64 msgid "Sorry! Your session expired. Please log in again." @@ -4295,11 +4295,11 @@ msgstr "Підписатися" #: src/screens/Profile/Sections/Labels.tsx:191 msgid "Subscribe to @{0} to use these labels:" -msgstr "" +msgstr "Підпишіться на @{0}, щоб використовувати ці мітки:" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:227 msgid "Subscribe to Labeler" -msgstr "" +msgstr "Підписатися на маркувальника" #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:172 #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:307 @@ -4308,7 +4308,7 @@ msgstr "Підписатися на {0} стрічку" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:191 msgid "Subscribe to this labeler" -msgstr "" +msgstr "Підписатися на цього маркувальника" #: src/view/screens/ProfileList.tsx:588 msgid "Subscribe to this list" @@ -4389,7 +4389,7 @@ msgstr "Умови Використання" #: src/lib/moderation/useReportOptions.ts:79 #: src/lib/moderation/useReportOptions.ts:87 msgid "Terms used violate community standards" -msgstr "Уживані терміни порушують стандарти спільноти" +msgstr "Використані терміни порушують стандарти спільноти" #: src/components/dialogs/MutedWords.tsx:323 msgid "text" @@ -4401,11 +4401,11 @@ msgstr "Поле вводу тексту" #: src/components/ReportDialog/SubmitView.tsx:76 msgid "Thank you. Your report has been sent." -msgstr "Дякую вам за повідомлення." +msgstr "Дякуємо. Вашу скаргу було надіслано." #: src/view/com/modals/ChangeHandle.tsx:465 msgid "That contains the following:" -msgstr "" +msgstr "Що містить наступне:" #: src/screens/Signup/index.tsx:85 msgid "That handle is already taken." @@ -4418,7 +4418,7 @@ msgstr "Обліковий запис зможе взаємодіяти з ва #: src/components/moderation/ModerationDetailsDialog.tsx:127 msgid "the author" -msgstr "" +msgstr "автором" #: src/view/screens/CommunityGuidelines.tsx:36 msgid "The Community Guidelines have been moved to <0/>" @@ -4430,11 +4430,11 @@ msgstr "Політику захисту авторського права пер #: src/components/moderation/LabelsOnMeDialog.tsx:48 msgid "The following labels were applied to your account." -msgstr "" +msgstr "Наступні мітки були додано до вашого облікового запису." #: src/components/moderation/LabelsOnMeDialog.tsx:49 msgid "The following labels were applied to your content." -msgstr "" +msgstr "Наступні мітки були додано до вашого контенту." #: src/screens/Onboarding/Layout.tsx:58 msgid "The following steps will help customize your Bluesky experience." @@ -4508,7 +4508,7 @@ msgstr "Виникла проблема з завантаженням ваших #: src/components/ReportDialog/SubmitView.tsx:81 msgid "There was an issue sending your report. Please check your internet connection." -msgstr "" +msgstr "Виникла проблема з надсиланням вашої скарги. Будь ласка, перевірте підключення до Інтернету." #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:65 msgid "There was an issue syncing your preferences with the server" @@ -4561,15 +4561,15 @@ msgstr "Цей користувач вказав, що не хоче, аби й #: src/components/moderation/LabelsOnMeDialog.tsx:204 msgid "This appeal will be sent to <0>{0}." -msgstr "" +msgstr "Це звернення буде надіслано до <0>{0}." #: src/lib/moderation/useGlobalLabelStrings.ts:19 msgid "This content has been hidden by the moderators." -msgstr "" +msgstr "Цей контент був прихований модераторами." #: src/lib/moderation/useGlobalLabelStrings.ts:24 msgid "This content has received a general warning from moderators." -msgstr "" +msgstr "Цей контент отримав загальне попередження від модераторів." #: src/components/dialogs/EmbedConsent.tsx:64 msgid "This content is hosted by {0}. Do you want to enable external media?" @@ -4586,7 +4586,7 @@ msgstr "Цей вміст не доступний для перегляду бе #: src/view/screens/Settings/ExportCarDialog.tsx:75 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost." -msgstr "" +msgstr "Ця функція знаходиться в беті. Ви можете дізнатися більше про експорт репозиторіїв у <0>цьому блозі.." #: src/view/com/posts/FeedErrorMessage.tsx:114 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." @@ -4612,11 +4612,11 @@ msgstr "Це важливо для випадку, якщо вам коли-не #: src/components/moderation/ModerationDetailsDialog.tsx:124 msgid "This label was applied by {0}." -msgstr "" +msgstr "Ця мітка була додана {0}." #: src/screens/Profile/Sections/Labels.tsx:178 msgid "This labeler hasn't declared what labels it publishes, and may not be active." -msgstr "" +msgstr "Цей маркувальник ще не заявив, які мітки він публікує, і може бути неактивним." #: src/view/com/modals/LinkWarning.tsx:72 msgid "This link is taking you to the following website:" @@ -4628,7 +4628,7 @@ msgstr "Список порожній!" #: src/screens/Profile/ErrorState.tsx:40 msgid "This moderation service is unavailable. See below for more details. If this issue persists, contact us." -msgstr "" +msgstr "Даний сервіс модерації недоступний. Перегляньте деталі нижче. Якщо проблема не зникне, зв'яжіться з нами." #: src/view/com/modals/AddAppPasswords.tsx:107 msgid "This name is already in use" @@ -4641,27 +4641,27 @@ msgstr "Цей пост було видалено." #: src/view/com/util/forms/PostDropdownBtn.tsx:366 #: src/view/com/util/post-ctrls/PostCtrls.tsx:250 msgid "This post is only visible to logged-in users. It won't be visible to people who aren't logged in." -msgstr "" +msgstr "Цей пост видно лише користувачам, які увійшли до системи. Воно не буде видимим для людей, які не ввійшли до системи." #: src/view/com/util/forms/PostDropdownBtn.tsx:348 msgid "This post will be hidden from feeds." -msgstr "" +msgstr "Цей пост буде приховано зі стрічок." #: src/view/com/profile/ProfileMenu.tsx:370 msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't logged in." -msgstr "" +msgstr "Цей профіль видно лише користувачам, які увійшли до системи. Воно не буде видимим для людей, які не ввійшли до системи." #: src/screens/Signup/StepInfo/Policies.tsx:37 msgid "This service has not provided terms of service or a privacy policy." -msgstr "" +msgstr "Цей сервіс не надав умови обслуговування або політики конфіденційності." #: src/view/com/modals/ChangeHandle.tsx:445 msgid "This should create a domain record at:" -msgstr "" +msgstr "Це має створити обліковий запис домену:" #: src/view/com/profile/ProfileFollowers.tsx:87 msgid "This user doesn't have any followers." -msgstr "" +msgstr "Цей користувач ще не має жодного підписника." #: src/components/moderation/ModerationDetailsDialog.tsx:72 #: src/lib/moderation/useModerationCauseDescription.ts:68 @@ -4670,19 +4670,19 @@ msgstr "Цей користувач заблокував вас. Ви не мо #: src/lib/moderation/useGlobalLabelStrings.ts:30 msgid "This user has requested that their content only be shown to signed-in users." -msgstr "" +msgstr "Цей користувач налаштував, щоб його контент був видимий лише для користувачів, які увійшли в систему." #: src/components/moderation/ModerationDetailsDialog.tsx:55 msgid "This user is included in the <0>{0} list which you have blocked." -msgstr "" +msgstr "Цей користувач є в списку <0>{0}, який ви заблокували." #: src/components/moderation/ModerationDetailsDialog.tsx:84 msgid "This user is included in the <0>{0} list which you have muted." -msgstr "" +msgstr "Цей користувач є в списку <0>{0}, який ви додали до ігнорування." #: src/view/com/profile/ProfileFollows.tsx:87 msgid "This user isn't following anyone." -msgstr "" +msgstr "Цей користувач не підписаний ні на кого." #: src/view/com/modals/SelfLabel.tsx:137 msgid "This warning is only available for posts with media attached." @@ -4694,7 +4694,7 @@ msgstr "Це видалить {0} зі ваших ігнорованих слі #: src/view/screens/Settings/index.tsx:525 msgid "Thread preferences" -msgstr "" +msgstr "Налаштування гілок" #: src/view/screens/PreferencesThreads.tsx:53 #: src/view/screens/Settings/index.tsx:535 @@ -4711,7 +4711,7 @@ msgstr "Налаштування обговорень" #: src/components/ReportDialog/SelectLabelerView.tsx:33 msgid "To whom would you like to send this report?" -msgstr "" +msgstr "Кому ви хотіли б відправити цю скаргу?" #: src/components/dialogs/MutedWords.tsx:112 msgid "Toggle between muted word options." @@ -4723,11 +4723,11 @@ msgstr "Розкрити/сховати" #: src/screens/Moderation/index.tsx:332 msgid "Toggle to enable or disable adult content" -msgstr "" +msgstr "Увімкнути або вимкнути вміст для дорослих" #: src/view/screens/Search/Search.tsx:427 msgid "Top" -msgstr "" +msgstr "Верх" #: src/view/com/modals/EditImage.tsx:272 msgid "Transformations" @@ -4747,7 +4747,7 @@ msgstr "Спробувати ще раз" #: src/view/com/modals/ChangeHandle.tsx:428 msgid "Type:" -msgstr "" +msgstr "Тип:" #: src/view/screens/ProfileList.tsx:480 msgid "Un-block list" @@ -4786,7 +4786,7 @@ msgstr "Розблокувати обліковий запис" #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:280 #: src/view/com/profile/ProfileMenu.tsx:343 msgid "Unblock Account?" -msgstr "" +msgstr "Розблокувати обліковий запис?" #: src/view/com/modals/Repost.tsx:43 #: src/view/com/modals/Repost.tsx:56 @@ -4812,7 +4812,7 @@ msgstr "Відписатися від {0}" #: src/view/com/profile/ProfileMenu.tsx:241 #: src/view/com/profile/ProfileMenu.tsx:251 msgid "Unfollow Account" -msgstr "" +msgstr "Відписатися від облікового запису" #: src/view/com/util/post-ctrls/PostCtrls.tsx:197 msgid "Unlike" @@ -4820,7 +4820,7 @@ msgstr "Прибрати вподобання" #: src/view/screens/ProfileFeed.tsx:585 msgid "Unlike this feed" -msgstr "" +msgstr "Видалити вподобання цієї стрічки" #: src/components/TagMenu/index.tsx:249 #: src/view/screens/ProfileList.tsx:581 @@ -4852,7 +4852,7 @@ msgstr "Відкріпити" #: src/view/screens/ProfileFeed.tsx:303 msgid "Unpin from home" -msgstr "Відкріп з головної" +msgstr "Відкріпити від головної сторінки" #: src/view/screens/ProfileList.tsx:446 msgid "Unpin moderation list" @@ -4864,11 +4864,11 @@ msgstr "Відписатися" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:190 msgid "Unsubscribe from this labeler" -msgstr "" +msgstr "Відписатися від цього маркувальника" #: src/lib/moderation/useReportOptions.ts:70 msgid "Unwanted Sexual Content" -msgstr "" +msgstr "Небажаний сексуальний вміст" #: src/view/com/modals/UserAddRemoveLists.tsx:70 msgid "Update {displayName} in Lists" @@ -4876,7 +4876,7 @@ msgstr "Змінити належність {displayName} до списків" #: src/view/com/modals/ChangeHandle.tsx:508 msgid "Update to {handle}" -msgstr "" +msgstr "Оновити до {handle}" #: src/screens/Login/SetNewPasswordForm.tsx:186 msgid "Updating..." @@ -4891,23 +4891,23 @@ msgstr "Завантажити текстовий файл до:" #: src/view/com/util/UserBanner.tsx:116 #: src/view/com/util/UserBanner.tsx:119 msgid "Upload from Camera" -msgstr "" +msgstr "Завантажити з камери" #: src/view/com/util/UserAvatar.tsx:345 #: src/view/com/util/UserBanner.tsx:133 msgid "Upload from Files" -msgstr "" +msgstr "Завантажити з файлів" #: src/view/com/util/UserAvatar.tsx:339 #: src/view/com/util/UserAvatar.tsx:343 #: src/view/com/util/UserBanner.tsx:127 #: src/view/com/util/UserBanner.tsx:131 msgid "Upload from Library" -msgstr "" +msgstr "Завантажити з бібліотеки" #: src/view/com/modals/ChangeHandle.tsx:408 msgid "Use a file on your server" -msgstr "" +msgstr "Використовувати файл на вашому сервері" #: src/view/screens/AppPasswords.tsx:197 msgid "Use app passwords to login to other Bluesky clients without giving full access to your account or password." @@ -4915,7 +4915,7 @@ msgstr "Використовуйте паролі для застосунків #: src/view/com/modals/ChangeHandle.tsx:517 msgid "Use bsky.social as hosting provider" -msgstr "" +msgstr "Використовувати bsky.social як хостинг-провайдер" #: src/view/com/modals/ChangeHandle.tsx:516 msgid "Use default provider" @@ -4933,7 +4933,7 @@ msgstr "У звичайному браузері" #: src/view/com/modals/ChangeHandle.tsx:400 msgid "Use the DNS panel" -msgstr "" +msgstr "Використати панель DNS" #: src/view/com/modals/AddAppPasswords.tsx:156 msgid "Use this to sign into the other app along with your handle." @@ -4950,7 +4950,7 @@ msgstr "Користувача заблоковано" #: src/lib/moderation/useModerationCauseDescription.ts:48 msgid "User Blocked by \"{0}\"" -msgstr "" +msgstr "Користувача заблоковано списком \"{0}\"" #: src/components/moderation/ModerationDetailsDialog.tsx:53 msgid "User Blocked by List" @@ -4958,7 +4958,7 @@ msgstr "Користувача заблоковано списком" #: src/lib/moderation/useModerationCauseDescription.ts:66 msgid "User Blocking You" -msgstr "" +msgstr "Користувач заблокував вас" #: src/components/moderation/ModerationDetailsDialog.tsx:70 msgid "User Blocks You" @@ -5011,15 +5011,15 @@ msgstr "Користувачі в «{0}»" #: src/components/LikesDialog.tsx:85 msgid "Users that have liked this content or profile" -msgstr "" +msgstr "Користувачі, які вподобали цей контент і профіль" #: src/view/com/modals/ChangeHandle.tsx:436 msgid "Value:" -msgstr "" +msgstr "Значення:" #: src/view/com/modals/ChangeHandle.tsx:509 msgid "Verify {0}" -msgstr "" +msgstr "Верифікувати {0}" #: src/view/screens/Settings/index.tsx:906 msgid "Verify email" @@ -5060,11 +5060,11 @@ msgstr "Переглянути запис для налагодження" #: src/components/ReportDialog/SelectReportOptionView.tsx:132 msgid "View details" -msgstr "" +msgstr "Переглянути деталі" #: src/components/ReportDialog/SelectReportOptionView.tsx:127 msgid "View details for reporting a copyright violation" -msgstr "" +msgstr "Переглянути деталі як надіслати скаргу про порушення авторських прав" #: src/view/com/posts/FeedSlice.tsx:99 msgid "View full thread" @@ -5072,7 +5072,7 @@ msgstr "Переглянути обговорення" #: src/components/moderation/LabelsOnMe.tsx:51 msgid "View information about these labels" -msgstr "" +msgstr "Переглянути інформацію про мітки" #: src/components/ProfileHoverCard/index.web.tsx:264 #: src/components/ProfileHoverCard/index.web.tsx:293 @@ -5086,11 +5086,11 @@ msgstr "Переглянути аватар" #: src/components/LabelingServiceCard/index.tsx:140 msgid "View the labeling service provided by @{0}" -msgstr "" +msgstr "Переглянути послуги маркування, який надає @{0}" #: src/view/screens/ProfileFeed.tsx:597 msgid "View users who like this feed" -msgstr "" +msgstr "Переглянути користувачів, які вподобали цю стрічку" #: src/view/com/modals/LinkWarning.tsx:89 #: src/view/com/modals/LinkWarning.tsx:95 @@ -5106,11 +5106,11 @@ msgstr "Попереджати" #: src/lib/moderation/useLabelBehaviorDescription.ts:48 msgid "Warn content" -msgstr "" +msgstr "Попереджувати про вміст" #: src/lib/moderation/useLabelBehaviorDescription.ts:46 msgid "Warn content and filter from feeds" -msgstr "" +msgstr "Попереджувати про вміст і фільтрувати його зі стрічки" #: src/screens/Hashtag.tsx:133 msgid "We couldn't find any results for that hashtag." @@ -5138,11 +5138,11 @@ msgstr "Ми рекомендуємо стрічку «Discover»:" #: src/components/dialogs/BirthDateSettings.tsx:52 msgid "We were unable to load your birth date preferences. Please try again." -msgstr "" +msgstr "Не вдалося завантажити ваші налаштування дати дня народження. Повторіть спробу." #: src/screens/Moderation/index.tsx:385 msgid "We were unable to load your configured labelers at this time." -msgstr "" +msgstr "Наразі ми не змогли завантажити список ваших маркувальників." #: src/screens/Onboarding/StepInterests/index.tsx:137 msgid "We weren't able to connect. Please try again to continue setting up your account. If it continues to fail, you can skip this flow." @@ -5179,7 +5179,7 @@ msgstr "Нам дуже прикро! Ми не можемо знайти сто #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:327 msgid "We're sorry! You can only subscribe to ten labelers, and you've reached your limit of ten." -msgstr "" +msgstr "На жаль, ви можете підписатися тільки на 10 маркувальників, і ви вже досягли цього ліміту." #: src/view/com/auth/onboarding/WelcomeMobile.tsx:48 msgid "Welcome to <0>Bluesky" @@ -5210,23 +5210,23 @@ msgstr "Хто може відповідати" #: src/components/ReportDialog/SelectReportOptionView.tsx:43 msgid "Why should this content be reviewed?" -msgstr "" +msgstr "Чому слід переглянути цей контент?" #: src/components/ReportDialog/SelectReportOptionView.tsx:56 msgid "Why should this feed be reviewed?" -msgstr "" +msgstr "Чому слід переглянути цю стрічку?" #: src/components/ReportDialog/SelectReportOptionView.tsx:53 msgid "Why should this list be reviewed?" -msgstr "" +msgstr "Чому слід переглянути цей список?" #: src/components/ReportDialog/SelectReportOptionView.tsx:50 msgid "Why should this post be reviewed?" -msgstr "" +msgstr "Чому слід переглянути цей пост?" #: src/components/ReportDialog/SelectReportOptionView.tsx:47 msgid "Why should this user be reviewed?" -msgstr "" +msgstr "Чому слід переглянути цього користувача?" #: src/view/com/modals/crop-image/CropImage.web.tsx:103 msgid "Wide" @@ -5261,7 +5261,7 @@ msgstr "Ви в черзі." #: src/view/com/profile/ProfileFollows.tsx:86 msgid "You are not following anyone." -msgstr "" +msgstr "Ви ні на кого не підписані." #: src/view/com/posts/FollowingEmptyState.tsx:67 #: src/view/com/posts/FollowingEndOfFeed.tsx:68 @@ -5279,7 +5279,7 @@ msgstr "Тепер ви можете увійти за допомогою нов #: src/view/com/profile/ProfileFollowers.tsx:86 msgid "You do not have any followers." -msgstr "" +msgstr "У вас немає жодного підписника." #: src/view/com/modals/InviteCodes.tsx:67 msgid "You don't have any invite codes yet! We'll send you some when you've been on Bluesky for a little longer." @@ -5316,20 +5316,20 @@ msgstr "Ви ввели неправильний код. Він має вигл #: src/lib/moderation/useModerationCauseDescription.ts:109 msgid "You have hidden this post" -msgstr "" +msgstr "Ви приховали цей пост" #: src/components/moderation/ModerationDetailsDialog.tsx:101 msgid "You have hidden this post." -msgstr "" +msgstr "Ви приховали цей пост." #: src/components/moderation/ModerationDetailsDialog.tsx:94 #: src/lib/moderation/useModerationCauseDescription.ts:92 msgid "You have muted this account." -msgstr "" +msgstr "Ви увімкнули ігнорування цього облікового запису." #: src/lib/moderation/useModerationCauseDescription.ts:86 msgid "You have muted this user" -msgstr "" +msgstr "Ви увімкнули ігнорування цього користувача" #: src/view/com/feeds/ProfileFeedgens.tsx:144 msgid "You have no feeds." @@ -5342,7 +5342,7 @@ msgstr "У вас немає списків." #: src/view/screens/ModerationBlockedAccounts.tsx:138 msgid "You have not blocked any accounts yet. To block an account, go to their profile and select \"Block account\" from the menu on their account." -msgstr "" +msgstr "Ви ще не заблокували жодного облікового запису. Щоб заблокувати когось, перейдіть до їх профілю та виберіть опцію \"Заблокувати\" у меню їх облікового запису." #: src/view/screens/AppPasswords.tsx:89 msgid "You have not created any app passwords yet. You can create one by pressing the button below." @@ -5350,7 +5350,7 @@ msgstr "Ви ще не створили жодного пароля для за #: src/view/screens/ModerationMutedAccounts.tsx:136 msgid "You have not muted any accounts yet. To mute an account, go to their profile and select \"Mute account\" from the menu on their account." -msgstr "" +msgstr "Ви ще не ігноруєте жодного облікового запису. Щоб увімкнути ігнорування когось, перейдіть до їх профілю та виберіть опцію \"Ігнорувати\" у меню їх облікового запису." #: src/components/dialogs/MutedWords.tsx:249 msgid "You haven't muted any words or tags yet" @@ -5358,7 +5358,7 @@ msgstr "У вас ще немає ігнорованих слів чи тегі #: src/components/moderation/LabelsOnMeDialog.tsx:68 msgid "You may appeal these labels if you feel they were placed in error." -msgstr "" +msgstr "Ви можете оскаржувати мітки, якщо вважаєте, що вони були розміщені помилково." #: src/screens/Signup/StepInfo/Policies.tsx:79 msgid "You must be 13 years of age or older to sign up." @@ -5370,7 +5370,7 @@ msgstr "Ви повинні бути старше 18 років, щоб дозв #: src/components/ReportDialog/SubmitView.tsx:203 msgid "You must select at least one labeler for a report" -msgstr "" +msgstr "Ви повинні обрати хоча б одного маркувальника для скарги" #: src/view/com/util/forms/PostDropdownBtn.tsx:150 msgid "You will no longer receive notifications for this thread" @@ -5401,7 +5401,7 @@ msgstr "Все готово!" #: src/components/moderation/ModerationDetailsDialog.tsx:98 #: src/lib/moderation/useModerationCauseDescription.ts:101 msgid "You've chosen to hide a word or tag within this post." -msgstr "" +msgstr "Ви обрали приховувати слово або тег в цьому пості." #: src/view/com/posts/FollowingEndOfFeed.tsx:48 msgid "You've reached the end of your feed! Find some more accounts to follow." @@ -5486,3 +5486,4 @@ msgstr "Відповідь опубліковано" #: src/screens/Signup/index.tsx:153 msgid "Your user handle" msgstr "Ваш псевдонім" + From 3beb4ec63d561906b3e6816378af126bb9d04cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gild=C3=A1sio=20Filho?= Date: Thu, 18 Apr 2024 17:54:23 -0300 Subject: [PATCH 04/47] Update messages.po (#3610) --- src/locale/locales/pt-BR/messages.po | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/locale/locales/pt-BR/messages.po b/src/locale/locales/pt-BR/messages.po index e7d3b65266..3118484d63 100644 --- a/src/locale/locales/pt-BR/messages.po +++ b/src/locale/locales/pt-BR/messages.po @@ -8,7 +8,7 @@ msgstr "" "Language: pt-BR\n" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2024-04-10 18:15\n" +"PO-Revision-Date: 2024-04-18 15:23\n" "Last-Translator: gildaswise\n" "Language-Team: maisondasilva, MightyLoggor, gildaswise, gleydson, faeriarum\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" @@ -36,7 +36,7 @@ msgstr "<0>{0} seguindo" #: src/components/ProfileHoverCard/index.web.tsx:314 msgid "<0>{followers} <1>{pluralizedFollowers}" -msgstr "" +msgstr "<0>{followers} <1>{pluralizedFollowers}" #: src/components/ProfileHoverCard/index.web.tsx:326 #: src/screens/Profile/Header/Metrics.tsx:45 @@ -953,7 +953,7 @@ msgstr "Copiado" #: src/components/dialogs/Embed.tsx:134 msgid "Copied!" -msgstr "" +msgstr "Copiado!" #: src/view/com/modals/AddAppPasswords.tsx:190 msgid "Copies app password" @@ -970,7 +970,7 @@ msgstr "Copiar {0}" #: src/components/dialogs/Embed.tsx:120 #: src/components/dialogs/Embed.tsx:139 msgid "Copy code" -msgstr "" +msgstr "Copiar código" #: src/view/screens/ProfileList.tsx:390 msgid "Copy link to list" @@ -1015,7 +1015,7 @@ msgstr "Criar Conta" #: src/components/dialogs/Signin.tsx:86 #: src/components/dialogs/Signin.tsx:88 msgid "Create an account" -msgstr "" +msgstr "Criar conta" #: src/view/com/modals/AddAppPasswords.tsx:227 msgid "Create App Password" @@ -1157,11 +1157,11 @@ msgstr "Menos escuro" #: src/view/screens/Settings/index.tsx:697 msgid "Disable haptics" -msgstr "" +msgstr "Desabilitar feedback tátil" #: src/view/screens/Settings/index.tsx:697 msgid "Disable vibrations" -msgstr "" +msgstr "Desabilitar vibrações" #: src/lib/moderation/useLabelBehaviorDescription.ts:32 #: src/lib/moderation/useLabelBehaviorDescription.ts:42 @@ -1400,17 +1400,17 @@ msgstr "E-mail:" #: src/components/dialogs/Embed.tsx:112 msgid "Embed HTML code" -msgstr "" +msgstr "Código HTML para incorporação" #: src/components/dialogs/Embed.tsx:97 #: src/view/com/util/forms/PostDropdownBtn.tsx:253 #: src/view/com/util/forms/PostDropdownBtn.tsx:255 msgid "Embed post" -msgstr "" +msgstr "Incorporar post" #: src/components/dialogs/Embed.tsx:101 msgid "Embed this post in your website. Simply copy the following snippet and paste it into the HTML code of your website." -msgstr "" +msgstr "Incorpore este post no seu site. Basta copiar o trecho abaixo e colar no código HTML do seu site." #: src/components/dialogs/EmbedConsent.tsx:101 msgid "Enable {0} only" @@ -2209,7 +2209,7 @@ msgstr "Idiomas" #: src/view/screens/Search/Search.tsx:437 msgid "Latest" -msgstr "" +msgstr "Mais recentes" #: src/components/moderation/ScreenHider.tsx:136 msgid "Learn More" @@ -3103,7 +3103,7 @@ msgstr "Senha atualizada!" #: src/view/screens/Search/Search.tsx:447 #: src/view/screens/Search/Search.tsx:456 msgid "People" -msgstr "" +msgstr "Pessoas" #: src/Navigation.tsx:164 msgid "People followed by @{0}" @@ -3572,7 +3572,7 @@ msgstr "Repostado por {0}" #: src/view/com/posts/FeedItem.tsx:216 msgid "Reposted by <0><1/>" -msgstr "" +msgstr "Repostado por <0><1/>" #: src/view/com/notifications/FeedItem.tsx:168 msgid "reposted your post" @@ -3794,7 +3794,7 @@ msgstr "Ver posts com <0>{displayTag} deste usuário" #: src/view/com/notifications/FeedItem.tsx:419 #: src/view/com/util/UserAvatar.tsx:381 msgid "See profile" -msgstr "" +msgstr "Ver perfil" #: src/view/screens/SavedFeeds.tsx:164 msgid "See this guide" @@ -4170,11 +4170,11 @@ msgstr "Fazer login como..." #: src/components/dialogs/Signin.tsx:75 msgid "Sign in or create your account to join the conversation!" -msgstr "" +msgstr "Faça login ou crie sua conta para entrar na conversa!" #: src/components/dialogs/Signin.tsx:46 msgid "Sign into Bluesky or create a new account" -msgstr "" +msgstr "Faça login no Bluesky ou crie uma nova conta" #: src/view/screens/Settings/index.tsx:118 #: src/view/screens/Settings/index.tsx:121 @@ -4722,7 +4722,7 @@ msgstr "Ligar ou desligar conteúdo adulto" #: src/view/screens/Search/Search.tsx:427 msgid "Top" -msgstr "" +msgstr "Principais" #: src/view/com/modals/EditImage.tsx:272 msgid "Transformations" From 08c60147510e81821e0c6b8346133b642abf2c78 Mon Sep 17 00:00:00 2001 From: Mary <148872143+mary-ext@users.noreply.github.com> Date: Fri, 19 Apr 2024 04:21:09 +0700 Subject: [PATCH 05/47] Fix splash screen language picker appearance on Chrome (#2952) * fix: correct language picker appearance on Chrome * fix: prevent chevron shrink on long language names --- src/components/AppLanguageDropdown.web.tsx | 36 ++++++++++++---------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/components/AppLanguageDropdown.web.tsx b/src/components/AppLanguageDropdown.web.tsx index aea1b2b900..c3c8575a10 100644 --- a/src/components/AppLanguageDropdown.web.tsx +++ b/src/components/AppLanguageDropdown.web.tsx @@ -42,31 +42,35 @@ export function AppLanguageDropdown() { // We don't have hitSlop here to increase the tap region, // alternative is negative margins. {height: 32, marginVertical: -((32 - 14) / 2)}, - a.flex_row, - a.gap_sm, - a.align_center, - a.flex_shrink, ]}> - - {APP_LANGUAGES.find(l => l.code2 === sanitizedLang)?.name} - - + + + {APP_LANGUAGES.find(l => l.code2 === sanitizedLang)?.name} + + + ( + path: string, +): (input: Input) => Promise< + Ouput & { + meta: Meta + } +> { + return async input => { + const url = new URL(path, GIPHY_API_URL) + url.searchParams.set('api_key', GIPHY_API_KEY) + + for (const [key, value] of Object.entries(input)) { + url.searchParams.set(key, String(value)) + } + + const res = await fetch(url.toString(), { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + if (!res.ok) { + throw new Error('Failed to fetch Giphy API') + } + return res.json() + } +} + +export type Gif = { + type: string + id: string + slug: string + url: string + bitly_url: string + embed_url: string + username: string + source: string + rating: string + content_url: string + user: User + source_tld: string + source_post_url: string + update_datetime: string + create_datetime: string + import_datetime: string + trending_datetime: string + images: Images + title: string + alt_text: string +} + +type Images = { + fixed_height: { + url: string + width: string + height: string + size: string + mp4: string + mp4_size: string + webp: string + webp_size: string + } + + fixed_height_still: { + url: string + width: string + height: string + } + + fixed_height_downsampled: { + url: string + width: string + height: string + size: string + webp: string + webp_size: string + } + + fixed_width: { + url: string + width: string + height: string + size: string + mp4: string + mp4_size: string + webp: string + webp_size: string + } + + fixed_width_still: { + url: string + width: string + height: string + } + + fixed_width_downsampled: { + url: string + width: string + height: string + size: string + webp: string + webp_size: string + } + + fixed_height_small: { + url: string + width: string + height: string + size: string + mp4: string + mp4_size: string + webp: string + webp_size: string + } + + fixed_height_small_still: { + url: string + width: string + height: string + } + + fixed_width_small: { + url: string + width: string + height: string + size: string + mp4: string + mp4_size: string + webp: string + webp_size: string + } + + fixed_width_small_still: { + url: string + width: string + height: string + } + + downsized: { + url: string + width: string + height: string + size: string + } + + downsized_still: { + url: string + width: string + height: string + } + + downsized_large: { + url: string + width: string + height: string + size: string + } + + downsized_medium: { + url: string + width: string + height: string + size: string + } + + downsized_small: { + mp4: string + width: string + height: string + mp4_size: string + } + + original: { + width: string + height: string + size: string + frames: string + mp4: string + mp4_size: string + webp: string + webp_size: string + } + + original_still: { + url: string + width: string + height: string + } + + looping: { + mp4: string + } + + preview: { + mp4: string + mp4_size: string + width: string + height: string + } + + preview_gif: { + url: string + width: string + height: string + } +} + +type User = { + avatar_url: string + banner_url: string + profile_url: string + username: string + display_name: string +} + +type Meta = { + msg: string + status: number + response_id: string +} + +type Pagination = { + offset: number + total_count: number + count: number +} diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index f90bdbee2f..f0f630dd46 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -13,7 +13,6 @@ import { KeyboardAvoidingView, LayoutAnimation, Platform, - Pressable, ScrollView, StyleSheet, TouchableOpacity, @@ -27,6 +26,7 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {observer} from 'mobx-react-lite' +import {LikelyType} from '#/lib/link-meta/link-meta' import {logEvent} from '#/lib/statsig/statsig' import {logger} from '#/logger' import {emitPostCreated} from '#/state/events' @@ -37,6 +37,7 @@ import { useLanguagePrefs, useLanguagePrefsApi, } from '#/state/preferences/languages' +import {Gif} from '#/state/queries/giphy' import {useProfileQuery} from '#/state/queries/profile' import {ThreadgateSetting} from '#/state/queries/threadgate' import {getAgent, useSession} from '#/state/session' @@ -56,6 +57,9 @@ import {useDialogStateControlContext} from 'state/dialogs' import {GalleryModel} from 'state/models/media/gallery' import {ComposerOpts} from 'state/shell/composer' import {ComposerReplyTo} from 'view/com/composer/ComposerReplyTo' +import {atoms as a} from '#/alf' +import {Button} from '#/components/Button' +import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji' import * as Prompt from '#/components/Prompt' import {QuoteEmbed} from '../util/post-embeds/QuoteEmbed' import {Text} from '../util/text/Text' @@ -66,6 +70,7 @@ import {ExternalEmbed} from './ExternalEmbed' import {LabelsBtn} from './labels/LabelsBtn' import {Gallery} from './photos/Gallery' import {OpenCameraBtn} from './photos/OpenCameraBtn' +import {SelectGifBtn} from './photos/SelectGifBtn' import {SelectPhotoBtn} from './photos/SelectPhotoBtn' import {SelectLangBtn} from './select-language/SelectLangBtn' import {SuggestedLanguage} from './select-language/SuggestedLanguage' @@ -314,13 +319,33 @@ export const ComposePost = observer(function ComposePost({ ? _(msg`Write your reply`) : _(msg`What's up?`) - const canSelectImages = useMemo(() => gallery.size < 4, [gallery.size]) + const canSelectImages = gallery.size < 4 && !extLink const hasMedia = gallery.size > 0 || Boolean(extLink) const onEmojiButtonPress = useCallback(() => { openPicker?.(textInput.current?.getCursorPosition()) }, [openPicker]) + const focusTextInput = useCallback(() => { + textInput.current?.focus() + }, []) + + const onSelectGif = useCallback( + (gif: Gif) => + setExtLink({ + uri: gif.url, + isLoading: true, + meta: { + url: gif.url, + image: gif.images.original_still.url, + likelyType: LikelyType.HTML, + title: `${gif.title} - Find & Share on GIPHY`, + description: `ALT: ${gif.alt_text}`, + }, + }), + [setExtLink], + ) + return ( - {canSelectImages ? ( - <> - - - - ) : null} - {!isMobile ? ( - - - - ) : null} + + + + + {!isMobile ? ( + + ) : null} + @@ -586,7 +613,7 @@ const styles = StyleSheet.create({ }, bottomBar: { flexDirection: 'row', - paddingVertical: 10, + paddingVertical: 4, paddingLeft: 15, paddingRight: 20, alignItems: 'center', diff --git a/src/view/com/composer/photos/OpenCameraBtn.tsx b/src/view/com/composer/photos/OpenCameraBtn.tsx index 4353704d57..8f9152e34d 100644 --- a/src/view/com/composer/photos/OpenCameraBtn.tsx +++ b/src/view/com/composer/photos/OpenCameraBtn.tsx @@ -1,32 +1,31 @@ import React, {useCallback} from 'react' -import {TouchableOpacity, StyleSheet} from 'react-native' import * as MediaLibrary from 'expo-media-library' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnalytics} from 'lib/analytics/analytics' -import {openCamera} from 'lib/media/picker' -import {useCameraPermission} from 'lib/hooks/usePermissions' -import {HITSLOP_10, POST_IMG_MAX} from 'lib/constants' -import {GalleryModel} from 'state/models/media/gallery' -import {isMobileWeb, isNative} from 'platform/detection' -import {logger} from '#/logger' -import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useAnalytics} from '#/lib/analytics/analytics' +import {POST_IMG_MAX} from '#/lib/constants' +import {useCameraPermission} from '#/lib/hooks/usePermissions' +import {openCamera} from '#/lib/media/picker' +import {logger} from '#/logger' +import {isMobileWeb, isNative} from '#/platform/detection' +import {GalleryModel} from '#/state/models/media/gallery' +import {atoms as a, useTheme} from '#/alf' +import {Button} from '#/components/Button' +import {Camera_Stroke2_Corner0_Rounded as Camera} from '#/components/icons/Camera' type Props = { gallery: GalleryModel + disabled?: boolean } -export function OpenCameraBtn({gallery}: Props) { - const pal = usePalette('default') +export function OpenCameraBtn({gallery, disabled}: Props) { const {track} = useAnalytics() const {_} = useLingui() const {requestCameraAccessIfNeeded} = useCameraPermission() const [mediaPermissionRes, requestMediaPermission] = MediaLibrary.usePermissions() + const t = useTheme() const onPressTakePicture = useCallback(async () => { track('Composer:CameraOpened') @@ -68,25 +67,17 @@ export function OpenCameraBtn({gallery}: Props) { } return ( - - - + label={_(msg`Camera`)} + accessibilityHint={_(msg`Opens camera on device`)} + style={a.p_sm} + variant="ghost" + shape="round" + color="primary" + disabled={disabled}> + + ) } - -const styles = StyleSheet.create({ - button: { - paddingHorizontal: 15, - }, -}) diff --git a/src/view/com/composer/photos/SelectGifBtn.tsx b/src/view/com/composer/photos/SelectGifBtn.tsx new file mode 100644 index 0000000000..31310fdc1d --- /dev/null +++ b/src/view/com/composer/photos/SelectGifBtn.tsx @@ -0,0 +1,53 @@ +import React, {useCallback} from 'react' +import {Keyboard} from 'react-native' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {logEvent} from '#/lib/statsig/statsig' +import {Gif} from '#/state/queries/giphy' +import {atoms as a, useTheme} from '#/alf' +import {Button} from '#/components/Button' +import {useDialogControl} from '#/components/Dialog' +import {GifSelectDialog} from '#/components/dialogs/GifSelect' +import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif' + +type Props = { + onClose: () => void + onSelectGif: (gif: Gif) => void + disabled?: boolean +} + +export function SelectGifBtn({onClose, onSelectGif, disabled}: Props) { + const {_} = useLingui() + const control = useDialogControl() + const t = useTheme() + + const onPressSelectGif = useCallback(async () => { + logEvent('composer:gif:open', {}) + Keyboard.dismiss() + control.open() + }, [control]) + + return ( + <> + + + + + ) +} diff --git a/src/view/com/composer/photos/SelectPhotoBtn.tsx b/src/view/com/composer/photos/SelectPhotoBtn.tsx index f7fa9502d6..747653fc8d 100644 --- a/src/view/com/composer/photos/SelectPhotoBtn.tsx +++ b/src/view/com/composer/photos/SelectPhotoBtn.tsx @@ -1,27 +1,26 @@ +/* eslint-disable react-native-a11y/has-valid-accessibility-ignores-invert-colors */ import React, {useCallback} from 'react' -import {TouchableOpacity, StyleSheet} from 'react-native' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnalytics} from 'lib/analytics/analytics' -import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions' -import {GalleryModel} from 'state/models/media/gallery' -import {HITSLOP_10} from 'lib/constants' -import {isNative} from 'platform/detection' -import {useLingui} from '@lingui/react' import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useAnalytics} from '#/lib/analytics/analytics' +import {usePhotoLibraryPermission} from '#/lib/hooks/usePermissions' +import {isNative} from '#/platform/detection' +import {GalleryModel} from '#/state/models/media/gallery' +import {atoms as a, useTheme} from '#/alf' +import {Button} from '#/components/Button' +import {Image_Stroke2_Corner0_Rounded as Image} from '#/components/icons/Image' type Props = { gallery: GalleryModel + disabled?: boolean } -export function SelectPhotoBtn({gallery}: Props) { - const pal = usePalette('default') +export function SelectPhotoBtn({gallery, disabled}: Props) { const {track} = useAnalytics() const {_} = useLingui() const {requestPhotoAccessIfNeeded} = usePhotoLibraryPermission() + const t = useTheme() const onPressSelectPhotos = useCallback(async () => { track('Composer:GalleryOpened') @@ -34,25 +33,17 @@ export function SelectPhotoBtn({gallery}: Props) { }, [track, requestPhotoAccessIfNeeded, gallery]) return ( - - - + label={_(msg`Gallery`)} + accessibilityHint={_(msg`Opens device photo gallery`)} + style={a.p_sm} + variant="ghost" + shape="round" + color="primary" + disabled={disabled}> + + ) } - -const styles = StyleSheet.create({ - button: { - paddingHorizontal: 15, - }, -}) diff --git a/src/view/screens/Storybook/Buttons.tsx b/src/view/screens/Storybook/Buttons.tsx index cae8ec3144..b532b0dd16 100644 --- a/src/view/screens/Storybook/Buttons.tsx +++ b/src/view/screens/Storybook/Buttons.tsx @@ -9,7 +9,7 @@ import { ButtonText, ButtonVariant, } from '#/components/Button' -import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight' +import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/Arrow' import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' import {H1} from '#/components/Typography' diff --git a/src/view/screens/Storybook/Icons.tsx b/src/view/screens/Storybook/Icons.tsx index 9d7dc0aa8a..bff1fdc9b7 100644 --- a/src/view/screens/Storybook/Icons.tsx +++ b/src/view/screens/Storybook/Icons.tsx @@ -2,11 +2,11 @@ import React from 'react' import {View} from 'react-native' import {atoms as a, useTheme} from '#/alf' -import {H1} from '#/components/Typography' -import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' -import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight' +import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/Arrow' import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays' +import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' import {Loader} from '#/components/Loader' +import {H1} from '#/components/Typography' export function Icons() { const t = useTheme() From c91f065be5d3f9b34431cee7113367b085e84030 Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 18 Apr 2024 21:32:25 -0700 Subject: [PATCH 11/47] add dimensions to data (#3616) * add dimensions to data * keep alt text * put it in the right url * just send the original height and width instead --- src/view/com/composer/Composer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index f0f630dd46..77bec9bff8 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -333,7 +333,7 @@ export const ComposePost = observer(function ComposePost({ const onSelectGif = useCallback( (gif: Gif) => setExtLink({ - uri: gif.url, + uri: `${gif.url}?hh=${gif.images.original.height}&ww=${gif.images.original.width}`, isLoading: true, meta: { url: gif.url, From f709fbcbddde49a812197d79758482b6497be8d2 Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 18 Apr 2024 21:38:54 -0700 Subject: [PATCH 12/47] align center post meta in threaded (#3615) * align center post meta in threaded * put `displayNameStyle` in correct place * maybe? * with mobile padding too? --- src/view/com/post-thread/PostThreadItem.tsx | 8 +++++++- src/view/com/util/PostMeta.tsx | 6 ++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 089714c727..ddcf1a1306 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -27,6 +27,7 @@ import {sanitizeHandle} from 'lib/strings/handles' import {countLines, pluralize} from 'lib/strings/helpers' import {niceDate} from 'lib/strings/time' import {s} from 'lib/styles' +import {isWeb} from 'platform/detection' import {useSession} from 'state/session' import {PostThreadFollowBtn} from 'view/com/post-thread/PostThreadFollowBtn' import {atoms as a} from '#/alf' @@ -478,7 +479,12 @@ let PostThreadItemLoaded = ({ avatarSize={28} displayNameType="md-bold" displayNameStyle={isThreadedChild && s.ml2} - style={isThreadedChild && s.mb2} + style={ + isThreadedChild && { + alignItems: 'center', + paddingBottom: isWeb ? 5 : 2, + } + } /> { /> )} - + Date: Fri, 19 Apr 2024 15:23:47 +0100 Subject: [PATCH 13/47] [Statsig] Update experiments (#3617) --- src/lib/statsig/gates.ts | 10 ++++----- .../Profile/Header/ProfileHeaderStandard.tsx | 2 +- src/state/shell/selected-feed.tsx | 2 +- src/view/com/feeds/FeedPage.tsx | 22 +++++++------------ .../com/post-thread/PostThreadFollowBtn.tsx | 2 +- src/view/screens/Home.tsx | 18 +++++++++++---- 6 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 314799f288..d540cde10b 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -1,12 +1,12 @@ export type Gate = // Keep this alphabetic please. - | 'autoexpand_suggestions_on_profile_follow' - | 'disable_min_shell_on_foregrounding' - | 'disable_poll_on_discover' + | 'autoexpand_suggestions_on_profile_follow_v2' + | 'disable_min_shell_on_foregrounding_v2' + | 'disable_poll_on_discover_v2' | 'hide_vertical_scroll_indicators' | 'new_profile_scroll_component' | 'new_search' | 'receive_updates' - | 'show_follow_back_label' - | 'start_session_with_following' + | 'show_follow_back_label_v2' + | 'start_session_with_following_v2' | 'use_new_suggestions_endpoint' diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index 9e03613263..7c52bcbda7 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -94,7 +94,7 @@ let ProfileHeaderStandard = ({ )}`, ), ) - if (isWeb && gate('autoexpand_suggestions_on_profile_follow')) { + if (isWeb && gate('autoexpand_suggestions_on_profile_follow_v2')) { setShowSuggestedFollows(true) } } catch (e: any) { diff --git a/src/state/shell/selected-feed.tsx b/src/state/shell/selected-feed.tsx index dca3445f33..df50b3952f 100644 --- a/src/state/shell/selected-feed.tsx +++ b/src/state/shell/selected-feed.tsx @@ -27,7 +27,7 @@ function getInitialFeed(gate: (gateName: Gate) => boolean) { return feedFromSession } } - if (!gate('start_session_with_following')) { + if (!gate('start_session_with_following_v2')) { const feedFromPersisted = persisted.get('lastSelectedHomeFeed') if (feedFromPersisted) { // Fall back to the last chosen one across all tabs. diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx index 2b8fde632c..4ebf64da9a 100644 --- a/src/view/com/feeds/FeedPage.tsx +++ b/src/view/com/feeds/FeedPage.tsx @@ -104,17 +104,11 @@ export function FeedPage({ }) }, [scrollToTop, feed, queryClient, setHasNew]) - let feedPollInterval - if ( - feed === // Discover - 'feedgen|at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot' && - // TODO: This gate check is still too early. Move it to where the polling happens. - gate('disable_poll_on_discover') - ) { - feedPollInterval = undefined - } else { - feedPollInterval = POLL_FREQ - } + const isDiscoverFeed = + feed === + 'feedgen|at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot' + const adjustedHasNew = + hasNew && !(isDiscoverFeed && gate('disable_poll_on_discover_v2')) return ( @@ -124,7 +118,7 @@ export function FeedPage({ enabled={isPageFocused} feed={feed} feedParams={feedParams} - pollInterval={feedPollInterval} + pollInterval={POLL_FREQ} disablePoll={hasNew} scrollElRef={scrollElRef} onScrolledDownChange={setIsScrolledDown} @@ -134,11 +128,11 @@ export function FeedPage({ headerOffset={headerOffset} /> - {(isScrolledDown || hasNew) && ( + {(isScrolledDown || adjustedHasNew) && ( )} diff --git a/src/view/com/post-thread/PostThreadFollowBtn.tsx b/src/view/com/post-thread/PostThreadFollowBtn.tsx index 7c9a544515..1f70f41c4a 100644 --- a/src/view/com/post-thread/PostThreadFollowBtn.tsx +++ b/src/view/com/post-thread/PostThreadFollowBtn.tsx @@ -140,7 +140,7 @@ function PostThreadFollowBtnLoaded({ style={[!isFollowing ? palInverted.text : pal.text, s.bold]} numberOfLines={1}> {!isFollowing ? ( - isFollowedBy && gate('show_follow_back_label') ? ( + isFollowedBy && gate('show_follow_back_label_v2') ? ( Follow Back ) : ( Follow diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index fbaa49a321..3eaa1b8757 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -5,6 +5,7 @@ import {useFocusEffect} from '@react-navigation/native' import {PROD_DEFAULT_FEED} from '#/lib/constants' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useSetTitle} from '#/lib/hooks/useSetTitle' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {logEvent, LogEvents, useGate} from '#/lib/statsig/statsig' import {emitSoftReset} from '#/state/events' import {FeedSourceInfo, usePinnedFeedsInfos} from '#/state/queries/feed' @@ -12,7 +13,11 @@ import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed' import {usePreferencesQuery} from '#/state/queries/preferences' import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types' import {useSession} from '#/state/session' -import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell' +import { + useMinimalShellMode, + useSetDrawerSwipeDisabled, + useSetMinimalShellMode, +} from '#/state/shell' import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed' import {useOTAUpdates} from 'lib/hooks/useOTAUpdates' import {HomeTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' @@ -112,11 +117,16 @@ function HomeScreenReady({ ) const gate = useGate() + const mode = useMinimalShellMode() + const {isMobile} = useWebMediaQueries() React.useEffect(() => { const listener = AppState.addEventListener('change', nextAppState => { if (nextAppState === 'active') { - // TODO: Check if minimal shell is on before logging an exposure. - if (gate('disable_min_shell_on_foregrounding')) { + if ( + isMobile && + mode.value === 1 && + gate('disable_min_shell_on_foregrounding_v2') + ) { setMinimalShellMode(false) } } @@ -124,7 +134,7 @@ function HomeScreenReady({ return () => { listener.remove() } - }, [setMinimalShellMode, gate]) + }, [setMinimalShellMode, mode, isMobile, gate]) const onPageSelected = React.useCallback( (index: number) => { From c42a557417960a8eed7eb132c24790f4a94cff37 Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 19 Apr 2024 15:51:41 +0100 Subject: [PATCH 14/47] [Statsig] Send locale info (#3621) --- src/lib/statsig/statsig.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx index c43d2bf8a3..2e3fdfd5cb 100644 --- a/src/lib/statsig/statsig.tsx +++ b/src/lib/statsig/statsig.tsx @@ -6,6 +6,7 @@ import {Statsig, StatsigProvider} from 'statsig-react-native-expo' import {logger} from '#/logger' import {isWeb} from '#/platform/detection' +import * as persisted from '#/state/persisted' import {IS_TESTFLIGHT} from 'lib/app-info' import {useSession} from '../../state/session' import {timeout} from '../async/timeout' @@ -23,6 +24,8 @@ type StatsigUser = { platform: 'ios' | 'android' | 'web' refSrc: string refUrl: string + appLanguage: string + contentLanguages: string[] } } @@ -132,6 +135,7 @@ function toStatsigUser(did: string | undefined): StatsigUser { if (did) { userID = sha256(did) } + const languagePrefs = persisted.get('languagePrefs') return { userID, platform: Platform.OS as 'ios' | 'android' | 'web', @@ -139,6 +143,8 @@ function toStatsigUser(did: string | undefined): StatsigUser { refSrc, refUrl, platform: Platform.OS as 'ios' | 'android' | 'web', + appLanguage: languagePrefs.appLanguage, + contentLanguages: languagePrefs.contentLanguages, }, } } From ade2ea6172a71d60bd7ce6aed97d09dbddb352d0 Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 19 Apr 2024 21:58:18 +0100 Subject: [PATCH 15/47] Ungate Top/Latest search (#3627) --- src/lib/statsig/gates.ts | 1 - src/view/screens/Search/Search.tsx | 105 +++++++++++------------------ 2 files changed, 38 insertions(+), 68 deletions(-) diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index d540cde10b..301746fb21 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -5,7 +5,6 @@ export type Gate = | 'disable_poll_on_discover_v2' | 'hide_vertical_scroll_indicators' | 'new_profile_scroll_component' - | 'new_search' | 'receive_updates' | 'show_follow_back_label_v2' | 'start_session_with_following_v2' diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index 0b11ff767b..36e780c77c 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -407,7 +407,6 @@ export function SearchScreenInner({ const {isDesktop} = useWebMediaQueries() const [activeTab, setActiveTab] = React.useState(0) const {_} = useLingui() - const gate = useGate() const onPageSelected = React.useCallback( (index: number) => { @@ -420,74 +419,46 @@ export function SearchScreenInner({ const sections = React.useMemo(() => { if (!query) return [] - if (gate('new_search')) { - if (hasSession) { - return [ - { - title: _(msg`Top`), - component: ( - - ), - }, - { - title: _(msg`Latest`), - component: ( - - ), - }, - { - title: _(msg`People`), - component: ( - - ), - }, - ] - } else { - return [ - { - title: _(msg`People`), - component: ( - - ), - }, - ] - } + if (hasSession) { + return [ + { + title: _(msg`Top`), + component: ( + + ), + }, + { + title: _(msg`Latest`), + component: ( + + ), + }, + { + title: _(msg`People`), + component: ( + + ), + }, + ] } else { - if (hasSession) { - return [ - { - title: _(msg`Posts`), - component: ( - - ), - }, - { - title: _(msg`Users`), - component: ( - - ), - }, - ] - } else { - return [ - { - title: _(msg`Users`), - component: ( - - ), - }, - ] - } + return [ + { + title: _(msg`People`), + component: ( + + ), + }, + ] } - }, [hasSession, gate, _, query, activeTab]) + }, [hasSession, _, query, activeTab]) if (hasSession) { return query ? ( From 8b33ffdfb5ca606708c8104ecad4fa5430268483 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 19 Apr 2024 22:10:37 +0100 Subject: [PATCH 16/47] Add disable autoplay preference and group related settings into a dedicated page (#3626) * add autoplay preference * group accessibility settings into a dedicated page * fix gray background on web * Put a11y first --------- Co-authored-by: Dan Abramov --- bskyweb/cmd/bskyweb/server.go | 1 + src/Navigation.tsx | 9 ++ src/lib/routes/types.ts | 1 + src/routes.ts | 1 + src/state/persisted/schema.ts | 2 + src/state/preferences/autoplay.tsx | 42 +++++++ src/state/preferences/index.tsx | 11 +- src/view/icons/index.tsx | 75 ++++++------ src/view/screens/AccessibilitySettings.tsx | 132 +++++++++++++++++++++ src/view/screens/Settings/index.tsx | 71 +++++------ 10 files changed, 263 insertions(+), 82 deletions(-) create mode 100644 src/state/preferences/autoplay.tsx create mode 100644 src/view/screens/AccessibilitySettings.tsx diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 17d6210148..cc2ed54264 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -190,6 +190,7 @@ func serve(cctx *cli.Context) error { e.GET("/settings/saved-feeds", server.WebGeneric) e.GET("/settings/threads", server.WebGeneric) e.GET("/settings/external-embeds", server.WebGeneric) + e.GET("/settings/accessibility", server.WebGeneric) e.GET("/sys/debug", server.WebGeneric) e.GET("/sys/debug-mod", server.WebGeneric) e.GET("/sys/log", server.WebGeneric) diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 99c0ebf3c3..363875dcb6 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -53,6 +53,7 @@ import { setEmailConfirmationRequested, shouldRequestEmailConfirmation, } from './state/shell/reminders' +import {AccessibilitySettingsScreen} from './view/screens/AccessibilitySettings' import {CommunityGuidelinesScreen} from './view/screens/CommunityGuidelines' import {CopyrightPolicyScreen} from './view/screens/CopyrightPolicy' import {DebugModScreen} from './view/screens/DebugMod' @@ -276,6 +277,14 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { requireAuth: true, }} /> + AccessibilitySettingsScreen} + options={{ + title: title(msg`Accessibility Settings`), + requireAuth: true, + }} + /> HashtagScreen} diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 95af2f237d..ac5cb0bce0 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -35,6 +35,7 @@ export type CommonNavigatorParams = { PreferencesFollowingFeed: undefined PreferencesThreads: undefined PreferencesExternalEmbeds: undefined + AccessibilitySettings: undefined Search: {q?: string} Hashtag: {tag: string; author?: string} } diff --git a/src/routes.ts b/src/routes.ts index f6f3729475..f27839698f 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -29,6 +29,7 @@ export const router = new Router({ PreferencesFollowingFeed: '/settings/following-feed', PreferencesThreads: '/settings/threads', PreferencesExternalEmbeds: '/settings/external-embeds', + AccessibilitySettings: '/settings/accessibility', SavedFeeds: '/settings/saved-feeds', Support: '/support', PrivacyPolicy: '/support/privacy', diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 67e082a95d..1b77d138bd 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -60,6 +60,7 @@ export const schema = z.object({ lastSelectedHomeFeed: z.string().optional(), pdsAddressHistory: z.array(z.string()).optional(), disableHaptics: z.boolean().optional(), + disableAutoplay: z.boolean().optional(), }) export type Schema = z.infer @@ -96,4 +97,5 @@ export const defaults: Schema = { lastSelectedHomeFeed: undefined, pdsAddressHistory: [], disableHaptics: false, + disableAutoplay: false, } diff --git a/src/state/preferences/autoplay.tsx b/src/state/preferences/autoplay.tsx new file mode 100644 index 0000000000..d5aa049f36 --- /dev/null +++ b/src/state/preferences/autoplay.tsx @@ -0,0 +1,42 @@ +import React from 'react' + +import * as persisted from '#/state/persisted' + +type StateContext = boolean +type SetContext = (v: boolean) => void + +const stateContext = React.createContext( + Boolean(persisted.defaults.disableAutoplay), +) +const setContext = React.createContext((_: boolean) => {}) + +export function Provider({children}: {children: React.ReactNode}) { + const [state, setState] = React.useState( + Boolean(persisted.get('disableAutoplay')), + ) + + const setStateWrapped = React.useCallback( + (autoplayDisabled: persisted.Schema['disableAutoplay']) => { + setState(Boolean(autoplayDisabled)) + persisted.write('disableAutoplay', autoplayDisabled) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate(() => { + setState(Boolean(persisted.get('disableAutoplay'))) + }) + }, [setStateWrapped]) + + return ( + + + {children} + + + ) +} + +export const useAutoplayDisabled = () => React.useContext(stateContext) +export const useSetAutoplayDisabled = () => React.useContext(setContext) diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index 804d0fc310..5c8fab2ad7 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -1,9 +1,10 @@ import React from 'react' -import {Provider as AltTextRequiredProvider} from '../preferences/alt-text-required' -import {Provider as HiddenPostsProvider} from '../preferences/hidden-posts' +import {Provider as AltTextRequiredProvider} from './alt-text-required' +import {Provider as AutoplayProvider} from './autoplay' import {Provider as DisableHapticsProvider} from './disable-haptics' import {Provider as ExternalEmbedsProvider} from './external-embeds-prefs' +import {Provider as HiddenPostsProvider} from './hidden-posts' import {Provider as InAppBrowserProvider} from './in-app-browser' import {Provider as LanguagesProvider} from './languages' @@ -11,6 +12,8 @@ export { useRequireAltTextEnabled, useSetRequireAltTextEnabled, } from './alt-text-required' +export {useAutoplayDisabled, useSetAutoplayDisabled} from './autoplay' +export {useHapticsDisabled, useSetHapticsDisabled} from './disable-haptics' export { useExternalEmbedsPrefs, useSetExternalEmbedPref, @@ -26,7 +29,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { - {children} + + {children} + diff --git a/src/view/icons/index.tsx b/src/view/icons/index.tsx index ede1e63355..b9af6a519c 100644 --- a/src/view/icons/index.tsx +++ b/src/view/icons/index.tsx @@ -1,63 +1,72 @@ import {library} from '@fortawesome/fontawesome-svg-core' - import {faAddressCard} from '@fortawesome/free-regular-svg-icons' +import {faBell as farBell} from '@fortawesome/free-regular-svg-icons/faBell' +import {faBookmark as farBookmark} from '@fortawesome/free-regular-svg-icons/faBookmark' +import {faCalendar as farCalendar} from '@fortawesome/free-regular-svg-icons/faCalendar' +import {faCircle} from '@fortawesome/free-regular-svg-icons/faCircle' +import {faCircleCheck as farCircleCheck} from '@fortawesome/free-regular-svg-icons/faCircleCheck' +import {faCirclePlay} from '@fortawesome/free-regular-svg-icons/faCirclePlay' +import {faCircleUser} from '@fortawesome/free-regular-svg-icons/faCircleUser' +import {faClone as farClone} from '@fortawesome/free-regular-svg-icons/faClone' +import {faComment} from '@fortawesome/free-regular-svg-icons/faComment' +import {faComments} from '@fortawesome/free-regular-svg-icons/faComments' +import {faCompass} from '@fortawesome/free-regular-svg-icons/faCompass' +import {faEyeSlash as farEyeSlash} from '@fortawesome/free-regular-svg-icons/faEyeSlash' +import {faFaceSmile} from '@fortawesome/free-regular-svg-icons/faFaceSmile' +import {faFloppyDisk} from '@fortawesome/free-regular-svg-icons/faFloppyDisk' +import {faHand as farHand} from '@fortawesome/free-regular-svg-icons/faHand' +import {faHeart} from '@fortawesome/free-regular-svg-icons/faHeart' +import {faImage as farImage} from '@fortawesome/free-regular-svg-icons/faImage' +import {faMessage} from '@fortawesome/free-regular-svg-icons/faMessage' +import {faPaste} from '@fortawesome/free-regular-svg-icons/faPaste' +import {faSquare} from '@fortawesome/free-regular-svg-icons/faSquare' +import {faSquareCheck} from '@fortawesome/free-regular-svg-icons/faSquareCheck' +import {faSquarePlus} from '@fortawesome/free-regular-svg-icons/faSquarePlus' +import {faTrashCan} from '@fortawesome/free-regular-svg-icons/faTrashCan' +import {faUser} from '@fortawesome/free-regular-svg-icons/faUser' +import {faFlask} from '@fortawesome/free-solid-svg-icons' +import {faUniversalAccess} from '@fortawesome/free-solid-svg-icons' import {faAngleDown} from '@fortawesome/free-solid-svg-icons/faAngleDown' import {faAngleLeft} from '@fortawesome/free-solid-svg-icons/faAngleLeft' import {faAngleRight} from '@fortawesome/free-solid-svg-icons/faAngleRight' import {faAngleUp} from '@fortawesome/free-solid-svg-icons/faAngleUp' +import {faArrowDown} from '@fortawesome/free-solid-svg-icons/faArrowDown' import {faArrowLeft} from '@fortawesome/free-solid-svg-icons/faArrowLeft' import {faArrowRight} from '@fortawesome/free-solid-svg-icons/faArrowRight' -import {faArrowUp} from '@fortawesome/free-solid-svg-icons/faArrowUp' -import {faArrowDown} from '@fortawesome/free-solid-svg-icons/faArrowDown' import {faArrowRightFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowRightFromBracket' +import {faArrowRotateLeft} from '@fortawesome/free-solid-svg-icons/faArrowRotateLeft' +import {faArrowsRotate} from '@fortawesome/free-solid-svg-icons/faArrowsRotate' +import {faArrowTrendUp} from '@fortawesome/free-solid-svg-icons/faArrowTrendUp' +import {faArrowUp} from '@fortawesome/free-solid-svg-icons/faArrowUp' import {faArrowUpFromBracket} from '@fortawesome/free-solid-svg-icons/faArrowUpFromBracket' import {faArrowUpRightFromSquare} from '@fortawesome/free-solid-svg-icons/faArrowUpRightFromSquare' -import {faArrowRotateLeft} from '@fortawesome/free-solid-svg-icons/faArrowRotateLeft' -import {faArrowTrendUp} from '@fortawesome/free-solid-svg-icons/faArrowTrendUp' -import {faArrowsRotate} from '@fortawesome/free-solid-svg-icons/faArrowsRotate' import {faAt} from '@fortawesome/free-solid-svg-icons/faAt' -import {faBars} from '@fortawesome/free-solid-svg-icons/faBars' import {faBan} from '@fortawesome/free-solid-svg-icons/faBan' +import {faBars} from '@fortawesome/free-solid-svg-icons/faBars' import {faBell} from '@fortawesome/free-solid-svg-icons/faBell' -import {faBell as farBell} from '@fortawesome/free-regular-svg-icons/faBell' import {faBookmark} from '@fortawesome/free-solid-svg-icons/faBookmark' -import {faBookmark as farBookmark} from '@fortawesome/free-regular-svg-icons/faBookmark' -import {faCalendar as farCalendar} from '@fortawesome/free-regular-svg-icons/faCalendar' import {faCamera} from '@fortawesome/free-solid-svg-icons/faCamera' import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck' +import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown' import {faChevronRight} from '@fortawesome/free-solid-svg-icons/faChevronRight' -import {faCircle} from '@fortawesome/free-regular-svg-icons/faCircle' -import {faCircleCheck as farCircleCheck} from '@fortawesome/free-regular-svg-icons/faCircleCheck' import {faCircleCheck} from '@fortawesome/free-solid-svg-icons/faCircleCheck' import {faCircleDot} from '@fortawesome/free-solid-svg-icons/faCircleDot' import {faCircleExclamation} from '@fortawesome/free-solid-svg-icons/faCircleExclamation' -import {faCirclePlay} from '@fortawesome/free-regular-svg-icons/faCirclePlay' -import {faCircleUser} from '@fortawesome/free-regular-svg-icons/faCircleUser' import {faClone} from '@fortawesome/free-solid-svg-icons/faClone' -import {faClone as farClone} from '@fortawesome/free-regular-svg-icons/faClone' -import {faComment} from '@fortawesome/free-regular-svg-icons/faComment' import {faCommentSlash} from '@fortawesome/free-solid-svg-icons/faCommentSlash' -import {faComments} from '@fortawesome/free-regular-svg-icons/faComments' -import {faCompass} from '@fortawesome/free-regular-svg-icons/faCompass' import {faDownload} from '@fortawesome/free-solid-svg-icons/faDownload' import {faEllipsis} from '@fortawesome/free-solid-svg-icons/faEllipsis' import {faEnvelope} from '@fortawesome/free-solid-svg-icons/faEnvelope' import {faExclamation} from '@fortawesome/free-solid-svg-icons/faExclamation' import {faEye} from '@fortawesome/free-solid-svg-icons/faEye' -import {faEyeSlash as farEyeSlash} from '@fortawesome/free-regular-svg-icons/faEyeSlash' -import {faFaceSmile} from '@fortawesome/free-regular-svg-icons/faFaceSmile' +import {faFilter} from '@fortawesome/free-solid-svg-icons/faFilter' import {faFire} from '@fortawesome/free-solid-svg-icons/faFire' -import {faFlask} from '@fortawesome/free-solid-svg-icons' -import {faFloppyDisk} from '@fortawesome/free-regular-svg-icons/faFloppyDisk' import {faGear} from '@fortawesome/free-solid-svg-icons/faGear' import {faGlobe} from '@fortawesome/free-solid-svg-icons/faGlobe' import {faHand} from '@fortawesome/free-solid-svg-icons/faHand' -import {faHand as farHand} from '@fortawesome/free-regular-svg-icons/faHand' import {faHashtag} from '@fortawesome/free-solid-svg-icons/faHashtag' -import {faHeart} from '@fortawesome/free-regular-svg-icons/faHeart' import {faHeart as fasHeart} from '@fortawesome/free-solid-svg-icons/faHeart' import {faHouse} from '@fortawesome/free-solid-svg-icons/faHouse' -import {faImage as farImage} from '@fortawesome/free-regular-svg-icons/faImage' import {faImage} from '@fortawesome/free-solid-svg-icons/faImage' import {faInfo} from '@fortawesome/free-solid-svg-icons/faInfo' import {faLanguage} from '@fortawesome/free-solid-svg-icons/faLanguage' @@ -66,10 +75,8 @@ import {faList} from '@fortawesome/free-solid-svg-icons/faList' import {faListUl} from '@fortawesome/free-solid-svg-icons/faListUl' import {faLock} from '@fortawesome/free-solid-svg-icons/faLock' import {faMagnifyingGlass} from '@fortawesome/free-solid-svg-icons/faMagnifyingGlass' -import {faMessage} from '@fortawesome/free-regular-svg-icons/faMessage' import {faNoteSticky} from '@fortawesome/free-solid-svg-icons/faNoteSticky' import {faPause} from '@fortawesome/free-solid-svg-icons/faPause' -import {faPaste} from '@fortawesome/free-regular-svg-icons/faPaste' import {faPen} from '@fortawesome/free-solid-svg-icons/faPen' import {faPenNib} from '@fortawesome/free-solid-svg-icons/faPenNib' import {faPenToSquare} from '@fortawesome/free-solid-svg-icons/faPenToSquare' @@ -87,23 +94,16 @@ import {faShareFromSquare} from '@fortawesome/free-solid-svg-icons/faShareFromSq import {faShield} from '@fortawesome/free-solid-svg-icons/faShield' import {faSignal} from '@fortawesome/free-solid-svg-icons/faSignal' import {faSliders} from '@fortawesome/free-solid-svg-icons/faSliders' -import {faSquare} from '@fortawesome/free-regular-svg-icons/faSquare' -import {faSquareCheck} from '@fortawesome/free-regular-svg-icons/faSquareCheck' -import {faSquarePlus} from '@fortawesome/free-regular-svg-icons/faSquarePlus' import {faThumbtack} from '@fortawesome/free-solid-svg-icons/faThumbtack' import {faTicket} from '@fortawesome/free-solid-svg-icons/faTicket' -import {faTrashCan} from '@fortawesome/free-regular-svg-icons/faTrashCan' -import {faUser} from '@fortawesome/free-regular-svg-icons/faUser' -import {faUsers} from '@fortawesome/free-solid-svg-icons/faUsers' import {faUserCheck} from '@fortawesome/free-solid-svg-icons/faUserCheck' -import {faUserSlash} from '@fortawesome/free-solid-svg-icons/faUserSlash' import {faUserPlus} from '@fortawesome/free-solid-svg-icons/faUserPlus' -import {faUserXmark} from '@fortawesome/free-solid-svg-icons/faUserXmark' +import {faUsers} from '@fortawesome/free-solid-svg-icons/faUsers' +import {faUserSlash} from '@fortawesome/free-solid-svg-icons/faUserSlash' import {faUsersSlash} from '@fortawesome/free-solid-svg-icons/faUsersSlash' +import {faUserXmark} from '@fortawesome/free-solid-svg-icons/faUserXmark' import {faX} from '@fortawesome/free-solid-svg-icons/faX' import {faXmark} from '@fortawesome/free-solid-svg-icons/faXmark' -import {faChevronDown} from '@fortawesome/free-solid-svg-icons/faChevronDown' -import {faFilter} from '@fortawesome/free-solid-svg-icons/faFilter' library.add( faAddressCard, @@ -196,6 +196,7 @@ library.add( faSquare, faSquareCheck, faSquarePlus, + faUniversalAccess, faUser, faUsers, faUserCheck, diff --git a/src/view/screens/AccessibilitySettings.tsx b/src/view/screens/AccessibilitySettings.tsx new file mode 100644 index 0000000000..ac0d985f10 --- /dev/null +++ b/src/view/screens/AccessibilitySettings.tsx @@ -0,0 +1,132 @@ +import React from 'react' +import {StyleSheet, View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useFocusEffect} from '@react-navigation/native' + +import {isNative} from '#/platform/detection' +import {useSetMinimalShellMode} from '#/state/shell' +import {useAnalytics} from 'lib/analytics/analytics' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' +import {s} from 'lib/styles' +import { + useAutoplayDisabled, + useHapticsDisabled, + useRequireAltTextEnabled, + useSetAutoplayDisabled, + useSetHapticsDisabled, + useSetRequireAltTextEnabled, +} from 'state/preferences' +import {ToggleButton} from 'view/com/util/forms/ToggleButton' +import {SimpleViewHeader} from '../com/util/SimpleViewHeader' +import {Text} from '../com/util/text/Text' +import {ScrollView} from '../com/util/Views' + +type Props = NativeStackScreenProps< + CommonNavigatorParams, + 'AccessibilitySettings' +> +export function AccessibilitySettingsScreen({}: Props) { + const pal = usePalette('default') + const setMinimalShellMode = useSetMinimalShellMode() + const {screen} = useAnalytics() + const {isMobile} = useWebMediaQueries() + const {_} = useLingui() + + const requireAltTextEnabled = useRequireAltTextEnabled() + const setRequireAltTextEnabled = useSetRequireAltTextEnabled() + const autoplayDisabled = useAutoplayDisabled() + const setAutoplayDisabled = useSetAutoplayDisabled() + const hapticsDisabled = useHapticsDisabled() + const setHapticsDisabled = useSetHapticsDisabled() + + useFocusEffect( + React.useCallback(() => { + screen('PreferencesExternalEmbeds') + setMinimalShellMode(false) + }, [screen, setMinimalShellMode]), + ) + + return ( + + + + + Accessibility Settings + + + + + + Alt text + + + setRequireAltTextEnabled(!requireAltTextEnabled)} + /> + + + Media + + + setAutoplayDisabled(!autoplayDisabled)} + /> + + {isNative && ( + <> + + Haptics + + + setHapticsDisabled(!hapticsDisabled)} + /> + + + )} + + + ) +} + +const styles = StyleSheet.create({ + heading: { + paddingHorizontal: 18, + paddingTop: 14, + paddingBottom: 6, + }, + toggleCard: { + paddingVertical: 8, + paddingHorizontal: 6, + marginBottom: 1, + }, +}) diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx index b97faafad1..bb38da676c 100644 --- a/src/view/screens/Settings/index.tsx +++ b/src/view/screens/Settings/index.tsx @@ -20,14 +20,10 @@ import {useLingui} from '@lingui/react' import {useFocusEffect, useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' -import {isIOS, isNative} from '#/platform/detection' +import {isNative} from '#/platform/detection' import {useModalControls} from '#/state/modals' import {clearLegacyStorage} from '#/state/persisted/legacy' import {clear as clearStorage} from '#/state/persisted/store' -import { - useRequireAltTextEnabled, - useSetRequireAltTextEnabled, -} from '#/state/preferences' import { useInAppBrowser, useSetInAppBrowser, @@ -56,10 +52,6 @@ import {makeProfileLink} from 'lib/routes/links' import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types' import {colors, s} from 'lib/styles' -import { - useHapticsDisabled, - useSetHapticsDisabled, -} from 'state/preferences/disable-haptics' import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn' import {SelectableBtn} from 'view/com/util/forms/SelectableBtn' import {ToggleButton} from 'view/com/util/forms/ToggleButton' @@ -162,12 +154,8 @@ export function SettingsScreen({}: Props) { const pal = usePalette('default') const {_} = useLingui() const setMinimalShellMode = useSetMinimalShellMode() - const requireAltTextEnabled = useRequireAltTextEnabled() - const setRequireAltTextEnabled = useSetRequireAltTextEnabled() const inAppBrowserPref = useInAppBrowser() const setUseInAppBrowser = useSetInAppBrowser() - const isHapticsDisabled = useHapticsDisabled() - const setHapticsDisabled = useSetHapticsDisabled() const onboardingDispatch = useOnboardingDispatch() const navigation = useNavigation() const {isMobile} = useWebMediaQueries() @@ -282,6 +270,10 @@ export function SettingsScreen({}: Props) { navigation.navigate('SavedFeeds') }, [navigation]) + const onPressAccessibilitySettings = React.useCallback(() => { + navigation.navigate('AccessibilitySettings') + }, [navigation]) + const onPressStatusPage = React.useCallback(() => { Linking.openURL(STATUS_PAGE_URL) }, []) @@ -318,7 +310,7 @@ export function SettingsScreen({}: Props) { - - Accessibility - - - setRequireAltTextEnabled(!requireAltTextEnabled)} - /> - - - - Appearance @@ -492,6 +469,29 @@ export function SettingsScreen({}: Props) { Basics + + + + + + Accessibility + + )} - {isNative && ( - - setHapticsDisabled(!isHapticsDisabled)} - /> - - )} Account From edbb18afa44bcfee4560ac19880a0c32563b6a0e Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 19 Apr 2024 22:55:53 +0100 Subject: [PATCH 17/47] Throttle gif search by 500ms (#3622) * debounce gif search by 300ms * Throttle it instead --------- Co-authored-by: Dan Abramov --- src/components/dialogs/GifSelect.tsx | 11 +++------ src/components/hooks/useThrottledValue.ts | 27 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 src/components/hooks/useThrottledValue.ts diff --git a/src/components/dialogs/GifSelect.tsx b/src/components/dialogs/GifSelect.tsx index 92e21af47d..c8897da364 100644 --- a/src/components/dialogs/GifSelect.tsx +++ b/src/components/dialogs/GifSelect.tsx @@ -1,10 +1,4 @@ -import React, { - useCallback, - useDeferredValue, - useMemo, - useRef, - useState, -} from 'react' +import React, {useCallback, useMemo, useRef, useState} from 'react' import {TextInput, View} from 'react-native' import {Image} from 'expo-image' import {msg, Trans} from '@lingui/macro' @@ -22,6 +16,7 @@ import {Gif, useGifphySearch, useGiphyTrending} from '#/state/queries/giphy' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import * as Dialog from '#/components/Dialog' import * as TextField from '#/components/forms/TextField' +import {useThrottledValue} from '#/components/hooks/useThrottledValue' import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow' import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2' import {InlineLinkText} from '#/components/Link' @@ -82,7 +77,7 @@ function GifList({ const {gtMobile} = useBreakpoints() const ref = useRef(null) const [undeferredSearch, setSearch] = useState('') - const search = useDeferredValue(undeferredSearch) + const search = useThrottledValue(undeferredSearch, 500) const isSearching = search.length > 0 diff --git a/src/components/hooks/useThrottledValue.ts b/src/components/hooks/useThrottledValue.ts new file mode 100644 index 0000000000..5764c547e4 --- /dev/null +++ b/src/components/hooks/useThrottledValue.ts @@ -0,0 +1,27 @@ +import {useEffect, useRef, useState} from 'react' + +import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' + +export function useThrottledValue(value: T, time?: number) { + const pendingValueRef = useRef(value) + const [throttledValue, setThrottledValue] = useState(value) + + useEffect(() => { + pendingValueRef.current = value + }, [value]) + + const handleTick = useNonReactiveCallback(() => { + if (pendingValueRef.current !== throttledValue) { + setThrottledValue(pendingValueRef.current) + } + }) + + useEffect(() => { + const id = setInterval(handleTick, time) + return () => { + clearInterval(id) + } + }, [handleTick, time]) + + return throttledValue +} From 22e86c99033a55881bf02a3129b653a13bac619e Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Fri, 19 Apr 2024 23:26:04 +0100 Subject: [PATCH 18/47] fix onEndReached issue by forcing flatlist to scroll (#3623) --- src/components/Dialog/index.web.tsx | 16 ++++++++++++++-- src/components/dialogs/GifSelect.tsx | 6 +++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx index d00d2d832b..a086955db6 100644 --- a/src/components/Dialog/index.web.tsx +++ b/src/components/Dialog/index.web.tsx @@ -199,11 +199,23 @@ export const ScrollableInner = Inner export function InnerFlatList({ label, + style, ...props }: FlatListProps & {label: string}) { + const {gtMobile} = useBreakpoints() return ( - - + + ) } diff --git a/src/components/dialogs/GifSelect.tsx b/src/components/dialogs/GifSelect.tsx index c8897da364..baff31168b 100644 --- a/src/components/dialogs/GifSelect.tsx +++ b/src/components/dialogs/GifSelect.tsx @@ -147,7 +147,11 @@ function GifList({ From c0ca891501cbc60eb945e3235800ec1e29a15ccd Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 19 Apr 2024 15:31:20 -0700 Subject: [PATCH 19/47] Player improvement pre-reqs (#3618) * add dims to type * save * add the dimensions to the embed info * add a new case * add a new case * limit this case to giphy * use gate * flip mp4/webp * fix tests * add new test --- __tests__/lib/string.test.ts | 56 ++++++++++++------- src/lib/statsig/gates.ts | 1 + src/lib/strings/embed-player.ts | 28 ++++++++-- .../util/post-embeds/ExternalLinkEmbed.tsx | 47 ++++++++++------ 4 files changed, 89 insertions(+), 43 deletions(-) diff --git a/__tests__/lib/string.test.ts b/__tests__/lib/string.test.ts index f003e5acc0..eeb5ae1572 100644 --- a/__tests__/lib/string.test.ts +++ b/__tests__/lib/string.test.ts @@ -1,17 +1,18 @@ import {RichText} from '@atproto/api' + +import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player' +import {cleanError} from '../../src/lib/strings/errors' +import {createFullHandle, makeValidHandle} from '../../src/lib/strings/handles' +import {enforceLen, pluralize} from '../../src/lib/strings/helpers' +import {detectLinkables} from '../../src/lib/strings/rich-text-detection' +import {shortenLinks} from '../../src/lib/strings/rich-text-manip' +import {ago} from '../../src/lib/strings/time' import { makeRecordUri, toNiceDomain, - toShortUrl, toShareUrl, + toShortUrl, } from '../../src/lib/strings/url-helpers' -import {pluralize, enforceLen} from '../../src/lib/strings/helpers' -import {ago} from '../../src/lib/strings/time' -import {detectLinkables} from '../../src/lib/strings/rich-text-detection' -import {shortenLinks} from '../../src/lib/strings/rich-text-manip' -import {makeValidHandle, createFullHandle} from '../../src/lib/strings/handles' -import {cleanError} from '../../src/lib/strings/errors' -import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player' describe('detectLinkables', () => { const inputs = [ @@ -434,6 +435,8 @@ describe('parseEmbedPlayerFromUrl', () => { 'https://giphy.com/gif/some-random-gif-name-gifId', 'https://giphy.com/gifs/', + 'https://giphy.com/gifs/39248209509382934029?hh=100&ww=100', + 'https://media.giphy.com/media/gifId/giphy.webp', 'https://media0.giphy.com/media/gifId/giphy.webp', 'https://media1.giphy.com/media/gifId/giphy.gif', @@ -621,18 +624,31 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, undefined, undefined, + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/39248209509382934029', + playerUri: 'https://i.giphy.com/media/39248209509382934029/200.mp4', + dimensions: { + width: 100, + height: 100, + }, + }, + { type: 'giphy_gif', source: 'giphy', isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -640,7 +656,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -648,7 +664,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -656,7 +672,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -664,7 +680,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -672,7 +688,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, undefined, undefined, @@ -684,7 +700,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { @@ -693,7 +709,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -701,7 +717,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -709,7 +725,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { type: 'giphy_gif', @@ -717,7 +733,7 @@ describe('parseEmbedPlayerFromUrl', () => { isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/giphy.webp', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', }, { diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 301746fb21..e42e9efe1e 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -4,6 +4,7 @@ export type Gate = | 'disable_min_shell_on_foregrounding_v2' | 'disable_poll_on_discover_v2' | 'hide_vertical_scroll_indicators' + | 'new_gif_player' | 'new_profile_scroll_component' | 'receive_updates' | 'show_follow_back_label_v2' diff --git a/src/lib/strings/embed-player.ts b/src/lib/strings/embed-player.ts index ee73284785..bbc58a2063 100644 --- a/src/lib/strings/embed-player.ts +++ b/src/lib/strings/embed-player.ts @@ -1,4 +1,5 @@ import {Dimensions} from 'react-native' + import {isWeb} from 'platform/detection' const {height: SCREEN_HEIGHT} = Dimensions.get('window') @@ -60,6 +61,10 @@ export interface EmbedPlayerParams { source: EmbedPlayerSource metaUri?: string hideDetails?: boolean + dimensions?: { + height: number + width: number + } } const giphyRegex = /media(?:[0-4]\.giphy\.com|\.giphy\.com)/i @@ -250,6 +255,16 @@ export function parseEmbedPlayerFromUrl( if (urlp.hostname === 'giphy.com' || urlp.hostname === 'www.giphy.com') { const [_, gifs, nameAndId] = urlp.pathname.split('/') + const h = urlp.searchParams.get('hh') + const w = urlp.searchParams.get('ww') + let dimensions + if (h && w) { + dimensions = { + height: Number(h), + width: Number(w), + } + } + /* * nameAndId is a string that consists of the name (dash separated) and the id of the gif (the last part of the name) * We want to get the id of the gif, then direct to media.giphy.com/media/{id}/giphy.webp so we can @@ -266,7 +281,10 @@ export function parseEmbedPlayerFromUrl( isGif: true, hideDetails: true, metaUri: `https://giphy.com/gifs/${gifId}`, - playerUri: `https://i.giphy.com/media/${gifId}/giphy.webp`, + playerUri: `https://i.giphy.com/media/${gifId}/${ + dimensions ? '200.mp4' : '200.webp' + }`, + dimensions, } } } @@ -287,7 +305,7 @@ export function parseEmbedPlayerFromUrl( isGif: true, hideDetails: true, metaUri: `https://giphy.com/gifs/${trackingOrId}`, - playerUri: `https://i.giphy.com/media/${trackingOrId}/giphy.webp`, + playerUri: `https://i.giphy.com/media/${trackingOrId}/200.webp`, } } else if (filename && gifFilenameRegex.test(filename)) { return { @@ -296,7 +314,7 @@ export function parseEmbedPlayerFromUrl( isGif: true, hideDetails: true, metaUri: `https://giphy.com/gifs/${idOrFilename}`, - playerUri: `https://i.giphy.com/media/${idOrFilename}/giphy.webp`, + playerUri: `https://i.giphy.com/media/${idOrFilename}/200.webp`, } } } @@ -315,7 +333,7 @@ export function parseEmbedPlayerFromUrl( isGif: true, hideDetails: true, metaUri: `https://giphy.com/gifs/${gifId}`, - playerUri: `https://i.giphy.com/media/${gifId}/giphy.webp`, + playerUri: `https://i.giphy.com/media/${gifId}/200.webp`, } } else if (mediaOrFilename) { const gifId = mediaOrFilename.split('.')[0] @@ -327,7 +345,7 @@ export function parseEmbedPlayerFromUrl( metaUri: `https://giphy.com/gifs/${gifId}`, playerUri: `https://i.giphy.com/media/${ mediaOrFilename.split('.')[0] - }/giphy.webp`, + }/200.webp`, } } } diff --git a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx index aaa98a41f6..ff7c643f62 100644 --- a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx +++ b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx @@ -1,15 +1,17 @@ import React from 'react' -import {Image} from 'expo-image' -import {Text} from '../text/Text' import {StyleSheet, View} from 'react-native' +import {Image} from 'expo-image' +import {AppBskyEmbedExternal} from '@atproto/api' + import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {AppBskyEmbedExternal} from '@atproto/api' -import {toNiceDomain} from 'lib/strings/url-helpers' +import {useGate} from 'lib/statsig/statsig' import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player' -import {ExternalPlayer} from 'view/com/util/post-embeds/ExternalPlayerEmbed' -import {ExternalGifEmbed} from 'view/com/util/post-embeds/ExternalGifEmbed' +import {toNiceDomain} from 'lib/strings/url-helpers' import {useExternalEmbedsPrefs} from 'state/preferences' +import {ExternalGifEmbed} from 'view/com/util/post-embeds/ExternalGifEmbed' +import {ExternalPlayer} from 'view/com/util/post-embeds/ExternalPlayerEmbed' +import {Text} from '../text/Text' export const ExternalLinkEmbed = ({ link, @@ -19,6 +21,7 @@ export const ExternalLinkEmbed = ({ const pal = usePalette('default') const {isMobile} = useWebMediaQueries() const externalEmbedPrefs = useExternalEmbedsPrefs() + const gate = useGate() const embedPlayerParams = React.useMemo(() => { const params = parseEmbedPlayerFromUrl(link.uri) @@ -27,6 +30,10 @@ export const ExternalLinkEmbed = ({ return params } }, [link.uri, externalEmbedPrefs]) + const isCompatibleGiphy = + embedPlayerParams?.source === 'giphy' && + embedPlayerParams.dimensions && + gate('new_gif_player') return ( @@ -37,20 +44,24 @@ export const ExternalLinkEmbed = ({ accessibilityIgnoresInvertColors /> ) : undefined} - {(embedPlayerParams?.isGif && ( + {isCompatibleGiphy ? ( + + ) : embedPlayerParams?.isGif ? ( - )) || - (embedPlayerParams && ( - - ))} + ) : embedPlayerParams ? ( + + ) : undefined} - - {toNiceDomain(link.uri)} - - {!embedPlayerParams?.isGif && ( + {!isCompatibleGiphy && ( + + {toNiceDomain(link.uri)} + + )} + + {!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && ( {link.title || link.uri} From d3c0b48da3053727dd4e02acc353f6372121d944 Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 19 Apr 2024 23:37:11 +0100 Subject: [PATCH 20/47] Top/Latest for hashtags (#3625) * Split HashtagScreen into two components * Hashtag tabs * Visual fixes --- src/screens/Hashtag.tsx | 208 +++++++++++++++++++++---------- src/view/com/util/ViewHeader.tsx | 55 +++++--- 2 files changed, 184 insertions(+), 79 deletions(-) diff --git a/src/screens/Hashtag.tsx b/src/screens/Hashtag.tsx index 5388593f14..34539f5102 100644 --- a/src/screens/Hashtag.tsx +++ b/src/screens/Hashtag.tsx @@ -1,11 +1,12 @@ import React from 'react' -import {ListRenderItemInfo, Pressable} from 'react-native' +import {ListRenderItemInfo, Pressable, StyleSheet, View} from 'react-native' import {PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' import {NativeStackScreenProps} from '@react-navigation/native-stack' +import {usePalette} from '#/lib/hooks/usePalette' import {HITSLOP_10} from 'lib/constants' import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender' import {CommonNavigatorParams} from 'lib/routes/types' @@ -13,18 +14,17 @@ import {shareUrl} from 'lib/sharing' import {cleanError} from 'lib/strings/errors' import {sanitizeHandle} from 'lib/strings/handles' import {enforceLen} from 'lib/strings/helpers' -import {isNative} from 'platform/detection' +import {isNative, isWeb} from 'platform/detection' import {useSearchPostsQuery} from 'state/queries/search-posts' -import {useSetMinimalShellMode} from 'state/shell' +import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from 'state/shell' +import {Pager} from '#/view/com/pager/Pager' +import {TabBar} from '#/view/com/pager/TabBar' +import {CenteredView} from '#/view/com/util/Views' import {Post} from 'view/com/post/Post' import {List} from 'view/com/util/List' import {ViewHeader} from 'view/com/util/ViewHeader' import {ArrowOutOfBox_Stroke2_Corner0_Rounded} from '#/components/icons/ArrowOutOfBox' -import { - ListFooter, - ListHeaderDesktop, - ListMaybePlaceholder, -} from '#/components/Lists' +import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' const renderItem = ({item}: ListRenderItemInfo) => { return @@ -38,20 +38,13 @@ export default function HashtagScreen({ route, }: NativeStackScreenProps) { const {tag, author} = route.params - const setMinimalShellMode = useSetMinimalShellMode() const {_} = useLingui() - const initialNumToRender = useInitialNumToRender() - const [isPTR, setIsPTR] = React.useState(false) + const pal = usePalette('default') const fullTag = React.useMemo(() => { return `#${decodeURIComponent(tag)}` }, [tag]) - const queryParam = React.useMemo(() => { - if (!author) return fullTag - return `${fullTag} from:${sanitizeHandle(author)}` - }, [fullTag, author]) - const headerTitle = React.useMemo(() => { return enforceLen(fullTag.toLowerCase(), 24, true, 'middle') }, [fullTag]) @@ -61,27 +54,6 @@ export default function HashtagScreen({ return sanitizeHandle(author) }, [author]) - const { - data, - isFetchingNextPage, - isLoading, - isError, - error, - refetch, - fetchNextPage, - hasNextPage, - } = useSearchPostsQuery({query: queryParam}) - - const posts = React.useMemo(() => { - return data?.pages.flatMap(page => page.posts) || [] - }, [data]) - - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) - const onShare = React.useCallback(() => { const url = new URL('https://bsky.app') url.pathname = `/hashtag/${decodeURIComponent(tag)}` @@ -91,6 +63,131 @@ export default function HashtagScreen({ shareUrl(url.toString()) }, [tag, author]) + const [activeTab, setActiveTab] = React.useState(0) + const setMinimalShellMode = useSetMinimalShellMode() + const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() + + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) + + const onPageSelected = React.useCallback( + (index: number) => { + setMinimalShellMode(false) + setDrawerSwipeDisabled(index > 0) + setActiveTab(index) + }, + [setDrawerSwipeDisabled, setMinimalShellMode], + ) + + const sections = React.useMemo(() => { + return [ + { + title: _(msg`Top`), + component: ( + + ), + }, + { + title: _(msg`Latest`), + component: ( + + ), + }, + ] + }, [_, fullTag, author, activeTab]) + + return ( + <> + + ( + + + + ) + : undefined + } + /> + + ( + + section.title)} {...props} /> + + )} + initialPage={0}> + {sections.map((section, i) => ( + {section.component} + ))} + + + ) +} + +function HashtagScreenTab({ + fullTag, + author, + sort, + active, +}: { + fullTag: string + author: string | undefined + sort: 'top' | 'latest' + active: boolean +}) { + const {_} = useLingui() + const initialNumToRender = useInitialNumToRender() + const [isPTR, setIsPTR] = React.useState(false) + + const queryParam = React.useMemo(() => { + if (!author) return fullTag + return `${fullTag} from:${sanitizeHandle(author)}` + }, [fullTag, author]) + + const { + data, + isFetched, + isFetchingNextPage, + isLoading, + isError, + error, + refetch, + fetchNextPage, + hasNextPage, + } = useSearchPostsQuery({query: queryParam, sort, enabled: active}) + + const posts = React.useMemo(() => { + return data?.pages.flatMap(page => page.posts) || [] + }, [data]) + const onRefresh = React.useCallback(async () => { setIsPTR(true) await refetch() @@ -104,29 +201,9 @@ export default function HashtagScreen({ return ( <> - ( - - - - ) - : undefined - } - /> {posts.length < 1 ? ( - } ListFooterComponent={ ) } + +const styles = StyleSheet.create({ + tabBarContainer: { + // @ts-ignore web only + position: isWeb ? 'sticky' : '', + top: 0, + zIndex: 1, + }, +}) diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index 872e10eef0..63a2b3de39 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -1,19 +1,20 @@ import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {useNavigation} from '@react-navigation/native' -import {CenteredView} from './Views' -import {Text} from './text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {useAnalytics} from 'lib/analytics/analytics' -import {NavigationProp} from 'lib/routes/types' -import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' import Animated from 'react-native-reanimated' -import {useSetDrawerOpen} from '#/state/shell' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {useNavigation} from '@react-navigation/native' + +import {useSetDrawerOpen} from '#/state/shell' +import {useAnalytics} from 'lib/analytics/analytics' +import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {NavigationProp} from 'lib/routes/types' import {useTheme} from '#/alf' +import {Text} from './text/Text' +import {CenteredView} from './Views' const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} @@ -62,6 +63,7 @@ export function ViewHeader({ return ( @@ -136,14 +138,17 @@ export function ViewHeader({ function DesktopWebHeader({ title, + subtitle, renderButton, showBorder = true, }: { title: string + subtitle?: string renderButton?: () => JSX.Element showBorder?: boolean }) { const pal = usePalette('default') + const t = useTheme() return ( - - - {title} - + + + + {title} + + + {renderButton?.()} - {renderButton?.()} + {subtitle ? ( + + + + {subtitle} + + + + ) : null} ) } @@ -236,6 +258,9 @@ const styles = StyleSheet.create({ subtitle: { fontSize: 13, }, + subtitleDesktop: { + fontSize: 15, + }, backBtn: { width: 30, height: 30, From ce1c1e1cbed05183b42dd44e620fe1d4065bce1e Mon Sep 17 00:00:00 2001 From: dan Date: Sat, 20 Apr 2024 00:29:35 +0100 Subject: [PATCH 21/47] Search in PWI (#3628) --- src/view/screens/Search/Search.tsx | 144 +++++++++++------------------ 1 file changed, 52 insertions(+), 92 deletions(-) diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index 36e780c77c..099174b9b3 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -393,13 +393,7 @@ function SearchScreenUserResults({ ) } -export function SearchScreenInner({ - query, - primarySearch, -}: { - query?: string - primarySearch?: boolean -}) { +export function SearchScreenInner({query}: {query?: string}) { const pal = usePalette('default') const setMinimalShellMode = useSetMinimalShellMode() const setDrawerSwipeDisabled = useSetDrawerSwipeDisabled() @@ -419,86 +413,35 @@ export function SearchScreenInner({ const sections = React.useMemo(() => { if (!query) return [] - if (hasSession) { - return [ - { - title: _(msg`Top`), - component: ( - - ), - }, - { - title: _(msg`Latest`), - component: ( - - ), - }, - { - title: _(msg`People`), - component: ( - - ), - }, - ] - } else { - return [ - { - title: _(msg`People`), - component: ( - - ), - }, - ] - } - }, [hasSession, _, query, activeTab]) - - if (hasSession) { - return query ? ( - ( - - section.title)} {...props} /> - - )} - initialPage={0}> - {sections.map((section, i) => ( - {section.component} - ))} - - ) : ( - - - - Suggested Follows - - - - - - ) - } + return [ + { + title: _(msg`Top`), + component: ( + + ), + }, + { + title: _(msg`Latest`), + component: ( + + ), + }, + { + title: _(msg`People`), + component: ( + + ), + }, + ] + }, [_, query, activeTab]) return query ? ( {section.component} ))} + ) : hasSession ? ( + + + + Suggested Follows + + + + + ) : ( - {isDesktop && !primarySearch ? ( - Find users with the search tool on the right - ) : ( - Find users on Bluesky - )} + Find posts and users on Bluesky From 0e3a13b6df3c3e0c72473715be7d6eb809cae597 Mon Sep 17 00:00:00 2001 From: Matthieu Sieben Date: Mon, 22 Apr 2024 19:44:44 +0200 Subject: [PATCH 22/47] docs(build): update build instructions (#3641) --- docs/build.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/build.md b/docs/build.md index 0eb2315098..deab91a5ba 100644 --- a/docs/build.md +++ b/docs/build.md @@ -2,7 +2,8 @@ ## App Build -- Set up your environment [using the react native instructions](https://reactnative.dev/docs/environment-setup). +- Set up your environment [using the expo instructions](https://docs.expo.dev/guides/local-app-development/). + - make sure that the JAVA_HOME points to the zulu-17 directory in your `.zshrc` or `.bashrc` file: `export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home`. DO NOT use another JDK or you will encounter build errors. - If you're running macOS, make sure you are running the correct versions of Ruby and Cocoapods: - Check if you've installed Cocoapods through `homebrew`. If you have, remove it: - `brew info cocoapods` @@ -14,7 +15,7 @@ - `rbenv global 2.7.6` - Add `eval "$(rbenv init - zsh)"` to your `~/.zshrc` - From inside the project directory: - - `bundler install` + - `bundler install` (this will install Cocoapods) - Setup your environment [for e2e testing using detox](https://wix.github.io/Detox/docs/introduction/getting-started): - `yarn global add detox-cli` - `brew tap wix/brew` @@ -27,7 +28,7 @@ - `git clone git@github.com:bluesky-social/atproto.git` - `cd atproto` - `brew install pnpm` - - `brew install jq` + - optional: `brew install jq` - `pnpm i` - `pnpm build` - Start the docker daemon (on MacOS this entails starting the Docker Desktop app) @@ -38,11 +39,22 @@ - Xcode must be installed for this to run. - A simulator must be preconfigured in Xcode settings. - if no iOS versions are available, install the iOS runtime at `Xcode > Settings > Platforms`. + - if the simulator download keeps failing you can download it from the developer website. + - [Apple Developer](https://developer.apple.com/download/all/?q=Simulator%20Runtime) + - `xcode-select -s /Applications/Xcode.app` + - `xcodebuild -runFirstLaunch` + - `xcrun simctl runtime add "~/Downloads/iOS_17.4_Simulator_Runtime.dmg"` (adapt the path to the downloaded file) - In addition, ensure Xcode Command Line Tools are installed using `xcode-select --install`. - - Pods must be installed: - - From the project directory root: `cd ios && pod install`. - Expo will require you to configure Xcode Signing. Follow the linked instructions. Error messages in Xcode related to the signing process can be safely ignored when installing on the iOS Simulator; Expo merely requires the profile to exist in order to install the app on the Simulator. + - Make sure you do have a certificate: open Xcode > Settings > Accounts > (sign-in) > Manage Certificates > + > Apple Development > Done. + - If you still encounter issues, try `rm -rf ios` before trying to build again (`yarn ios`) - Android: `yarn android` + - Install "Android Studio" + - Make sure you have the Android SDK installed (Android Studio > Tools > Android SDK). + - In "SDK Platforms": "Android x" (where x is Android's current version). + - In "SDK Tools": "Android SDK Build-Tools" and "Android Emulator" are required. + - Add `export ANDROID_HOME=/Users//Library/Android/sdk` to your `.zshrc` or `.bashrc` (and restart your terminal). + - Setup an emulator (Android Studio > Tools > Device Manager). - Web: `yarn web` - If you are cloning or forking this repo as an open-source developer, please check the tips below as well - Run e2e tests From 243769e6577f1fc2a23dc4542bd57eab3bba45de Mon Sep 17 00:00:00 2001 From: Hailey Date: Mon, 22 Apr 2024 13:06:25 -0700 Subject: [PATCH 23/47] remove gate from updates (#3646) --- src/lib/hooks/useOTAUpdates.ts | 4 +--- src/lib/statsig/gates.ts | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib/hooks/useOTAUpdates.ts b/src/lib/hooks/useOTAUpdates.ts index b8d331c6f1..a1692e62cc 100644 --- a/src/lib/hooks/useOTAUpdates.ts +++ b/src/lib/hooks/useOTAUpdates.ts @@ -12,7 +12,6 @@ import { import {logger} from '#/logger' import {IS_TESTFLIGHT} from 'lib/app-info' -import {useGate} from 'lib/statsig/statsig' import {isIOS} from 'platform/detection' const MINIMUM_MINIMIZE_TIME = 15 * 60e3 @@ -31,8 +30,7 @@ async function setExtraParams() { } export function useOTAUpdates() { - const gate = useGate() - const shouldReceiveUpdates = isEnabled && !__DEV__ && gate('receive_updates') + const shouldReceiveUpdates = isEnabled && !__DEV__ const appState = React.useRef('active') const lastMinimize = React.useRef(0) diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index e42e9efe1e..843c14f04d 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -6,7 +6,6 @@ export type Gate = | 'hide_vertical_scroll_indicators' | 'new_gif_player' | 'new_profile_scroll_component' - | 'receive_updates' | 'show_follow_back_label_v2' | 'start_session_with_following_v2' | 'use_new_suggestions_endpoint' From bcd88b088a43d64f34da5ec0d16dadaa74beedb3 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 22 Apr 2024 21:24:50 +0100 Subject: [PATCH 24/47] add maxwidth to app language dropdown (#3635) --- src/components/AppLanguageDropdown.web.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/AppLanguageDropdown.web.tsx b/src/components/AppLanguageDropdown.web.tsx index c3c8575a10..a106d99663 100644 --- a/src/components/AppLanguageDropdown.web.tsx +++ b/src/components/AppLanguageDropdown.web.tsx @@ -71,6 +71,7 @@ export function AppLanguageDropdown() { color: t.atoms.text.color, background: t.atoms.bg.backgroundColor, padding: 4, + maxWidth: '100%', }}> {APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ( ))} From f4e72cc83c431285dee454b6201377871dbed09a Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 22 Apr 2024 22:07:48 +0100 Subject: [PATCH 31/47] [GIFs] Add error boundary to GIF picker (#3643) * error boundary on gif picker * add dialog.close for web users * fix size of dialog on web * Safer coercion --------- Co-authored-by: Dan Abramov --- src/components/dialogs/GifSelect.tsx | 37 +++++++++++++++++++++++++++- src/view/com/util/ErrorBoundary.tsx | 12 ++++++--- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/components/dialogs/GifSelect.tsx b/src/components/dialogs/GifSelect.tsx index baff31168b..ad4fbeadea 100644 --- a/src/components/dialogs/GifSelect.tsx +++ b/src/components/dialogs/GifSelect.tsx @@ -13,6 +13,8 @@ import { useSetExternalEmbedPref, } from '#/state/preferences' import {Gif, useGifphySearch, useGiphyTrending} from '#/state/queries/giphy' +import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' +import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import * as Dialog from '#/components/Dialog' import * as TextField from '#/components/forms/TextField' @@ -54,13 +56,18 @@ export function GifSelectDialog({ break } + const renderErrorBoundary = useCallback( + (error: any) => , + [], + ) + return ( - {content} + {content} ) } @@ -357,3 +364,31 @@ function GiphyConsentPrompt({control}: {control: Dialog.DialogControlProps}) { ) } + +function DialogError({details}: {details?: string}) { + const {_} = useLingui() + const control = Dialog.useDialogContext() + + return ( + + + + + + ) +} diff --git a/src/view/com/util/ErrorBoundary.tsx b/src/view/com/util/ErrorBoundary.tsx index 22fdd606e4..dccd2bbc9b 100644 --- a/src/view/com/util/ErrorBoundary.tsx +++ b/src/view/com/util/ErrorBoundary.tsx @@ -1,12 +1,14 @@ import React, {Component, ErrorInfo, ReactNode} from 'react' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {logger} from '#/logger' import {ErrorScreen} from './error/ErrorScreen' import {CenteredView} from './Views' -import {msg} from '@lingui/macro' -import {logger} from '#/logger' -import {useLingui} from '@lingui/react' interface Props { children?: ReactNode + renderError?: (error: any) => ReactNode } interface State { @@ -30,6 +32,10 @@ export class ErrorBoundary extends Component { public render() { if (this.state.hasError) { + if (this.props.renderError) { + return this.props.renderError(this.state.error) + } + return ( From 0b3cc5901996e58f8c26566c547a2ea3ea5b6e0b Mon Sep 17 00:00:00 2001 From: surfdude29 <149612116+surfdude29@users.noreply.github.com> Date: Mon, 22 Apr 2024 22:13:10 +0100 Subject: [PATCH 32/47] Update French translations (#3644) * Update French translations * Apply suggestions from code review Co-authored-by: Stanislas Signoud --------- Co-authored-by: Stanislas Signoud --- src/locale/locales/fr/messages.po | 988 +++++++++--------------------- 1 file changed, 292 insertions(+), 696 deletions(-) diff --git a/src/locale/locales/fr/messages.po b/src/locale/locales/fr/messages.po index a8402fe505..23a477fc7f 100644 --- a/src/locale/locales/fr/messages.po +++ b/src/locale/locales/fr/messages.po @@ -8,7 +8,7 @@ msgstr "" "Language: fr\n" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2024-03-12 09:00+0000\n" +"PO-Revision-Date: 2024-04-22 15:00+0100\n" "Last-Translator: surfdude29\n" "Language-Team: Stanislas Signoud (@signez.fr), surfdude29\n" "Plural-Forms: \n" @@ -32,11 +32,11 @@ msgstr "<0/> membres" #: src/view/shell/Drawer.tsx:97 msgid "<0>{0} following" -msgstr "" +msgstr "<0>{0} abonnements" #: src/components/ProfileHoverCard/index.web.tsx:314 msgid "<0>{followers} <1>{pluralizedFollowers}" -msgstr "" +msgstr "<0>{followers} <1>{pluralizedFollowers}" #: src/components/ProfileHoverCard/index.web.tsx:326 #: src/screens/Profile/Header/Metrics.tsx:45 @@ -59,14 +59,6 @@ msgstr "<0>Bienvenue sur<1>Bluesky" msgid "⚠Invalid Handle" msgstr "⚠Pseudo invalide" -#: src/view/com/util/moderation/LabelInfo.tsx:45 -#~ msgid "A content warning has been applied to this {0}." -#~ msgstr "Un avertissement sur le contenu a été appliqué sur ce {0}." - -#: src/lib/hooks/useOTAUpdate.ts:16 -#~ msgid "A new version of the app is available. Please update to continue using the app." -#~ msgstr "Une nouvelle version de l’application est disponible. Veuillez faire la mise à jour pour continuer à utiliser l’application." - #: src/view/com/util/ViewHeader.tsx:89 #: src/view/screens/Search/Search.tsx:796 msgid "Access navigation links and settings" @@ -83,7 +75,7 @@ msgstr "Accessibilité" #: src/components/moderation/LabelsOnMe.tsx:42 msgid "account" -msgstr "" +msgstr "compte" #: src/screens/Login/LoginForm.tsx:144 #: src/view/screens/Settings/index.tsx:330 @@ -97,7 +89,7 @@ msgstr "Compte bloqué" #: src/view/com/profile/ProfileMenu.tsx:153 msgid "Account followed" -msgstr "" +msgstr "Compte suivi" #: src/view/com/profile/ProfileMenu.tsx:113 msgid "Account muted" @@ -127,7 +119,7 @@ msgstr "Compte débloqué" #: src/view/com/profile/ProfileMenu.tsx:166 msgid "Account unfollowed" -msgstr "" +msgstr "Compte désabonné" #: src/view/com/profile/ProfileMenu.tsx:102 msgid "Account unmuted" @@ -167,15 +159,6 @@ msgstr "Ajouter un texte alt" msgid "Add App Password" msgstr "Ajouter un mot de passe d’application" -#: src/view/com/modals/report/InputIssueDetails.tsx:41 -#: src/view/com/modals/report/Modal.tsx:191 -#~ msgid "Add details" -#~ msgstr "Ajouter des détails" - -#: src/view/com/modals/report/Modal.tsx:194 -#~ msgid "Add details to report" -#~ msgstr "Ajouter des détails au rapport" - #: src/view/com/composer/Composer.tsx:467 msgid "Add link card" msgstr "Ajouter une carte de lien" @@ -228,13 +211,9 @@ msgstr "Définissez le nombre de likes qu’une réponse doit avoir pour être a msgid "Adult Content" msgstr "Contenu pour adultes" -#: src/view/com/modals/ContentFilteringSettings.tsx:141 -#~ msgid "Adult content can only be enabled via the Web at <0/>." -#~ msgstr "Le contenu pour adultes ne peut être activé que via le Web à <0/>." - #: src/components/moderation/LabelPreference.tsx:242 msgid "Adult content is disabled." -msgstr "" +msgstr "Le contenu pour adultes est désactivé." #: src/screens/Moderation/index.tsx:375 #: src/view/screens/Settings/index.tsx:635 @@ -272,11 +251,11 @@ msgstr "Un e-mail a été envoyé à {0}. Il comprend un code de confirmation qu #: src/view/com/modals/ChangeEmail.tsx:119 msgid "An email has been sent to your previous address, {0}. It includes a confirmation code which you can enter below." -msgstr "Un courriel a été envoyé à votre ancienne adresse, {0}. Il comprend un code de confirmation que vous pouvez saisir ici." +msgstr "Un e-mail a été envoyé à votre ancienne adresse, {0}. Il comprend un code de confirmation que vous pouvez saisir ici." #: src/lib/moderation/useReportOptions.ts:26 msgid "An issue not included in these options" -msgstr "" +msgstr "Un problème qui ne fait pas partie de ces options" #: src/components/hooks/useFollowMethods.ts:35 #: src/components/hooks/useFollowMethods.ts:50 @@ -298,7 +277,7 @@ msgstr "Animaux" #: src/lib/moderation/useReportOptions.ts:31 msgid "Anti-Social Behavior" -msgstr "" +msgstr "Comportement antisocial" #: src/view/screens/LanguageSettings.tsx:95 msgid "App Language" @@ -329,32 +308,15 @@ msgstr "Mots de passe d’application" #: src/components/moderation/LabelsOnMeDialog.tsx:133 #: src/components/moderation/LabelsOnMeDialog.tsx:136 msgid "Appeal" -msgstr "" +msgstr "Faire appel" #: src/components/moderation/LabelsOnMeDialog.tsx:201 msgid "Appeal \"{0}\" label" -msgstr "" - -#: src/view/com/util/forms/PostDropdownBtn.tsx:337 -#: src/view/com/util/forms/PostDropdownBtn.tsx:346 -#~ msgid "Appeal content warning" -#~ msgstr "Faire appel de l’avertissement sur le contenu" - -#: src/view/com/modals/AppealLabel.tsx:65 -#~ msgid "Appeal Content Warning" -#~ msgstr "Faire appel de l’avertissement sur le contenu" +msgstr "Faire appel de l’étiquette « {0} »" #: src/components/moderation/LabelsOnMeDialog.tsx:192 msgid "Appeal submitted." -msgstr "" - -#: src/view/com/util/moderation/LabelInfo.tsx:52 -#~ msgid "Appeal this decision" -#~ msgstr "Faire appel de cette décision" - -#: src/view/com/util/moderation/LabelInfo.tsx:56 -#~ msgid "Appeal this decision." -#~ msgstr "Faire appel de cette décision." +msgstr "Appel soumis." #: src/view/screens/Settings/index.tsx:436 msgid "Appearance" @@ -366,7 +328,7 @@ msgstr "Êtes-vous sûr de vouloir supprimer le mot de passe de l’application #: src/view/com/feeds/FeedSourceCard.tsx:280 msgid "Are you sure you want to remove {0} from your feeds?" -msgstr "" +msgstr "Êtes-vous sûr de vouloir supprimer {0} de vos fils d’actu ?" #: src/view/com/composer/Composer.tsx:509 msgid "Are you sure you'd like to discard this draft?" @@ -376,10 +338,6 @@ msgstr "Êtes-vous sûr de vouloir rejeter ce brouillon ?" msgid "Are you sure?" msgstr "Vous confirmez ?" -#: src/view/com/util/forms/PostDropdownBtn.tsx:322 -#~ msgid "Are you sure? This cannot be undone." -#~ msgstr "Vous confirmez ? Cela ne pourra pas être annulé." - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:60 msgid "Are you writing in <0>{0}?" msgstr "Écrivez-vous en <0>{0} ?" @@ -394,7 +352,7 @@ msgstr "Nudité artistique ou non érotique." #: src/screens/Signup/StepHandle.tsx:119 msgid "At least 3 characters" -msgstr "" +msgstr "Au moins 3 caractères" #: src/components/moderation/LabelsOnMeDialog.tsx:246 #: src/components/moderation/LabelsOnMeDialog.tsx:247 @@ -412,11 +370,6 @@ msgstr "" msgid "Back" msgstr "Arrière" -#: src/view/com/post-thread/PostThread.tsx:480 -#~ msgctxt "action" -#~ msgid "Back" -#~ msgstr "Retour" - #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:144 msgid "Based on your interest in {interestsText}" msgstr "En fonction de votre intérêt pour {interestsText}" @@ -436,7 +389,7 @@ msgstr "Date de naissance :" #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:286 #: src/view/com/profile/ProfileMenu.tsx:361 msgid "Block" -msgstr "" +msgstr "Bloquer" #: src/view/com/profile/ProfileMenu.tsx:300 #: src/view/com/profile/ProfileMenu.tsx:307 @@ -445,7 +398,7 @@ msgstr "Bloquer ce compte" #: src/view/com/profile/ProfileMenu.tsx:344 msgid "Block Account?" -msgstr "" +msgstr "Bloquer ce compte ?" #: src/view/screens/ProfileList.tsx:532 msgid "Block accounts" @@ -460,10 +413,6 @@ msgstr "Liste de blocage" msgid "Block these accounts?" msgstr "Bloquer ces comptes ?" -#: src/view/screens/ProfileList.tsx:320 -#~ msgid "Block this List" -#~ msgstr "Bloquer cette liste" - #: src/view/com/lists/ListCard.tsx:110 #: src/view/com/util/post-embeds/QuoteEmbed.tsx:58 msgid "Blocked" @@ -492,7 +441,7 @@ msgstr "Post bloqué." #: src/screens/Profile/Sections/Labels.tsx:163 msgid "Blocking does not prevent this labeler from placing labels on your account." -msgstr "" +msgstr "Le blocage n’empêche pas cet étiqueteur de placer des étiquettes sur votre compte." #: src/view/screens/ProfileList.tsx:633 msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." @@ -500,7 +449,7 @@ msgstr "Le blocage est public. Les comptes bloqués ne peuvent pas répondre à #: src/view/com/profile/ProfileMenu.tsx:353 msgid "Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you." -msgstr "" +msgstr "Le blocage n’empêchera pas les étiquettes d’être appliquées à votre compte, mais il empêchera ce compte de répondre à vos discussions ou d’interagir avec vous." #: src/view/com/auth/SplashScreen.web.tsx:149 msgid "Blog" @@ -536,20 +485,16 @@ msgstr "Bluesky n’affichera pas votre profil et vos posts à des personnes non #: src/lib/moderation/useLabelBehaviorDescription.ts:53 msgid "Blur images" -msgstr "" +msgstr "Flouter les images" #: src/lib/moderation/useLabelBehaviorDescription.ts:51 msgid "Blur images and filter from feeds" -msgstr "" +msgstr "Flouter les images et les filtrer des fils d’actu" #: src/screens/Onboarding/index.tsx:33 msgid "Books" msgstr "Livres" -#: src/view/screens/Settings/index.tsx:893 -#~ msgid "Build version {0} {1}" -#~ msgstr "Version Build {0} {1}" - #: src/view/com/auth/SplashScreen.web.tsx:146 msgid "Business" msgstr "Affaires" @@ -564,7 +509,7 @@ msgstr "par {0}" #: src/components/LabelingServiceCard/index.tsx:57 msgid "By {0}" -msgstr "" +msgstr "Par {0}" #: src/view/com/profile/ProfileSubpageHeader.tsx:161 msgid "by <0/>" @@ -572,7 +517,7 @@ msgstr "par <0/>" #: src/screens/Signup/StepInfo/Policies.tsx:74 msgid "By creating an account you agree to the {els}." -msgstr "" +msgstr "En créant un compte, vous acceptez les {els}." #: src/view/com/profile/ProfileSubpageHeader.tsx:159 msgid "by you" @@ -648,11 +593,11 @@ msgstr "Annuler la recherche" #: src/view/com/modals/LinkWarning.tsx:106 msgid "Cancels opening the linked website" -msgstr "" +msgstr "Annule l’ouverture du site web lié" #: src/view/com/modals/VerifyEmail.tsx:152 msgid "Change" -msgstr "" +msgstr "Modifier" #: src/view/screens/Settings/index.tsx:356 msgctxt "action" @@ -685,10 +630,6 @@ msgstr "Modifier le mot de passe" msgid "Change post language to {0}" msgstr "Modifier la langue de post en {0}" -#: src/view/screens/Settings/index.tsx:733 -#~ msgid "Change your Bluesky password" -#~ msgstr "Changer votre mot de passe pour Bluesky" - #: src/view/com/modals/ChangeEmail.tsx:109 msgid "Change Your Email" msgstr "Modifier votre e-mail" @@ -714,10 +655,6 @@ msgstr "Consultez votre boîte de réception, vous avez du recevoir un e-mail co msgid "Choose \"Everybody\" or \"Nobody\"" msgstr "Choisir « Tout le monde » ou « Personne »" -#: src/view/screens/Settings/index.tsx:697 -#~ msgid "Choose a new Bluesky username or create" -#~ msgstr "Choisir un nouveau pseudo Bluesky ou en créer un" - #: src/view/com/auth/server-input/index.tsx:79 msgid "Choose Service" msgstr "Choisir un service" @@ -762,11 +699,11 @@ msgstr "Effacer la recherche" #: src/view/screens/Settings/index.tsx:833 msgid "Clears all legacy storage data" -msgstr "" +msgstr "Efface toutes les données de stockage existantes" #: src/view/screens/Settings/index.tsx:845 msgid "Clears all storage data" -msgstr "" +msgstr "Efface toutes les données de stockage" #: src/view/screens/Support.tsx:40 msgid "click here" @@ -874,11 +811,11 @@ msgstr "Configurer les paramètres de filtrage de contenu pour la catégorie : #: src/components/moderation/LabelPreference.tsx:81 msgid "Configure content filtering setting for category: {name}" -msgstr "" +msgstr "Configure les paramètres de filtrage de contenu pour la catégorie : {name}" #: src/components/moderation/LabelPreference.tsx:244 msgid "Configured in <0>moderation settings." -msgstr "" +msgstr "Configuré dans <0>les paramètres de modération." #: src/components/Prompt.tsx:153 #: src/components/Prompt.tsx:156 @@ -890,12 +827,6 @@ msgstr "" msgid "Confirm" msgstr "Confirmer" -#: src/view/com/modals/Confirm.tsx:75 -#: src/view/com/modals/Confirm.tsx:78 -#~ msgctxt "action" -#~ msgid "Confirm" -#~ msgstr "Confirmer" - #: src/view/com/modals/ChangeEmail.tsx:193 #: src/view/com/modals/ChangeEmail.tsx:195 msgid "Confirm Change" @@ -909,17 +840,13 @@ msgstr "Confirmer les paramètres de langue" msgid "Confirm delete account" msgstr "Confirmer la suppression du compte" -#: src/view/com/modals/ContentFilteringSettings.tsx:156 -#~ msgid "Confirm your age to enable adult content." -#~ msgstr "Confirmez votre âge pour activer le contenu pour adultes." - #: src/screens/Moderation/index.tsx:301 msgid "Confirm your age:" -msgstr "" +msgstr "Confirmez votre âge :" #: src/screens/Moderation/index.tsx:292 msgid "Confirm your birthdate" -msgstr "" +msgstr "Confirme votre date de naissance" #: src/view/com/modals/ChangeEmail.tsx:157 #: src/view/com/modals/DeleteAccount.tsx:175 @@ -938,23 +865,15 @@ msgstr "Contacter le support" #: src/components/moderation/LabelsOnMe.tsx:42 msgid "content" -msgstr "" +msgstr "contenu" #: src/lib/moderation/useGlobalLabelStrings.ts:18 msgid "Content Blocked" -msgstr "" - -#: src/view/screens/Moderation.tsx:83 -#~ msgid "Content filtering" -#~ msgstr "Filtrage du contenu" - -#: src/view/com/modals/ContentFilteringSettings.tsx:44 -#~ msgid "Content Filtering" -#~ msgstr "Filtrage du contenu" +msgstr "Contenu bloqué" #: src/screens/Moderation/index.tsx:285 msgid "Content filters" -msgstr "" +msgstr "Filtres de contenu" #: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:74 #: src/view/screens/LanguageSettings.tsx:278 @@ -979,7 +898,7 @@ msgstr "Avertissements sur le contenu" #: src/components/Menu/index.web.tsx:84 msgid "Context menu backdrop, click to close the menu." -msgstr "" +msgstr "Menu contextuel en arrière-plan, cliquez pour fermer le menu." #: src/screens/Onboarding/StepAlgoFeeds/index.tsx:161 #: src/screens/Onboarding/StepFollowingFeed.tsx:154 @@ -994,7 +913,7 @@ msgstr "Continuer" #: src/components/AccountList.tsx:108 msgid "Continue as {0} (currently signed in)" -msgstr "" +msgstr "Continuer comme {0} (actuellement connecté)" #: src/screens/Onboarding/StepFollowingFeed.tsx:151 #: src/screens/Onboarding/StepInterests/index.tsx:249 @@ -1034,7 +953,7 @@ msgstr "Copié dans le presse-papier" #: src/components/dialogs/Embed.tsx:134 msgid "Copied!" -msgstr "" +msgstr "Copié !" #: src/view/com/modals/AddAppPasswords.tsx:190 msgid "Copies app password" @@ -1042,16 +961,16 @@ msgstr "Copie le mot de passe d’application" #: src/view/com/modals/AddAppPasswords.tsx:189 msgid "Copy" -msgstr "Copie" +msgstr "Copier" #: src/view/com/modals/ChangeHandle.tsx:480 msgid "Copy {0}" -msgstr "" +msgstr "Copier {0}" #: src/components/dialogs/Embed.tsx:120 #: src/components/dialogs/Embed.tsx:139 msgid "Copy code" -msgstr "" +msgstr "Copier ce code" #: src/view/screens/ProfileList.tsx:390 msgid "Copy link to list" @@ -1062,10 +981,6 @@ msgstr "Copier le lien vers la liste" msgid "Copy link to post" msgstr "Copier le lien vers le post" -#: src/view/com/profile/ProfileHeader.tsx:295 -#~ msgid "Copy link to profile" -#~ msgstr "Copier le lien vers le profil" - #: src/view/com/util/forms/PostDropdownBtn.tsx:228 #: src/view/com/util/forms/PostDropdownBtn.tsx:230 msgid "Copy post text" @@ -1100,7 +1015,7 @@ msgstr "Créer un compte" #: src/components/dialogs/Signin.tsx:86 #: src/components/dialogs/Signin.tsx:88 msgid "Create an account" -msgstr "" +msgstr "Créer un compte" #: src/view/com/modals/AddAppPasswords.tsx:227 msgid "Create App Password" @@ -1113,20 +1028,12 @@ msgstr "Créer un nouveau compte" #: src/components/ReportDialog/SelectReportOptionView.tsx:94 msgid "Create report for {0}" -msgstr "" +msgstr "Créer un rapport pour {0}" #: src/view/screens/AppPasswords.tsx:246 msgid "Created {0}" msgstr "{0} créé" -#: src/view/screens/ProfileFeed.tsx:616 -#~ msgid "Created by <0/>" -#~ msgstr "Créée par <0/>" - -#: src/view/screens/ProfileFeed.tsx:614 -#~ msgid "Created by you" -#~ msgstr "Créée par vous" - #: src/view/com/composer/Composer.tsx:469 msgid "Creates a card with a thumbnail. The card links to {url}" msgstr "Crée une carte avec une miniature. La carte pointe vers {url}" @@ -1168,11 +1075,11 @@ msgstr "Thème sombre" #: src/screens/Signup/StepInfo/index.tsx:134 msgid "Date of birth" -msgstr "" +msgstr "Date de naissance" #: src/view/screens/Settings/index.tsx:805 msgid "Debug Moderation" -msgstr "" +msgstr "Déboguer la modération" #: src/view/screens/Debug.tsx:83 msgid "Debug panel" @@ -1182,7 +1089,7 @@ msgstr "Panneau de débug" #: src/view/screens/AppPasswords.tsx:268 #: src/view/screens/ProfileList.tsx:615 msgid "Delete" -msgstr "" +msgstr "Supprimer" #: src/view/screens/Settings/index.tsx:760 msgid "Delete account" @@ -1198,7 +1105,7 @@ msgstr "Supprimer le mot de passe de l’appli" #: src/view/screens/AppPasswords.tsx:263 msgid "Delete app password?" -msgstr "" +msgstr "Supprimer le mot de passe de l’appli ?" #: src/view/screens/ProfileList.tsx:417 msgid "Delete List" @@ -1219,7 +1126,7 @@ msgstr "Supprimer le post" #: src/view/screens/ProfileList.tsx:610 msgid "Delete this list?" -msgstr "" +msgstr "Supprimer cette liste ?" #: src/view/com/util/forms/PostDropdownBtn.tsx:336 msgid "Delete this post?" @@ -1250,30 +1157,26 @@ msgstr "Atténué" #: src/view/screens/Settings/index.tsx:697 msgid "Disable haptics" -msgstr "" +msgstr "Désactiver l’haptique" #: src/view/screens/Settings/index.tsx:697 msgid "Disable vibrations" -msgstr "" +msgstr "Désactiver les vibrations" #: src/lib/moderation/useLabelBehaviorDescription.ts:32 #: src/lib/moderation/useLabelBehaviorDescription.ts:42 #: src/lib/moderation/useLabelBehaviorDescription.ts:68 #: src/screens/Moderation/index.tsx:341 msgid "Disabled" -msgstr "" +msgstr "Désactivé" #: src/view/com/composer/Composer.tsx:511 msgid "Discard" msgstr "Ignorer" -#: src/view/com/composer/Composer.tsx:145 -#~ msgid "Discard draft" -#~ msgstr "Ignorer le brouillon" - #: src/view/com/composer/Composer.tsx:508 msgid "Discard draft?" -msgstr "" +msgstr "Abandonner le brouillon ?" #: src/screens/Moderation/index.tsx:518 #: src/screens/Moderation/index.tsx:522 @@ -1299,19 +1202,19 @@ msgstr "Afficher le nom" #: src/view/com/modals/ChangeHandle.tsx:397 msgid "DNS Panel" -msgstr "" +msgstr "Panneau DNS" #: src/lib/moderation/useGlobalLabelStrings.ts:39 msgid "Does not include nudity." -msgstr "" +msgstr "Ne comprend pas de nudité." #: src/screens/Signup/StepHandle.tsx:105 msgid "Doesn't begin or end with a hyphen" -msgstr "" +msgstr "Ne commence pas ou ne se termine pas par un trait d’union" #: src/view/com/modals/ChangeHandle.tsx:481 msgid "Domain Value" -msgstr "" +msgstr "Valeur du domaine" #: src/view/com/modals/ChangeHandle.tsx:488 msgid "Domain verified!" @@ -1352,14 +1255,6 @@ msgstr "Terminer" msgid "Done{extraText}" msgstr "Terminé{extraText}" -#: src/view/com/auth/login/ChooseAccountForm.tsx:46 -#~ msgid "Double tap to sign in" -#~ msgstr "Tapotez deux fois pour vous connecter" - -#: src/view/screens/Settings/index.tsx:755 -#~ msgid "Download Bluesky account data (repository)" -#~ msgstr "Télécharger les données du compte Bluesky (dépôt)" - #: src/view/screens/Settings/ExportCarDialog.tsx:59 #: src/view/screens/Settings/ExportCarDialog.tsx:63 msgid "Download CAR file" @@ -1375,7 +1270,7 @@ msgstr "En raison des politiques d’Apple, le contenu pour adultes ne peut êtr #: src/view/com/modals/ChangeHandle.tsx:258 msgid "e.g. alice" -msgstr "" +msgstr "ex. alice" #: src/view/com/modals/EditProfile.tsx:186 msgid "e.g. Alice Roberts" @@ -1383,7 +1278,7 @@ msgstr "ex. Alice Dupont" #: src/view/com/modals/ChangeHandle.tsx:380 msgid "e.g. alice.com" -msgstr "" +msgstr "ex. alice.fr" #: src/view/com/modals/EditProfile.tsx:204 msgid "e.g. Artist, dog-lover, and avid reader." @@ -1391,7 +1286,7 @@ msgstr "ex. Artiste, amoureuse des chiens et lectrice passionnée." #: src/lib/moderation/useGlobalLabelStrings.ts:43 msgid "E.g. artistic nudes." -msgstr "" +msgstr "Ex. nus artistiques." #: src/view/com/modals/CreateOrEditList.tsx:284 msgid "e.g. Great Posters" @@ -1421,7 +1316,7 @@ msgstr "Modifier" #: src/view/com/util/UserAvatar.tsx:301 #: src/view/com/util/UserBanner.tsx:85 msgid "Edit avatar" -msgstr "" +msgstr "Modifier l’avatar" #: src/view/com/composer/photos/Gallery.tsx:144 #: src/view/com/modals/EditImage.tsx:208 @@ -1505,17 +1400,17 @@ msgstr "E-mail :" #: src/components/dialogs/Embed.tsx:112 msgid "Embed HTML code" -msgstr "" +msgstr "Code HTML à intégrer" #: src/components/dialogs/Embed.tsx:97 #: src/view/com/util/forms/PostDropdownBtn.tsx:253 #: src/view/com/util/forms/PostDropdownBtn.tsx:255 msgid "Embed post" -msgstr "" +msgstr "Intégrer le post" #: src/components/dialogs/Embed.tsx:101 msgid "Embed this post in your website. Simply copy the following snippet and paste it into the HTML code of your website." -msgstr "" +msgstr "Intégrez ce post à votre site web. Il suffit de copier l’extrait suivant et de le coller dans le code HTML de votre site web." #: src/components/dialogs/EmbedConsent.tsx:101 msgid "Enable {0} only" @@ -1523,7 +1418,7 @@ msgstr "Activer {0} uniquement" #: src/screens/Moderation/index.tsx:329 msgid "Enable adult content" -msgstr "" +msgstr "Activer le contenu pour adultes" #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:94 msgid "Enable Adult Content" @@ -1537,11 +1432,7 @@ msgstr "Activer le contenu pour adultes dans vos fils d’actu" #: src/components/dialogs/EmbedConsent.tsx:82 #: src/components/dialogs/EmbedConsent.tsx:89 msgid "Enable external media" -msgstr "" - -#: src/view/com/modals/EmbedConsent.tsx:97 -#~ msgid "Enable External Media" -#~ msgstr "Activer les médias externes" +msgstr "Activer les médias externes" #: src/view/screens/PreferencesExternalEmbeds.tsx:75 msgid "Enable media players for" @@ -1553,11 +1444,11 @@ msgstr "Activez ce paramètre pour ne voir que les réponses des personnes que v #: src/components/dialogs/EmbedConsent.tsx:94 msgid "Enable this source only" -msgstr "" +msgstr "Active cette source uniquement" #: src/screens/Moderation/index.tsx:339 msgid "Enabled" -msgstr "" +msgstr "Activé" #: src/screens/Profile/Sections/Feed.tsx:100 msgid "End of feed" @@ -1569,7 +1460,7 @@ msgstr "Entrer un nom pour ce mot de passe d’application" #: src/screens/Login/SetNewPasswordForm.tsx:139 msgid "Enter a password" -msgstr "" +msgstr "Saisir un mot de passe" #: src/components/dialogs/MutedWords.tsx:99 #: src/components/dialogs/MutedWords.tsx:100 @@ -1627,11 +1518,11 @@ msgstr "Tout le monde" #: src/lib/moderation/useReportOptions.ts:66 msgid "Excessive mentions or replies" -msgstr "" +msgstr "Mentions ou réponses excessives" #: src/view/com/modals/DeleteAccount.tsx:230 msgid "Exits account deletion process" -msgstr "" +msgstr "Sort du processus de suppression du compte" #: src/view/com/modals/ChangeHandle.tsx:151 msgid "Exits handle change process" @@ -1639,7 +1530,7 @@ msgstr "Sort du processus de changement de pseudo" #: src/view/com/modals/crop-image/CropImage.web.tsx:136 msgid "Exits image cropping process" -msgstr "" +msgstr "Sort du processus de recadrage de l’image" #: src/view/com/lightbox/Lightbox.web.tsx:130 msgid "Exits image view" @@ -1661,11 +1552,11 @@ msgstr "Développe ou réduit le post complet auquel vous répondez" #: src/lib/moderation/useGlobalLabelStrings.ts:47 msgid "Explicit or potentially disturbing media." -msgstr "" +msgstr "Médias explicites ou potentiellement dérangeants." #: src/lib/moderation/useGlobalLabelStrings.ts:35 msgid "Explicit sexual images." -msgstr "" +msgstr "Images sexuelles explicites." #: src/view/screens/Settings/index.tsx:741 msgid "Export my data" @@ -1716,7 +1607,7 @@ msgstr "Échec du chargement des fils d’actu recommandés" #: src/view/com/lightbox/Lightbox.tsx:83 msgid "Failed to save image: {0}" -msgstr "" +msgstr "Échec de l’enregistrement de l’image : {0}" #: src/Navigation.tsx:196 msgid "Feed" @@ -1760,11 +1651,11 @@ msgstr "Les fils d’actu peuvent également être thématiques !" #: src/view/com/modals/ChangeHandle.tsx:481 msgid "File Contents" -msgstr "" +msgstr "Contenu du fichier" #: src/lib/moderation/useLabelBehaviorDescription.ts:66 msgid "Filter from feeds" -msgstr "" +msgstr "Filtrer des fils d’actu" #: src/screens/Onboarding/StepFinished.tsx:155 msgid "Finalizing" @@ -1835,7 +1726,7 @@ msgstr "Suivre {0}" #: src/view/com/profile/ProfileMenu.tsx:242 #: src/view/com/profile/ProfileMenu.tsx:253 msgid "Follow Account" -msgstr "" +msgstr "Suivre le compte" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:187 msgid "Follow All" @@ -1843,7 +1734,7 @@ msgstr "Suivre tous" #: src/view/com/post-thread/PostThreadFollowBtn.tsx:144 msgid "Follow Back" -msgstr "" +msgstr "Suivre en retour" #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:182 msgid "Follow selected accounts and continue to the next step" @@ -1887,7 +1778,7 @@ msgstr "Suit {0}" #: src/view/screens/Settings/index.tsx:504 msgid "Following feed preferences" -msgstr "" +msgstr "Préférences du fil d’actu « Following »" #: src/Navigation.tsx:262 #: src/view/com/home/HomeHeaderLayout.web.tsx:54 @@ -1917,14 +1808,6 @@ msgstr "Pour des raisons de sécurité, nous devrons envoyer un code de confirma msgid "For security reasons, you won't be able to view this again. If you lose this password, you'll need to generate a new one." msgstr "Pour des raisons de sécurité, vous ne pourrez plus afficher ceci. Si vous perdez ce mot de passe, vous devrez en générer un autre." -#: src/view/com/auth/login/LoginForm.tsx:244 -#~ msgid "Forgot" -#~ msgstr "Oublié" - -#: src/view/com/auth/login/LoginForm.tsx:241 -#~ msgid "Forgot password" -#~ msgstr "Mot de passe oublié" - #: src/screens/Login/index.tsx:129 #: src/screens/Login/index.tsx:144 msgid "Forgot Password" @@ -1932,15 +1815,15 @@ msgstr "Mot de passe oublié" #: src/screens/Login/LoginForm.tsx:201 msgid "Forgot password?" -msgstr "" +msgstr "Mot de passe oublié ?" #: src/screens/Login/LoginForm.tsx:212 msgid "Forgot?" -msgstr "" +msgstr "Oublié ?" #: src/lib/moderation/useReportOptions.ts:52 msgid "Frequently Posts Unwanted Content" -msgstr "" +msgstr "Publication fréquente de contenu indésirable" #: src/screens/Hashtag.tsx:109 #: src/screens/Hashtag.tsx:149 @@ -1963,7 +1846,7 @@ msgstr "C’est parti" #: src/lib/moderation/useReportOptions.ts:37 msgid "Glaring violations of law or terms of service" -msgstr "" +msgstr "Violations flagrantes de la loi ou des conditions d’utilisation" #: src/components/moderation/ScreenHider.tsx:151 #: src/components/moderation/ScreenHider.tsx:160 @@ -1995,11 +1878,11 @@ msgstr "Retour à l’étape précédente" #: src/view/screens/NotFound.tsx:55 msgid "Go home" -msgstr "" +msgstr "Accéder à l’accueil" #: src/view/screens/NotFound.tsx:54 msgid "Go Home" -msgstr "" +msgstr "Accéder à l’accueil" #: src/view/screens/Search/Search.tsx:896 #: src/view/shell/desktop/Search.tsx:263 @@ -2013,7 +1896,7 @@ msgstr "Aller à la suite" #: src/lib/moderation/useGlobalLabelStrings.ts:46 msgid "Graphic Media" -msgstr "" +msgstr "Médias crus" #: src/view/com/modals/ChangeHandle.tsx:266 msgid "Handle" @@ -2021,7 +1904,7 @@ msgstr "Pseudo" #: src/lib/moderation/useReportOptions.ts:32 msgid "Harassment, trolling, or intolerance" -msgstr "" +msgstr "Harcèlement, trolling ou intolérance" #: src/Navigation.tsx:282 msgid "Hashtag" @@ -2092,10 +1975,6 @@ msgstr "Cacher ce post ?" msgid "Hide user list" msgstr "Cacher la liste des comptes" -#: src/view/com/profile/ProfileHeader.tsx:487 -#~ msgid "Hides posts from {0} in your feed" -#~ msgstr "Masque les posts de {0} dans votre fil d’actu" - #: src/view/com/posts/FeedErrorMessage.tsx:111 msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." msgstr "Hmm, un problème s’est produit avec le serveur de fils d’actu. Veuillez informer la personne propriétaire du fil d’actu de ce problème." @@ -2106,11 +1985,11 @@ msgstr "Hmm, le serveur du fils d’actu semble être mal configuré. Veuillez i #: src/view/com/posts/FeedErrorMessage.tsx:105 msgid "Hmm, the feed server appears to be offline. Please let the feed owner know about this issue." -msgstr "Mmm… le serveur de fils d’actu semble être hors ligne. Veuillez informer la personne propriétaire du fil d’actu de ce problème." +msgstr "Hmm, le serveur de fils d’actu semble être hors ligne. Veuillez informer la personne propriétaire du fil d’actu de ce problème." #: src/view/com/posts/FeedErrorMessage.tsx:102 msgid "Hmm, the feed server gave a bad response. Please let the feed owner know about this issue." -msgstr "Mmm… le serveur de fils d’actu ne répond pas. Veuillez informer la personne propriétaire du fil d’actu de ce problème." +msgstr "Hmm, le serveur de fils d’actu ne répond pas. Veuillez informer la personne propriétaire du fil d’actu de ce problème." #: src/view/com/posts/FeedErrorMessage.tsx:96 msgid "Hmm, we're having trouble finding this feed. It may have been deleted." @@ -2118,11 +1997,11 @@ msgstr "Hmm, nous n’arrivons pas à trouver ce fil d’actu. Il a peut-être #: src/screens/Moderation/index.tsx:59 msgid "Hmmmm, it seems we're having trouble loading this data. See below for more details. If this issue persists, please contact us." -msgstr "" +msgstr "Hmm, il semble que nous ayons des difficultés à charger ces données. Voir ci-dessous pour plus de détails. Si le problème persiste, veuillez nous contacter." #: src/screens/Profile/ErrorState.tsx:31 msgid "Hmmmm, we couldn't load that moderation service." -msgstr "" +msgstr "Hmm, nous n’avons pas pu charger ce service de modération." #: src/Navigation.tsx:446 #: src/view/shell/bottom-bar/BottomBar.tsx:148 @@ -2134,7 +2013,7 @@ msgstr "Accueil" #: src/view/com/modals/ChangeHandle.tsx:420 msgid "Host:" -msgstr "" +msgstr "Hébergeur :" #: src/screens/Login/ForgotPasswordForm.tsx:89 #: src/screens/Login/LoginForm.tsx:134 @@ -2169,15 +2048,15 @@ msgstr "Si rien n’est sélectionné, il n’y a pas de restriction d’âge." #: src/screens/Signup/StepInfo/Policies.tsx:83 msgid "If you are not yet an adult according to the laws of your country, your parent or legal guardian must read these Terms on your behalf." -msgstr "" +msgstr "Si vous n’êtes pas encore un adulte selon les lois de votre pays, vos parents ou votre tuteur légal doivent lire ces conditions en votre nom." #: src/view/screens/ProfileList.tsx:612 msgid "If you delete this list, you won't be able to recover it." -msgstr "" +msgstr "Si vous supprimez cette liste, vous ne pourrez pas la récupérer." #: src/view/com/util/forms/PostDropdownBtn.tsx:338 msgid "If you remove this post, you won't be able to recover it." -msgstr "" +msgstr "Si vous supprimez ce post, vous ne pourrez pas le récupérer." #: src/view/com/modals/ChangePassword.tsx:148 msgid "If you want to change your password, we will send you a code to verify that this is your account." @@ -2185,7 +2064,7 @@ msgstr "Si vous souhaitez modifier votre mot de passe, nous vous enverrons un co #: src/lib/moderation/useReportOptions.ts:36 msgid "Illegal and Urgent" -msgstr "" +msgstr "Illégal et urgent" #: src/view/com/util/images/Gallery.tsx:38 msgid "Image" @@ -2195,14 +2074,9 @@ msgstr "Image" msgid "Image alt text" msgstr "Texte alt de l’image" -#: src/view/com/util/UserAvatar.tsx:311 -#: src/view/com/util/UserBanner.tsx:118 -#~ msgid "Image options" -#~ msgstr "Options d’images" - #: src/lib/moderation/useReportOptions.ts:47 msgid "Impersonation or false claims about identity or affiliation" -msgstr "" +msgstr "Usurpation d’identité ou fausses déclarations concernant l’identité ou l’affiliation" #: src/screens/Login/SetNewPasswordForm.tsx:127 msgid "Input code sent to your email for password reset" @@ -2212,14 +2086,6 @@ msgstr "Entrez le code envoyé à votre e-mail pour réinitialiser le mot de pas msgid "Input confirmation code for account deletion" msgstr "Entrez le code de confirmation pour supprimer le compte" -#: src/view/com/auth/create/Step1.tsx:177 -#~ msgid "Input email for Bluesky account" -#~ msgstr "Saisir l’email pour le compte Bluesky" - -#: src/view/com/auth/create/Step1.tsx:151 -#~ msgid "Input invite code to proceed" -#~ msgstr "Entrez le code d’invitation pour continuer" - #: src/view/com/modals/AddAppPasswords.tsx:181 msgid "Input name for app password" msgstr "Entrez le nom du mot de passe de l’appli" @@ -2246,7 +2112,7 @@ msgstr "Entrez votre mot de passe" #: src/view/com/modals/ChangeHandle.tsx:389 msgid "Input your preferred hosting provider" -msgstr "" +msgstr "Entrez votre hébergeur préféré" #: src/screens/Signup/StepHandle.tsx:63 msgid "Input your user handle" @@ -2294,35 +2160,35 @@ msgstr "Journalisme" #: src/components/moderation/LabelsOnMe.tsx:59 msgid "label has been placed on this {labelTarget}" -msgstr "" +msgstr "étiquette a été placée sur ce {labelTarget}" #: src/components/moderation/ContentHider.tsx:144 msgid "Labeled by {0}." -msgstr "" +msgstr "Étiqueté par {0}." #: src/components/moderation/ContentHider.tsx:142 msgid "Labeled by the author." -msgstr "" +msgstr "Étiqueté par l’auteur." #: src/view/screens/Profile.tsx:193 msgid "Labels" -msgstr "" +msgstr "Étiquettes" #: src/screens/Profile/Sections/Labels.tsx:153 msgid "Labels are annotations on users and content. They can be used to hide, warn, and categorize the network." -msgstr "" +msgstr "Les étiquettes sont des annotations sur les comptes et le contenu. Elles peuvent être utilisées pour masquer, avertir et catégoriser le réseau." #: src/components/moderation/LabelsOnMe.tsx:61 msgid "labels have been placed on this {labelTarget}" -msgstr "" +msgstr "étiquettes ont été placées sur ce {labelTarget}" #: src/components/moderation/LabelsOnMeDialog.tsx:62 msgid "Labels on your account" -msgstr "" +msgstr "Étiquettes sur votre compte" #: src/components/moderation/LabelsOnMeDialog.tsx:64 msgid "Labels on your content" -msgstr "" +msgstr "Étiquettes sur votre contenu" #: src/view/com/composer/select-language/SelectLangBtn.tsx:104 msgid "Language selection" @@ -2341,17 +2207,9 @@ msgstr "Paramètres linguistiques" msgid "Languages" msgstr "Langues" -#: src/view/com/auth/create/StepHeader.tsx:20 -#~ msgid "Last step!" -#~ msgstr "Dernière étape !" - #: src/view/screens/Search/Search.tsx:437 msgid "Latest" -msgstr "" - -#: src/view/com/util/moderation/ContentHider.tsx:103 -#~ msgid "Learn more" -#~ msgstr "En savoir plus" +msgstr "Dernier" #: src/components/moderation/ScreenHider.tsx:136 msgid "Learn More" @@ -2360,7 +2218,7 @@ msgstr "En savoir plus" #: src/components/moderation/ContentHider.tsx:65 #: src/components/moderation/ContentHider.tsx:128 msgid "Learn more about the moderation applied to this content." -msgstr "" +msgstr "En savoir plus sur la modération appliquée à ce contenu." #: src/components/moderation/PostHider.tsx:85 #: src/components/moderation/ScreenHider.tsx:125 @@ -2373,7 +2231,7 @@ msgstr "En savoir plus sur ce qui est public sur Bluesky." #: src/components/moderation/ContentHider.tsx:152 msgid "Learn more." -msgstr "" +msgstr "En savoir plus." #: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:82 msgid "Leave them all unchecked to see any language." @@ -2400,11 +2258,6 @@ msgstr "Réinitialisez votre mot de passe !" msgid "Let's go!" msgstr "Allons-y !" -#: src/view/com/util/UserAvatar.tsx:248 -#: src/view/com/util/UserBanner.tsx:62 -#~ msgid "Library" -#~ msgstr "Bibliothèque" - #: src/view/screens/Settings/index.tsx:449 msgid "Light" msgstr "Clair" @@ -2436,7 +2289,7 @@ msgstr "Liké par {0} {1}" #: src/components/LabelingServiceCard/index.tsx:72 msgid "Liked by {count} {0}" -msgstr "" +msgstr "Liké par {count} {0}"" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:284 #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:298 @@ -2505,11 +2358,6 @@ msgstr "Liste démasquée" msgid "Lists" msgstr "Listes" -#: src/view/com/post-thread/PostThread.tsx:333 -#: src/view/com/post-thread/PostThread.tsx:341 -#~ msgid "Load more posts" -#~ msgstr "Charger plus de posts" - #: src/view/screens/Notifications.tsx:159 msgid "Load new notifications" msgstr "Charger les nouvelles notifications" @@ -2546,7 +2394,7 @@ msgstr "Se connecter à un compte qui n’est pas listé" #: src/screens/Login/SetNewPasswordForm.tsx:116 msgid "Looks like XXXXX-XXXXX" -msgstr "" +msgstr "De la forme XXXXX-XXXXX" #: src/view/com/modals/LinkWarning.tsx:79 msgid "Make sure this is where you intend to go!" @@ -2556,14 +2404,6 @@ msgstr "Assurez-vous que c’est bien là que vous avez l’intention d’aller msgid "Manage your muted words and tags" msgstr "Gérer les mots et les mots-clés masqués" -#: src/view/com/auth/create/Step2.tsx:118 -#~ msgid "May not be longer than 253 characters" -#~ msgstr "Ne doit pas dépasser 253 caractères" - -#: src/view/com/auth/create/Step2.tsx:109 -#~ msgid "May only contain letters and numbers" -#~ msgstr "Ne peut contenir que des lettres et des chiffres" - #: src/view/screens/Profile.tsx:197 msgid "Media" msgstr "Média" @@ -2587,7 +2427,7 @@ msgstr "Message du serveur : {0}" #: src/lib/moderation/useReportOptions.ts:45 msgid "Misleading Account" -msgstr "" +msgstr "Compte trompeur" #: src/Navigation.tsx:119 #: src/screens/Moderation/index.tsx:104 @@ -2600,7 +2440,7 @@ msgstr "Modération" #: src/components/moderation/ModerationDetailsDialog.tsx:112 msgid "Moderation details" -msgstr "" +msgstr "Détails de la modération" #: src/view/com/lists/ListCard.tsx:93 #: src/view/com/modals/UserAddRemoveLists.tsx:206 @@ -2640,11 +2480,11 @@ msgstr "Paramètres de modération" #: src/Navigation.tsx:216 msgid "Moderation states" -msgstr "" +msgstr "États de modération" #: src/screens/Moderation/index.tsx:215 msgid "Moderation tools" -msgstr "" +msgstr "Outils de modération" #: src/components/moderation/ModerationDetailsDialog.tsx:48 #: src/lib/moderation/useModerationCauseDescription.ts:40 @@ -2653,7 +2493,7 @@ msgstr "La modération a choisi d’ajouter un avertissement général sur le co #: src/view/com/post-thread/PostThreadItem.tsx:535 msgid "More" -msgstr "" +msgstr "Plus" #: src/view/shell/desktop/Feeds.tsx:65 msgid "More feeds" @@ -2667,10 +2507,6 @@ msgstr "Plus d’options" msgid "Most-liked replies first" msgstr "Réponses les plus likées en premier" -#: src/view/com/auth/create/Step2.tsx:122 -#~ msgid "Must be at least 3 characters" -#~ msgstr "Doit comporter au moins 3 caractères" - #: src/components/TagMenu/index.tsx:249 msgid "Mute" msgstr "Masquer" @@ -2709,10 +2545,6 @@ msgstr "Masquer la liste" msgid "Mute these accounts?" msgstr "Masquer ces comptes ?" -#: src/view/screens/ProfileList.tsx:279 -#~ msgid "Mute this List" -#~ msgstr "Masquer cette liste" - #: src/components/dialogs/MutedWords.tsx:126 msgid "Mute this word in post text and tags" msgstr "Masquer ce mot dans le texte du post et les mots-clés" @@ -2750,7 +2582,7 @@ msgstr "Les comptes masqués voient leurs posts supprimés de votre fil d’actu #: src/lib/moderation/useModerationCauseDescription.ts:85 msgid "Muted by \"{0}\"" -msgstr "" +msgstr "Masqué par « {0} »" #: src/screens/Moderation/index.tsx:231 msgid "Muted words & tags" @@ -2775,16 +2607,12 @@ msgstr "Mon profil" #: src/view/screens/Settings/index.tsx:547 msgid "My saved feeds" -msgstr "" +msgstr "Mes fils d’actu enregistrés" #: src/view/screens/Settings/index.tsx:553 msgid "My Saved Feeds" msgstr "Mes fils d’actu enregistrés" -#: src/view/com/auth/server-input/index.tsx:118 -#~ msgid "my-server.com" -#~ msgstr "mon-serveur.fr" - #: src/view/com/modals/AddAppPasswords.tsx:180 #: src/view/com/modals/CreateOrEditList.tsx:291 msgid "Name" @@ -2798,7 +2626,7 @@ msgstr "Le nom est requis" #: src/lib/moderation/useReportOptions.ts:78 #: src/lib/moderation/useReportOptions.ts:86 msgid "Name or Description Violates Community Standards" -msgstr "" +msgstr "Nom ou description qui viole les normes communautaires" #: src/screens/Onboarding/index.tsx:25 msgid "Nature" @@ -2816,29 +2644,20 @@ msgstr "Navigue vers votre profil" #: src/components/ReportDialog/SelectReportOptionView.tsx:123 msgid "Need to report a copyright violation?" -msgstr "" - -#: src/view/com/modals/EmbedConsent.tsx:107 -#: src/view/com/modals/EmbedConsent.tsx:123 -#~ msgid "Never load embeds from {0}" -#~ msgstr "Ne jamais charger les contenus intégrés de {0}" +msgstr "Besoin de signaler une violation des droits d’auteur ?" #: src/view/com/auth/onboarding/WelcomeDesktop.tsx:72 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:74 msgid "Never lose access to your followers and data." -msgstr "Ne perdez jamais l’accès à vos followers et à vos données." +msgstr "Ne perdez jamais l’accès à vos abonné·e·s et à vos données." #: src/screens/Onboarding/StepFinished.tsx:123 msgid "Never lose access to your followers or data." -msgstr "Ne perdez jamais l’accès à vos followers ou à vos données." - -#: src/components/dialogs/MutedWords.tsx:293 -#~ msgid "Nevermind" -#~ msgstr "Peu importe" +msgstr "Ne perdez jamais l’accès à vos abonné·e·s ou à vos données." #: src/view/com/modals/ChangeHandle.tsx:519 msgid "Nevermind, create a handle for me" -msgstr "" +msgstr "Peu importe, créez un pseudo pour moi" #: src/view/screens/Lists.tsx:76 msgctxt "action" @@ -2931,7 +2750,7 @@ msgstr "Aucune description" #: src/view/com/modals/ChangeHandle.tsx:405 msgid "No DNS Panel" -msgstr "" +msgstr "Pas de panneau DNS" #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:118 msgid "No longer following {0}" @@ -2939,7 +2758,7 @@ msgstr "Ne suit plus {0}" #: src/screens/Signup/StepHandle.tsx:115 msgid "No longer than 253 characters" -msgstr "" +msgstr "Pas plus de 253 caractères" #: src/view/com/notifications/Feed.tsx:109 msgid "No notifications yet!" @@ -2976,11 +2795,11 @@ msgstr "Personne" #: src/components/LikedByList.tsx:79 #: src/components/LikesDialog.tsx:99 msgid "Nobody has liked this yet. Maybe you should be the first!" -msgstr "" +msgstr "Personne n’a encore liké. Peut-être devriez-vous ouvrir la voie !" #: src/lib/moderation/useGlobalLabelStrings.ts:42 msgid "Non-sexual Nudity" -msgstr "" +msgstr "Nudité non sexuelle" #: src/view/com/modals/SelfLabel.tsx:135 msgid "Not Applicable." @@ -3000,7 +2819,7 @@ msgstr "Pas maintenant" #: src/view/com/util/forms/PostDropdownBtn.tsx:364 #: src/view/com/util/post-ctrls/PostCtrls.tsx:248 msgid "Note about sharing" -msgstr "" +msgstr "Note sur le partage" #: src/screens/Moderation/index.tsx:540 msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites." @@ -3022,19 +2841,15 @@ msgstr "Nudité" #: src/lib/moderation/useReportOptions.ts:71 msgid "Nudity or adult content not labeled as such" -msgstr "" - -#: src/lib/moderation/useReportOptions.ts:71 -#~ msgid "Nudity or pornography not labeled as such" -#~ msgstr "" +msgstr "Nudité ou contenu adulte non identifié comme tel" #: src/screens/Signup/index.tsx:143 msgid "of" -msgstr "" +msgstr "sur" #: src/lib/moderation/useLabelBehaviorDescription.ts:11 msgid "Off" -msgstr "" +msgstr "Éteint" #: src/view/com/util/ErrorBoundary.tsx:49 msgid "Oh no!" @@ -3047,7 +2862,7 @@ msgstr "Oh non ! Il y a eu un problème." #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:126 #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:333 msgid "OK" -msgstr "" +msgstr "OK" #: src/screens/Login/PasswordUpdatedForm.tsx:44 msgid "Okay" @@ -3071,7 +2886,7 @@ msgstr "Seul {0} peut répondre." #: src/screens/Signup/StepHandle.tsx:98 msgid "Only contains letters, numbers, and hyphens" -msgstr "" +msgstr "Ne contient que des lettres, des chiffres et des traits d’union" #: src/components/Lists.tsx:75 msgid "Oops, something went wrong!" @@ -3087,10 +2902,6 @@ msgstr "Oups !" msgid "Open" msgstr "Ouvrir" -#: src/view/screens/Moderation.tsx:75 -#~ msgid "Open content filtering settings" -#~ msgstr "Ouvrir les paramètres de filtrage de contenu" - #: src/view/com/composer/Composer.tsx:491 #: src/view/com/composer/Composer.tsx:492 msgid "Open emoji picker" @@ -3098,7 +2909,7 @@ msgstr "Ouvrir le sélecteur d’emoji" #: src/view/screens/ProfileFeed.tsx:311 msgid "Open feed options menu" -msgstr "" +msgstr "Ouvrir le menu des options de fil d’actu" #: src/view/screens/Settings/index.tsx:685 msgid "Open links with in-app browser" @@ -3106,11 +2917,7 @@ msgstr "Ouvrir des liens avec le navigateur interne à l’appli" #: src/screens/Moderation/index.tsx:227 msgid "Open muted words and tags settings" -msgstr "" - -#: src/view/screens/Moderation.tsx:92 -#~ msgid "Open muted words settings" -#~ msgstr "Ouvrir les paramètres des mots masqués" +msgstr "Ouvrir les paramètres des mots masqués et mots-clés" #: src/view/com/home/HomeHeaderLayoutMobile.tsx:52 msgid "Open navigation" @@ -3127,7 +2934,7 @@ msgstr "Ouvrir la page Storybook" #: src/view/screens/Settings/index.tsx:780 msgid "Open system log" -msgstr "" +msgstr "Ouvrir le journal du système" #: src/view/com/util/forms/DropdownButton.tsx:154 msgid "Opens {numItems} options" @@ -3157,10 +2964,6 @@ msgstr "Ouvre les paramètres linguistiques configurables" msgid "Opens device photo gallery" msgstr "Ouvre la galerie de photos de l’appareil" -#: src/view/com/profile/ProfileHeader.tsx:420 -#~ msgid "Opens editor for profile display name, avatar, background image, and description" -#~ msgstr "Ouvre l’éditeur pour le nom d’affichage du profil, l’avatar, l’image d’arrière-plan et la description" - #: src/view/screens/Settings/index.tsx:620 msgid "Opens external embeds settings" msgstr "Ouvre les paramètres d’intégration externe" @@ -3168,20 +2971,12 @@ msgstr "Ouvre les paramètres d’intégration externe" #: src/view/com/auth/SplashScreen.tsx:50 #: src/view/com/auth/SplashScreen.web.tsx:94 msgid "Opens flow to create a new Bluesky account" -msgstr "" +msgstr "Ouvre le flux de création d’un nouveau compte Bluesky" #: src/view/com/auth/SplashScreen.tsx:65 #: src/view/com/auth/SplashScreen.web.tsx:109 msgid "Opens flow to sign into your existing Bluesky account" -msgstr "" - -#: src/view/com/profile/ProfileHeader.tsx:575 -#~ msgid "Opens followers list" -#~ msgstr "Ouvre la liste des comptes abonnés" - -#: src/view/com/profile/ProfileHeader.tsx:594 -#~ msgid "Opens following list" -#~ msgstr "Ouvre la liste des abonnements" +msgstr "Ouvre le flux pour vous connecter à votre compte Bluesky existant" #: src/view/com/modals/InviteCodes.tsx:173 msgid "Opens list of invite codes" @@ -3189,27 +2984,23 @@ msgstr "Ouvre la liste des codes d’invitation" #: src/view/screens/Settings/index.tsx:762 msgid "Opens modal for account deletion confirmation. Requires email code" -msgstr "" - -#: src/view/screens/Settings/index.tsx:774 -#~ msgid "Opens modal for account deletion confirmation. Requires email code." -#~ msgstr "Ouvre la fenêtre modale pour confirmer la suppression du compte. Requiert un code e-mail." +msgstr "Ouvre la fenêtre modale pour confirmer la suppression du compte. Requiert un code e-mail." #: src/view/screens/Settings/index.tsx:720 msgid "Opens modal for changing your Bluesky password" -msgstr "" +msgstr "Ouvre une fenêtre modale pour changer le mot de passe de Bluesky" #: src/view/screens/Settings/index.tsx:669 msgid "Opens modal for choosing a new Bluesky handle" -msgstr "" +msgstr "Ouvre une fenêtre modale pour choisir un nouveau pseudo Bluesky" #: src/view/screens/Settings/index.tsx:743 msgid "Opens modal for downloading your Bluesky account data (repository)" -msgstr "" +msgstr "Ouvre une fenêtre modale pour télécharger les données du compte Bluesky (dépôt)" #: src/view/screens/Settings/index.tsx:932 msgid "Opens modal for email verification" -msgstr "" +msgstr "Ouvre une fenêtre modale pour la vérification de l’e-mail" #: src/view/com/modals/ChangeHandle.tsx:282 msgid "Opens modal for using custom domain" @@ -3234,23 +3025,15 @@ msgstr "Ouvre l’écran avec tous les fils d’actu enregistrés" #: src/view/screens/Settings/index.tsx:647 msgid "Opens the app password settings" -msgstr "" - -#: src/view/screens/Settings/index.tsx:676 -#~ msgid "Opens the app password settings page" -#~ msgstr "Ouvre la page de configuration du mot de passe" +msgstr "Ouvre les paramètres du mot de passe de l’application" #: src/view/screens/Settings/index.tsx:505 msgid "Opens the Following feed preferences" -msgstr "" - -#: src/view/screens/Settings/index.tsx:535 -#~ msgid "Opens the home feed preferences" -#~ msgstr "Ouvre les préférences du fil d’accueil" +msgstr "Ouvre les préférences du fil d’actu « Following »" #: src/view/com/modals/LinkWarning.tsx:93 msgid "Opens the linked website" -msgstr "" +msgstr "Ouvre le site web lié" #: src/view/screens/Settings/index.tsx:793 #: src/view/screens/Settings/index.tsx:803 @@ -3271,7 +3054,7 @@ msgstr "Option {0} sur {numItems}" #: src/components/ReportDialog/SubmitView.tsx:160 msgid "Optionally provide additional information below:" -msgstr "" +msgstr "Ajoutez des informations supplémentaires ci-dessous (optionnel) :" #: src/view/com/modals/Threadgate.tsx:89 msgid "Or combine these options:" @@ -3279,7 +3062,7 @@ msgstr "Ou une combinaison de ces options :" #: src/lib/moderation/useReportOptions.ts:25 msgid "Other" -msgstr "" +msgstr "Autre" #: src/components/AccountList.tsx:73 msgid "Other account" @@ -3307,7 +3090,7 @@ msgstr "Mot de passe" #: src/view/com/modals/ChangePassword.tsx:142 msgid "Password Changed" -msgstr "" +msgstr "Mot de passe modifié" #: src/screens/Login/index.tsx:157 msgid "Password updated" @@ -3320,7 +3103,7 @@ msgstr "Mot de passe mis à jour !" #: src/view/screens/Search/Search.tsx:447 #: src/view/screens/Search/Search.tsx:456 msgid "People" -msgstr "" +msgstr "Personnes" #: src/Navigation.tsx:164 msgid "People followed by @{0}" @@ -3353,7 +3136,7 @@ msgstr "Ajouter à l’accueil" #: src/view/screens/ProfileFeed.tsx:306 msgid "Pin to Home" -msgstr "" +msgstr "Ajouter à l’accueil" #: src/view/screens/SavedFeeds.tsx:89 msgid "Pinned Feeds" @@ -3410,12 +3193,7 @@ msgstr "Veuillez également entrer votre mot de passe :" #: src/components/moderation/LabelsOnMeDialog.tsx:221 msgid "Please explain why you think this label was incorrectly applied by {0}" -msgstr "" - -#: src/view/com/modals/AppealLabel.tsx:72 -#: src/view/com/modals/AppealLabel.tsx:75 -#~ msgid "Please tell us why you think this content warning was incorrectly applied!" -#~ msgstr "Dites-nous donc pourquoi vous pensez que cet avertissement de contenu a été appliqué à tort !" +msgstr "Veuillez expliquer pourquoi vous pensez que cette étiquette a été appliquée à tort par {0}" #: src/view/com/modals/VerifyEmail.tsx:101 msgid "Please Verify Your Email" @@ -3433,10 +3211,6 @@ msgstr "Politique" msgid "Porn" msgstr "Porno" -#: src/lib/moderation/useGlobalLabelStrings.ts:34 -#~ msgid "Pornography" -#~ msgstr "" - #: src/view/com/composer/Composer.tsx:367 #: src/view/com/composer/Composer.tsx:375 msgctxt "action" @@ -3469,12 +3243,12 @@ msgstr "Post caché" #: src/components/moderation/ModerationDetailsDialog.tsx:97 #: src/lib/moderation/useModerationCauseDescription.ts:99 msgid "Post Hidden by Muted Word" -msgstr "" +msgstr "Post caché par mot masqué" #: src/components/moderation/ModerationDetailsDialog.tsx:100 #: src/lib/moderation/useModerationCauseDescription.ts:108 msgid "Post Hidden by You" -msgstr "" +msgstr "Post caché par vous" #: src/view/com/composer/select-language/SelectLangBtn.tsx:87 msgid "Post language" @@ -3512,13 +3286,13 @@ msgstr "Lien potentiellement trompeur" #: src/components/forms/HostingProvider.tsx:46 msgid "Press to change hosting provider" -msgstr "" +msgstr "Appuyer pour changer d’hébergeur" #: src/components/Error.tsx:74 #: src/components/Lists.tsx:80 #: src/screens/Signup/index.tsx:187 msgid "Press to retry" -msgstr "" +msgstr "Appuyer pour réessayer" #: src/view/com/lightbox/Lightbox.web.tsx:150 msgid "Previous image" @@ -3552,7 +3326,7 @@ msgstr "Traitement…" #: src/view/screens/DebugMod.tsx:888 #: src/view/screens/Profile.tsx:361 msgid "profile" -msgstr "" +msgstr "profil" #: src/view/shell/bottom-bar/BottomBar.tsx:261 #: src/view/shell/desktop/LeftNav.tsx:419 @@ -3614,7 +3388,7 @@ msgstr "Ratios" #: src/view/screens/Search/Search.tsx:924 msgid "Recent Searches" -msgstr "" +msgstr "Recherches récentes" #: src/view/com/auth/onboarding/RecommendedFeeds.tsx:117 msgid "Recommended Feeds" @@ -3633,29 +3407,25 @@ msgstr "Comptes recommandés" msgid "Remove" msgstr "Supprimer" -#: src/view/com/feeds/FeedSourceCard.tsx:108 -#~ msgid "Remove {0} from my feeds?" -#~ msgstr "Supprimer {0} de mes fils d’actu ?" - #: src/view/com/util/AccountDropdownBtn.tsx:22 msgid "Remove account" msgstr "Supprimer compte" #: src/view/com/util/UserAvatar.tsx:360 msgid "Remove Avatar" -msgstr "" +msgstr "Supprimer l’avatar" #: src/view/com/util/UserBanner.tsx:148 msgid "Remove Banner" -msgstr "" +msgstr "Supprimer l’image d’en-tête" #: src/view/com/posts/FeedErrorMessage.tsx:160 msgid "Remove feed" -msgstr "Supprimer fil d’actu" +msgstr "Supprimer le fil d’actu" #: src/view/com/posts/FeedErrorMessage.tsx:201 msgid "Remove feed?" -msgstr "" +msgstr "Supprimer le fil d’actu ?" #: src/view/com/feeds/FeedSourceCard.tsx:173 #: src/view/com/feeds/FeedSourceCard.tsx:233 @@ -3666,7 +3436,7 @@ msgstr "Supprimer de mes fils d’actu" #: src/view/com/feeds/FeedSourceCard.tsx:278 msgid "Remove from my feeds?" -msgstr "" +msgstr "Supprimer de mes fils d’actu ?" #: src/view/com/composer/photos/Gallery.tsx:167 msgid "Remove image" @@ -3684,17 +3454,9 @@ msgstr "Supprimer le mot masqué de votre liste" msgid "Remove repost" msgstr "Supprimer le repost" -#: src/view/com/feeds/FeedSourceCard.tsx:175 -#~ msgid "Remove this feed from my feeds?" -#~ msgstr "Supprimer ce fil d’actu ?" - #: src/view/com/posts/FeedErrorMessage.tsx:202 msgid "Remove this feed from your saved feeds" -msgstr "" - -#: src/view/com/posts/FeedErrorMessage.tsx:132 -#~ msgid "Remove this feed from your saved feeds?" -#~ msgstr "Supprimer ce fil d’actu de vos fils d’actu enregistrés ?" +msgstr "Supprimer ce fil d’actu de vos fils d’actu enregistrés" #: src/view/com/modals/ListAddRemoveUsers.tsx:199 #: src/view/com/modals/UserAddRemoveLists.tsx:152 @@ -3707,7 +3469,7 @@ msgstr "Supprimé de mes fils d’actu" #: src/view/screens/ProfileFeed.tsx:210 msgid "Removed from your feeds" -msgstr "" +msgstr "Supprimé de vos fils d’actu" #: src/view/com/composer/ExternalEmbed.tsx:71 msgid "Removes default thumbnail from {0}" @@ -3736,10 +3498,6 @@ msgctxt "description" msgid "Reply to <0/>" msgstr "Réponse à <0/>" -#: src/view/com/modals/report/Modal.tsx:166 -#~ msgid "Report {collectionName}" -#~ msgstr "Signaler {collectionName}" - #: src/view/com/profile/ProfileMenu.tsx:319 #: src/view/com/profile/ProfileMenu.tsx:322 msgid "Report Account" @@ -3747,7 +3505,7 @@ msgstr "Signaler le compte" #: src/components/ReportDialog/index.tsx:49 msgid "Report dialog" -msgstr "" +msgstr "Fenêtre de dialogue de signalement" #: src/view/screens/ProfileFeed.tsx:363 #: src/view/screens/ProfileFeed.tsx:365 @@ -3765,23 +3523,23 @@ msgstr "Signaler le post" #: src/components/ReportDialog/SelectReportOptionView.tsx:42 msgid "Report this content" -msgstr "" +msgstr "Signaler ce contenu" #: src/components/ReportDialog/SelectReportOptionView.tsx:55 msgid "Report this feed" -msgstr "" +msgstr "Signaler ce fil d’actu" #: src/components/ReportDialog/SelectReportOptionView.tsx:52 msgid "Report this list" -msgstr "" +msgstr "Signaler cette liste" #: src/components/ReportDialog/SelectReportOptionView.tsx:49 msgid "Report this post" -msgstr "" +msgstr "Signaler ce post" #: src/components/ReportDialog/SelectReportOptionView.tsx:46 msgid "Report this user" -msgstr "" +msgstr "Signaler ce compte" #: src/view/com/modals/Repost.tsx:44 #: src/view/com/modals/Repost.tsx:49 @@ -3808,13 +3566,9 @@ msgstr "Republié par" msgid "Reposted by {0}" msgstr "Republié par {0}" -#: src/view/com/posts/FeedItem.tsx:214 -#~ msgid "Reposted by <0/>" -#~ msgstr "Republié par <0/>" - #: src/view/com/posts/FeedItem.tsx:216 msgid "Reposted by <0><1/>" -msgstr "" +msgstr "Republié par <0><1/>" #: src/view/com/notifications/FeedItem.tsx:168 msgid "reposted your post" @@ -3850,10 +3604,6 @@ msgstr "Réinitialiser le code" msgid "Reset Code" msgstr "Code de réinitialisation" -#: src/view/screens/Settings/index.tsx:824 -#~ msgid "Reset onboarding" -#~ msgstr "Réinitialiser le didacticiel" - #: src/view/screens/Settings/index.tsx:822 #: src/view/screens/Settings/index.tsx:825 msgid "Reset onboarding state" @@ -3863,10 +3613,6 @@ msgstr "Réinitialisation du didacticiel" msgid "Reset password" msgstr "Réinitialiser mot de passe" -#: src/view/screens/Settings/index.tsx:814 -#~ msgid "Reset preferences" -#~ msgstr "Réinitialiser les préférences" - #: src/view/screens/Settings/index.tsx:812 #: src/view/screens/Settings/index.tsx:815 msgid "Reset preferences state" @@ -3908,12 +3654,12 @@ msgstr "Retourne à la page précédente" #: src/view/screens/NotFound.tsx:59 msgid "Returns to home page" -msgstr "" +msgstr "Retour à la page d’accueil" #: src/view/screens/NotFound.tsx:58 #: src/view/screens/ProfileFeed.tsx:113 msgid "Returns to previous page" -msgstr "" +msgstr "Retour à la page précédente" #: src/components/dialogs/BirthDateSettings.tsx:125 #: src/view/com/modals/ChangeHandle.tsx:174 @@ -3934,7 +3680,7 @@ msgstr "Enregistrer le texte alt" #: src/components/dialogs/BirthDateSettings.tsx:119 msgid "Save birthday" -msgstr "" +msgstr "Enregistrer la date de naissance" #: src/view/com/modals/EditProfile.tsx:233 msgid "Save Changes" @@ -3951,7 +3697,7 @@ msgstr "Enregistrer le recadrage de l’image" #: src/view/screens/ProfileFeed.tsx:347 #: src/view/screens/ProfileFeed.tsx:353 msgid "Save to my feeds" -msgstr "" +msgstr "Enregistrer dans mes fils d’actu" #: src/view/screens/SavedFeeds.tsx:123 msgid "Saved Feeds" @@ -3959,11 +3705,11 @@ msgstr "Fils d’actu enregistrés" #: src/view/com/lightbox/Lightbox.tsx:81 msgid "Saved to your camera roll." -msgstr "" +msgstr "Enregistré dans votre photothèque" #: src/view/screens/ProfileFeed.tsx:214 msgid "Saved to your feeds" -msgstr "" +msgstr "Enregistré à mes fils d’actu" #: src/view/com/modals/EditProfile.tsx:226 msgid "Saves any changes to your profile" @@ -3975,7 +3721,7 @@ msgstr "Enregistre le changement de pseudo en {handle}" #: src/view/com/modals/crop-image/CropImage.web.tsx:146 msgid "Saves image crop settings" -msgstr "" +msgstr "Enregistre les paramètres de recadrage de l’image" #: src/screens/Onboarding/index.tsx:36 msgid "Science" @@ -4044,23 +3790,19 @@ msgstr "Voir les posts <0>{displayTag} de ce compte" #: src/view/com/notifications/FeedItem.tsx:419 #: src/view/com/util/UserAvatar.tsx:381 msgid "See profile" -msgstr "" +msgstr "Voir le profil" #: src/view/screens/SavedFeeds.tsx:164 msgid "See this guide" msgstr "Voir ce guide" -#: src/view/com/auth/HomeLoggedOutCTA.tsx:40 -#~ msgid "See what's next" -#~ msgstr "Voir la suite" - #: src/view/com/util/Selector.tsx:106 msgid "Select {item}" msgstr "Sélectionner {item}" #: src/screens/Login/ChooseAccountForm.tsx:61 msgid "Select account" -msgstr "" +msgstr "Sélectionner un compte" #: src/screens/Login/index.tsx:120 msgid "Select from an existing account" @@ -4068,28 +3810,23 @@ msgstr "Sélectionner un compte existant" #: src/view/screens/LanguageSettings.tsx:299 msgid "Select languages" -msgstr "" +msgstr "Sélectionner les langues" #: src/components/ReportDialog/SelectLabelerView.tsx:30 msgid "Select moderator" -msgstr "" +msgstr "Sélectionner une modération" #: src/view/com/util/Selector.tsx:107 msgid "Select option {i} of {numItems}" msgstr "Sélectionne l’option {i} sur {numItems}" -#: src/view/com/auth/create/Step1.tsx:96 -#: src/view/com/auth/login/LoginForm.tsx:153 -#~ msgid "Select service" -#~ msgstr "Sélectionner un service" - #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:52 msgid "Select some accounts below to follow" msgstr "Sélectionnez quelques comptes à suivre ci-dessous" #: src/components/ReportDialog/SubmitView.tsx:133 msgid "Select the moderation service(s) to report to" -msgstr "" +msgstr "Sélectionnez le(s) service(s) de modération destinataires du signalement" #: src/view/com/auth/server-input/index.tsx:82 msgid "Select the service that hosts your data." @@ -4107,17 +3844,13 @@ msgstr "Sélectionnez ce que vous voulez voir (ou ne pas voir), et nous nous occ msgid "Select which languages you want your subscribed feeds to include. If none are selected, all languages will be shown." msgstr "Sélectionnez les langues que vous souhaitez voir figurer dans les fils d’actu que vous suivez. Si aucune langue n’est sélectionnée, toutes les langues seront affichées." -#: src/view/screens/LanguageSettings.tsx:98 -#~ msgid "Select your app language for the default text to display in the app" -#~ msgstr "Sélectionnez la langue de votre application à afficher par défaut" - #: src/view/screens/LanguageSettings.tsx:98 msgid "Select your app language for the default text to display in the app." -msgstr "" +msgstr "Sélectionnez votre langue par défaut pour les textes de l’application." #: src/screens/Signup/StepInfo/index.tsx:135 msgid "Select your date of birth" -msgstr "" +msgstr "Sélectionnez votre date de naissance" #: src/screens/Onboarding/StepInterests/index.tsx:200 msgid "Select your interests from the options below" @@ -4157,15 +3890,11 @@ msgstr "Envoyer des commentaires" #: src/components/ReportDialog/SubmitView.tsx:213 #: src/components/ReportDialog/SubmitView.tsx:217 msgid "Send report" -msgstr "" - -#: src/view/com/modals/report/SendReportButton.tsx:45 -#~ msgid "Send Report" -#~ msgstr "Envoyer le rapport" +msgstr "Envoyer le rapport" #: src/components/ReportDialog/SelectLabelerView.tsx:44 msgid "Send report to {0}" -msgstr "" +msgstr "Envoyer le rapport à {0}" #: src/view/com/modals/DeleteAccount.tsx:132 msgid "Sends email with confirmation code for account deletion" @@ -4175,48 +3904,14 @@ msgstr "Envoie un e-mail avec le code de confirmation pour la suppression du com msgid "Server address" msgstr "Adresse du serveur" -#: src/view/com/modals/ContentFilteringSettings.tsx:311 -#~ msgid "Set {value} for {labelGroup} content moderation policy" -#~ msgstr "Choisis {value} pour la politique de modération de contenu {labelGroup}" - -#: src/view/com/modals/ContentFilteringSettings.tsx:160 -#: src/view/com/modals/ContentFilteringSettings.tsx:179 -#~ msgctxt "action" -#~ msgid "Set Age" -#~ msgstr "Enregistrer l’âge" - #: src/screens/Moderation/index.tsx:304 msgid "Set birthdate" -msgstr "" - -#: src/view/screens/Settings/index.tsx:488 -#~ msgid "Set color theme to dark" -#~ msgstr "Change le thème de couleur en sombre" - -#: src/view/screens/Settings/index.tsx:481 -#~ msgid "Set color theme to light" -#~ msgstr "Change le thème de couleur en clair" - -#: src/view/screens/Settings/index.tsx:475 -#~ msgid "Set color theme to system setting" -#~ msgstr "Change le thème de couleur en fonction du paramètre système" - -#: src/view/screens/Settings/index.tsx:514 -#~ msgid "Set dark theme to the dark theme" -#~ msgstr "Choisir le thème le plus sombre comme thème sombre" - -#: src/view/screens/Settings/index.tsx:507 -#~ msgid "Set dark theme to the dim theme" -#~ msgstr "Choisir le thème atténué comme thème sombre" +msgstr "Entrez votre date de naissance" #: src/screens/Login/SetNewPasswordForm.tsx:102 msgid "Set new password" msgstr "Définir un nouveau mot de passe" -#: src/view/com/auth/create/Step1.tsx:202 -#~ msgid "Set password" -#~ msgstr "Définit le mot de passe" - #: src/view/screens/PreferencesFollowingFeed.tsx:225 msgid "Set this setting to \"No\" to hide all quote posts from your feed. Reposts will still be visible." msgstr "Choisissez « Non » pour cacher toutes les citations sur votre fils d’actu. Les reposts seront toujours visibles." @@ -4247,48 +3942,39 @@ msgstr "Définit le pseudo Bluesky" #: src/view/screens/Settings/index.tsx:458 msgid "Sets color theme to dark" -msgstr "" +msgstr "Change le thème de couleur en sombre" #: src/view/screens/Settings/index.tsx:451 msgid "Sets color theme to light" -msgstr "" +msgstr "Change le thème de couleur en clair" #: src/view/screens/Settings/index.tsx:445 msgid "Sets color theme to system setting" -msgstr "" +msgstr "Change le thème de couleur en fonction du paramètre système" #: src/view/screens/Settings/index.tsx:484 msgid "Sets dark theme to the dark theme" -msgstr "" +msgstr "Change le thème sombre comme étant le plus sombre" #: src/view/screens/Settings/index.tsx:477 msgid "Sets dark theme to the dim theme" -msgstr "" +msgstr "Change le thème sombre comme étant le thème atténué" #: src/screens/Login/ForgotPasswordForm.tsx:113 msgid "Sets email for password reset" msgstr "Définit l’e-mail pour la réinitialisation du mot de passe" -#: src/view/com/auth/login/ForgotPasswordForm.tsx:122 -#~ msgid "Sets hosting provider for password reset" -#~ msgstr "Définit l’hébergeur pour la réinitialisation du mot de passe" - #: src/view/com/modals/crop-image/CropImage.web.tsx:124 msgid "Sets image aspect ratio to square" -msgstr "" +msgstr "Définit le rapport d’aspect de l’image comme étant carré" #: src/view/com/modals/crop-image/CropImage.web.tsx:114 msgid "Sets image aspect ratio to tall" -msgstr "" +msgstr "Définit le rapport d’aspect de l’image comme portrait" #: src/view/com/modals/crop-image/CropImage.web.tsx:104 msgid "Sets image aspect ratio to wide" -msgstr "" - -#: src/view/com/auth/create/Step1.tsx:97 -#: src/view/com/auth/login/LoginForm.tsx:154 -#~ msgid "Sets server for the Bluesky client" -#~ msgstr "Définit le serveur pour le client Bluesky" +msgstr "Définit le rapport d’aspect de l’image comme paysage" #: src/Navigation.tsx:139 #: src/view/screens/Settings/index.tsx:316 @@ -4304,7 +3990,7 @@ msgstr "Activité sexuelle ou nudité érotique." #: src/lib/moderation/useGlobalLabelStrings.ts:38 msgid "Sexually Suggestive" -msgstr "" +msgstr "Sexuellement suggestif" #: src/view/com/lightbox/Lightbox.tsx:141 msgctxt "action" @@ -4324,7 +4010,7 @@ msgstr "Partager" #: src/view/com/util/forms/PostDropdownBtn.tsx:369 #: src/view/com/util/post-ctrls/PostCtrls.tsx:253 msgid "Share anyway" -msgstr "" +msgstr "Partager quand même" #: src/view/screens/ProfileFeed.tsx:373 #: src/view/screens/ProfileFeed.tsx:375 @@ -4334,11 +4020,11 @@ msgstr "Partager le fil d’actu" #: src/view/com/modals/LinkWarning.tsx:89 #: src/view/com/modals/LinkWarning.tsx:95 msgid "Share Link" -msgstr "" +msgstr "Partager le lien" #: src/view/com/modals/LinkWarning.tsx:92 msgid "Shares the linked website" -msgstr "" +msgstr "Partage le site web lié" #: src/components/moderation/ContentHider.tsx:115 #: src/components/moderation/LabelPreference.tsx:136 @@ -4360,15 +4046,11 @@ msgstr "Afficher quand même" #: src/lib/moderation/useLabelBehaviorDescription.ts:27 #: src/lib/moderation/useLabelBehaviorDescription.ts:63 msgid "Show badge" -msgstr "" +msgstr "Afficher le badge" #: src/lib/moderation/useLabelBehaviorDescription.ts:61 msgid "Show badge and filter from feeds" -msgstr "" - -#: src/view/com/modals/EmbedConsent.tsx:87 -#~ msgid "Show embeds from {0}" -#~ msgstr "Afficher les intégrations de {0}" +msgstr "Afficher les badges et filtrer des fils d’actu" #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:200 msgid "Show follows similar to {0}" @@ -4439,15 +4121,11 @@ msgstr "Afficher les comptes" #: src/lib/moderation/useLabelBehaviorDescription.ts:58 msgid "Show warning" -msgstr "" +msgstr "Afficher l’avertissement" #: src/lib/moderation/useLabelBehaviorDescription.ts:56 msgid "Show warning and filter from feeds" -msgstr "" - -#: src/view/com/profile/ProfileHeader.tsx:462 -#~ msgid "Shows a list of users similar to this user." -#~ msgstr "Affiche une liste de comptes similaires à ce compte." +msgstr "Afficher l’avertissement et filtrer des fils d’actu" #: src/view/com/post-thread/PostThreadFollowBtn.tsx:130 msgid "Shows posts from {0} in your feed" @@ -4474,12 +4152,6 @@ msgstr "Affiche les posts de {0} dans votre fil d’actu" msgid "Sign in" msgstr "Connexion" -#: src/view/com/auth/HomeLoggedOutCTA.tsx:82 -#: src/view/com/auth/SplashScreen.tsx:86 -#: src/view/com/auth/SplashScreen.web.tsx:91 -#~ msgid "Sign In" -#~ msgstr "Connexion" - #: src/components/AccountList.tsx:109 msgid "Sign in as {0}" msgstr "Se connecter en tant que {0}" @@ -4490,15 +4162,11 @@ msgstr "Se connecter en tant que…" #: src/components/dialogs/Signin.tsx:75 msgid "Sign in or create your account to join the conversation!" -msgstr "" - -#: src/view/com/auth/login/LoginForm.tsx:140 -#~ msgid "Sign into" -#~ msgstr "Se connecter à" +msgstr "Connectez-vous ou créez votre compte pour participer à la conversation !" #: src/components/dialogs/Signin.tsx:46 msgid "Sign into Bluesky or create a new account" -msgstr "" +msgstr "Connectez-vous à Bluesky ou créez un nouveau compte" #: src/view/screens/Settings/index.tsx:118 #: src/view/screens/Settings/index.tsx:121 @@ -4534,10 +4202,6 @@ msgstr "Connecté en tant que" msgid "Signed in as @{0}" msgstr "Connecté en tant que @{0}" -#: src/view/com/modals/SwitchAccount.tsx:70 -#~ msgid "Signs {0} out of Bluesky" -#~ msgstr "Déconnecte {0} de Bluesky" - #: src/screens/Onboarding/StepInterests/index.tsx:239 #: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:203 #: src/view/com/auth/onboarding/WelcomeMobile.tsx:35 @@ -4556,11 +4220,7 @@ msgstr "Développement de logiciels" #: src/screens/Moderation/index.tsx:114 #: src/screens/Profile/Sections/Labels.tsx:87 msgid "Something went wrong, please try again." -msgstr "" - -#: src/components/Lists.tsx:203 -#~ msgid "Something went wrong!" -#~ msgstr "Quelque chose n’a pas marché !" +msgstr "Quelque chose n’a pas marché, veuillez réessayer." #: src/App.native.tsx:64 msgid "Sorry! Your session expired. Please log in again." @@ -4576,15 +4236,15 @@ msgstr "Trier les réponses au même post par :" #: src/components/moderation/LabelsOnMeDialog.tsx:146 msgid "Source:" -msgstr "" +msgstr "Source :" #: src/lib/moderation/useReportOptions.ts:65 msgid "Spam" -msgstr "" +msgstr "Spam" #: src/lib/moderation/useReportOptions.ts:53 msgid "Spam; excessive mentions or replies" -msgstr "" +msgstr "Spam ; mentions ou réponses excessives" #: src/screens/Onboarding/index.tsx:30 msgid "Sports" @@ -4600,11 +4260,7 @@ msgstr "État du service" #: src/screens/Signup/index.tsx:143 msgid "Step" -msgstr "" - -#: src/view/com/auth/create/StepHeader.tsx:22 -#~ msgid "Step {0} of {numSteps}" -#~ msgstr "Étape {0} sur {numSteps}" +msgstr "Étape" #: src/view/screens/Settings/index.tsx:295 msgid "Storage cleared, you need to restart the app now." @@ -4626,11 +4282,11 @@ msgstr "S’abonner" #: src/screens/Profile/Sections/Labels.tsx:191 msgid "Subscribe to @{0} to use these labels:" -msgstr "" +msgstr "Abonnez-vous à @{0} pour utiliser ces étiquettes :" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:227 msgid "Subscribe to Labeler" -msgstr "" +msgstr "S’abonner à l’étiqueteur" #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:172 #: src/screens/Onboarding/StepAlgoFeeds/FeedCard.tsx:307 @@ -4639,7 +4295,7 @@ msgstr "S’abonner au fil d’actu {0}" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:191 msgid "Subscribe to this labeler" -msgstr "" +msgstr "S’abonner à cet étiqueteur" #: src/view/screens/ProfileList.tsx:588 msgid "Subscribe to this list" @@ -4720,7 +4376,7 @@ msgstr "Conditions d’utilisation" #: src/lib/moderation/useReportOptions.ts:79 #: src/lib/moderation/useReportOptions.ts:87 msgid "Terms used violate community standards" -msgstr "" +msgstr "Termes utilisés qui violent les normes de la communauté" #: src/components/dialogs/MutedWords.tsx:323 msgid "text" @@ -4732,11 +4388,11 @@ msgstr "Champ de saisie de texte" #: src/components/ReportDialog/SubmitView.tsx:76 msgid "Thank you. Your report has been sent." -msgstr "" +msgstr "Nous vous remercions. Votre rapport a été envoyé." #: src/view/com/modals/ChangeHandle.tsx:465 msgid "That contains the following:" -msgstr "" +msgstr "Qui contient les éléments suivants :" #: src/screens/Signup/index.tsx:85 msgid "That handle is already taken." @@ -4749,7 +4405,7 @@ msgstr "Ce compte pourra interagir avec vous après le déblocage." #: src/components/moderation/ModerationDetailsDialog.tsx:127 msgid "the author" -msgstr "" +msgstr "l’auteur" #: src/view/screens/CommunityGuidelines.tsx:36 msgid "The Community Guidelines have been moved to <0/>" @@ -4761,11 +4417,11 @@ msgstr "Notre politique de droits d’auteur a été déplacée vers <0/>" #: src/components/moderation/LabelsOnMeDialog.tsx:48 msgid "The following labels were applied to your account." -msgstr "" +msgstr "Les étiquettes suivantes ont été appliquées à votre compte." #: src/components/moderation/LabelsOnMeDialog.tsx:49 msgid "The following labels were applied to your content." -msgstr "" +msgstr "Les étiquettes suivantes ont été appliquées à votre contenu." #: src/screens/Onboarding/Layout.tsx:58 msgid "The following steps will help customize your Bluesky experience." @@ -4839,7 +4495,7 @@ msgstr "Il y a eu un problème lors de la récupération de vos listes. Appuyez #: src/components/ReportDialog/SubmitView.tsx:81 msgid "There was an issue sending your report. Please check your internet connection." -msgstr "" +msgstr "Il y a eu un problème lors de l’envoi de votre rapport. Veuillez vérifier votre connexion internet." #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:65 msgid "There was an issue syncing your preferences with the server" @@ -4892,15 +4548,15 @@ msgstr "Ce compte a demandé aux personnes de se connecter pour voir son profil. #: src/components/moderation/LabelsOnMeDialog.tsx:204 msgid "This appeal will be sent to <0>{0}." -msgstr "" +msgstr "Cet appel sera envoyé à <0>{0}." #: src/lib/moderation/useGlobalLabelStrings.ts:19 msgid "This content has been hidden by the moderators." -msgstr "" +msgstr "Ce contenu a été masqué par la modération." #: src/lib/moderation/useGlobalLabelStrings.ts:24 msgid "This content has received a general warning from moderators." -msgstr "" +msgstr "Ce contenu a reçu un avertissement général de la part de la modération." #: src/components/dialogs/EmbedConsent.tsx:64 msgid "This content is hosted by {0}. Do you want to enable external media?" @@ -4915,13 +4571,9 @@ msgstr "Ce contenu n’est pas disponible car l’un des comptes impliqués a bl msgid "This content is not viewable without a Bluesky account." msgstr "Ce contenu n’est pas visible sans un compte Bluesky." -#: src/view/screens/Settings/ExportCarDialog.tsx:75 -#~ msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost." -#~ msgstr "Cette fonctionnalité est en version bêta. Vous pouvez en savoir plus sur les exportations de dépôts dans <0>ce blogpost." - #: src/view/screens/Settings/ExportCarDialog.tsx:75 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost." -msgstr "" +msgstr "Cette fonctionnalité est en version bêta. Vous pouvez en savoir plus sur les exportations de dépôts dans <0>ce blogpost." #: src/view/com/posts/FeedErrorMessage.tsx:114 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." @@ -4947,11 +4599,11 @@ msgstr "Ceci est important au cas où vous auriez besoin de changer d’e-mail o #: src/components/moderation/ModerationDetailsDialog.tsx:124 msgid "This label was applied by {0}." -msgstr "" +msgstr "Cette étiquette a été apposée par {0}." #: src/screens/Profile/Sections/Labels.tsx:178 msgid "This labeler hasn't declared what labels it publishes, and may not be active." -msgstr "" +msgstr "Cet étiqueteur n’a pas déclaré les étiquettes qu’il publie et peut ne pas être actif." #: src/view/com/modals/LinkWarning.tsx:72 msgid "This link is taking you to the following website:" @@ -4963,7 +4615,7 @@ msgstr "Cette liste est vide !" #: src/screens/Profile/ErrorState.tsx:40 msgid "This moderation service is unavailable. See below for more details. If this issue persists, contact us." -msgstr "" +msgstr "Ce service de modération n’est pas disponible. Voir ci-dessous pour plus de détails. Si le problème persiste, contactez-nous." #: src/view/com/modals/AddAppPasswords.tsx:107 msgid "This name is already in use" @@ -4976,27 +4628,27 @@ msgstr "Ce post a été supprimé." #: src/view/com/util/forms/PostDropdownBtn.tsx:366 #: src/view/com/util/post-ctrls/PostCtrls.tsx:250 msgid "This post is only visible to logged-in users. It won't be visible to people who aren't logged in." -msgstr "" +msgstr "Ce post n’est visible que pour les personnes connectées. Il ne sera pas visible pour les personnes qui ne sont pas connectées." #: src/view/com/util/forms/PostDropdownBtn.tsx:348 msgid "This post will be hidden from feeds." -msgstr "" +msgstr "Ce post sera masqué des fils d’actu." #: src/view/com/profile/ProfileMenu.tsx:370 msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't logged in." -msgstr "" +msgstr "Ce profil n’est visible que pour les personnes connectées. Il ne sera pas visible pour les personnes qui ne sont pas connectées." #: src/screens/Signup/StepInfo/Policies.tsx:37 msgid "This service has not provided terms of service or a privacy policy." -msgstr "" +msgstr "Ce service n’a pas fourni de conditions d’utilisation ni de politique de confidentialité." #: src/view/com/modals/ChangeHandle.tsx:445 msgid "This should create a domain record at:" -msgstr "" +msgstr "Cela devrait créer un enregistrement de domaine à :" #: src/view/com/profile/ProfileFollowers.tsx:87 msgid "This user doesn't have any followers." -msgstr "" +msgstr "Ce compte n’a pas d’abonné·e·s." #: src/components/moderation/ModerationDetailsDialog.tsx:72 #: src/lib/moderation/useModerationCauseDescription.ts:68 @@ -5005,27 +4657,19 @@ msgstr "Ce compte vous a bloqué. Vous ne pouvez pas voir son contenu." #: src/lib/moderation/useGlobalLabelStrings.ts:30 msgid "This user has requested that their content only be shown to signed-in users." -msgstr "" - -#: src/view/com/modals/ModerationDetails.tsx:42 -#~ msgid "This user is included in the <0/> list which you have blocked." -#~ msgstr "Ce compte est inclus dans la liste <0/> que vous avez bloquée." - -#: src/view/com/modals/ModerationDetails.tsx:74 -#~ msgid "This user is included in the <0/> list which you have muted." -#~ msgstr "Ce compte est inclus dans la liste <0/> que vous avez masquée." +msgstr "Cette personne a demandé que son contenu ne soit affiché qu’aux personnes connectées." #: src/components/moderation/ModerationDetailsDialog.tsx:55 msgid "This user is included in the <0>{0} list which you have blocked." -msgstr "" +msgstr "Ce compte est inclus dans la liste <0>{0} que vous avez bloquée." #: src/components/moderation/ModerationDetailsDialog.tsx:84 msgid "This user is included in the <0>{0} list which you have muted." -msgstr "" +msgstr "Ce compte est inclus dans la liste <0>{0} que vous avez masquée." #: src/view/com/profile/ProfileFollows.tsx:87 msgid "This user isn't following anyone." -msgstr "" +msgstr "Ce compte ne suit personne." #: src/view/com/modals/SelfLabel.tsx:137 msgid "This warning is only available for posts with media attached." @@ -5035,13 +4679,9 @@ msgstr "Cet avertissement n’est disponible que pour les posts contenant des m msgid "This will delete {0} from your muted words. You can always add it back later." msgstr "Cela supprimera {0} de vos mots masqués. Vous pourrez toujours le réintégrer plus tard." -#: src/view/com/util/forms/PostDropdownBtn.tsx:282 -#~ msgid "This will hide this post from your feeds." -#~ msgstr "Cela va masquer ce post de vos fils d’actu." - #: src/view/screens/Settings/index.tsx:525 msgid "Thread preferences" -msgstr "" +msgstr "Préférences des fils de discussion" #: src/view/screens/PreferencesThreads.tsx:53 #: src/view/screens/Settings/index.tsx:535 @@ -5054,11 +4694,11 @@ msgstr "Mode arborescent" #: src/Navigation.tsx:269 msgid "Threads Preferences" -msgstr "Préférences de fils de discussion" +msgstr "Préférences des fils de discussion" #: src/components/ReportDialog/SelectLabelerView.tsx:33 msgid "To whom would you like to send this report?" -msgstr "" +msgstr "À qui souhaitez-vous envoyer ce rapport ?" #: src/components/dialogs/MutedWords.tsx:112 msgid "Toggle between muted word options." @@ -5070,11 +4710,11 @@ msgstr "Activer le menu déroulant" #: src/screens/Moderation/index.tsx:332 msgid "Toggle to enable or disable adult content" -msgstr "" +msgstr "Activer ou désactiver le contenu pour adultes" #: src/view/screens/Search/Search.tsx:427 msgid "Top" -msgstr "" +msgstr "Meilleur" #: src/view/com/modals/EditImage.tsx:272 msgid "Transformations" @@ -5094,7 +4734,7 @@ msgstr "Réessayer" #: src/view/com/modals/ChangeHandle.tsx:428 msgid "Type:" -msgstr "" +msgstr "Type :" #: src/view/screens/ProfileList.tsx:480 msgid "Un-block list" @@ -5133,7 +4773,7 @@ msgstr "Débloquer le compte" #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:280 #: src/view/com/profile/ProfileMenu.tsx:343 msgid "Unblock Account?" -msgstr "" +msgstr "Débloquer le compte ?" #: src/view/com/modals/Repost.tsx:43 #: src/view/com/modals/Repost.tsx:56 @@ -5145,7 +4785,7 @@ msgstr "Annuler le repost" #: src/view/com/auth/onboarding/RecommendedFollowsItem.tsx:141 #: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:248 msgid "Unfollow" -msgstr "" +msgstr "Se désabonner" #: src/view/com/profile/FollowButton.tsx:60 msgctxt "action" @@ -5159,11 +4799,7 @@ msgstr "Se désabonner de {0}" #: src/view/com/profile/ProfileMenu.tsx:241 #: src/view/com/profile/ProfileMenu.tsx:251 msgid "Unfollow Account" -msgstr "" - -#: src/view/com/auth/create/state.ts:262 -#~ msgid "Unfortunately, you do not meet the requirements to create an account." -#~ msgstr "Malheureusement, vous ne remplissez pas les conditions requises pour créer un compte." +msgstr "Se désabonner du compte" #: src/view/com/util/post-ctrls/PostCtrls.tsx:197 msgid "Unlike" @@ -5171,7 +4807,7 @@ msgstr "Déliker" #: src/view/screens/ProfileFeed.tsx:585 msgid "Unlike this feed" -msgstr "" +msgstr "Déliker ce fil d’actu" #: src/components/TagMenu/index.tsx:249 #: src/view/screens/ProfileList.tsx:581 @@ -5203,39 +4839,31 @@ msgstr "Désépingler" #: src/view/screens/ProfileFeed.tsx:303 msgid "Unpin from home" -msgstr "" +msgstr "Désépingler de l’accueil" #: src/view/screens/ProfileList.tsx:446 msgid "Unpin moderation list" msgstr "Supprimer la liste de modération" -#: src/view/screens/ProfileFeed.tsx:346 -#~ msgid "Unsave" -#~ msgstr "Supprimer" - #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:225 msgid "Unsubscribe" -msgstr "" +msgstr "Se désabonner" #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:190 msgid "Unsubscribe from this labeler" -msgstr "" +msgstr "Se désabonner de cet étiqueteur" #: src/lib/moderation/useReportOptions.ts:70 msgid "Unwanted Sexual Content" -msgstr "" +msgstr "Contenu sexuel non désiré" #: src/view/com/modals/UserAddRemoveLists.tsx:70 msgid "Update {displayName} in Lists" msgstr "Mise à jour de {displayName} dans les listes" -#: src/lib/hooks/useOTAUpdate.ts:15 -#~ msgid "Update Available" -#~ msgstr "Mise à jour disponible" - #: src/view/com/modals/ChangeHandle.tsx:508 msgid "Update to {handle}" -msgstr "" +msgstr "Mettre à jour pour {handle}" #: src/screens/Login/SetNewPasswordForm.tsx:186 msgid "Updating..." @@ -5250,23 +4878,23 @@ msgstr "Envoyer un fichier texte vers :" #: src/view/com/util/UserBanner.tsx:116 #: src/view/com/util/UserBanner.tsx:119 msgid "Upload from Camera" -msgstr "" +msgstr "Envoyer à partir de l’appareil photo" #: src/view/com/util/UserAvatar.tsx:345 #: src/view/com/util/UserBanner.tsx:133 msgid "Upload from Files" -msgstr "" +msgstr "Envoyer à partir de fichiers" #: src/view/com/util/UserAvatar.tsx:339 #: src/view/com/util/UserAvatar.tsx:343 #: src/view/com/util/UserBanner.tsx:127 #: src/view/com/util/UserBanner.tsx:131 msgid "Upload from Library" -msgstr "" +msgstr "Envoyer à partir de la photothèque" #: src/view/com/modals/ChangeHandle.tsx:408 msgid "Use a file on your server" -msgstr "" +msgstr "Utiliser un fichier sur votre serveur" #: src/view/screens/AppPasswords.tsx:197 msgid "Use app passwords to login to other Bluesky clients without giving full access to your account or password." @@ -5274,7 +4902,7 @@ msgstr "Utilisez les mots de passe de l’appli pour se connecter à d’autres #: src/view/com/modals/ChangeHandle.tsx:517 msgid "Use bsky.social as hosting provider" -msgstr "" +msgstr "Utiliser bsky.social comme hébergeur" #: src/view/com/modals/ChangeHandle.tsx:516 msgid "Use default provider" @@ -5292,7 +4920,7 @@ msgstr "Utiliser mon navigateur par défaut" #: src/view/com/modals/ChangeHandle.tsx:400 msgid "Use the DNS panel" -msgstr "" +msgstr "Utiliser le panneau DNS" #: src/view/com/modals/AddAppPasswords.tsx:156 msgid "Use this to sign into the other app along with your handle." @@ -5309,7 +4937,7 @@ msgstr "Compte bloqué" #: src/lib/moderation/useModerationCauseDescription.ts:48 msgid "User Blocked by \"{0}\"" -msgstr "" +msgstr "Compte bloqué par « {0} »" #: src/components/moderation/ModerationDetailsDialog.tsx:53 msgid "User Blocked by List" @@ -5317,16 +4945,12 @@ msgstr "Compte bloqué par liste" #: src/lib/moderation/useModerationCauseDescription.ts:66 msgid "User Blocking You" -msgstr "" +msgstr "Compte qui vous bloque" #: src/components/moderation/ModerationDetailsDialog.tsx:70 msgid "User Blocks You" msgstr "Compte qui vous bloque" -#: src/view/com/auth/create/Step2.tsx:79 -#~ msgid "User handle" -#~ msgstr "Pseudo" - #: src/view/com/lists/ListCard.tsx:85 #: src/view/com/modals/UserAddRemoveLists.tsx:198 msgid "User list by {0}" @@ -5374,15 +4998,15 @@ msgstr "Comptes dans « {0} »" #: src/components/LikesDialog.tsx:85 msgid "Users that have liked this content or profile" -msgstr "" +msgstr "Comptes qui ont liké ce contenu ou ce profil" #: src/view/com/modals/ChangeHandle.tsx:436 msgid "Value:" -msgstr "" +msgstr "Valeur :" #: src/view/com/modals/ChangeHandle.tsx:509 msgid "Verify {0}" -msgstr "" +msgstr "Vérifier {0}" #: src/view/screens/Settings/index.tsx:906 msgid "Verify email" @@ -5407,7 +5031,7 @@ msgstr "Vérifiez votre e-mail" #: src/view/screens/Settings/index.tsx:857 msgid "Version {0}" -msgstr "" +msgstr "Version {0}" #: src/screens/Onboarding/index.tsx:42 msgid "Video Games" @@ -5423,11 +5047,11 @@ msgstr "Afficher l’entrée de débogage" #: src/components/ReportDialog/SelectReportOptionView.tsx:132 msgid "View details" -msgstr "" +msgstr "Voir les détails" #: src/components/ReportDialog/SelectReportOptionView.tsx:127 msgid "View details for reporting a copyright violation" -msgstr "" +msgstr "Voir les détails pour signaler une violation du droit d’auteur" #: src/view/com/posts/FeedSlice.tsx:99 msgid "View full thread" @@ -5435,7 +5059,7 @@ msgstr "Voir le fil de discussion entier" #: src/components/moderation/LabelsOnMe.tsx:51 msgid "View information about these labels" -msgstr "" +msgstr "Voir les informations sur ces étiquettes" #: src/components/ProfileHoverCard/index.web.tsx:264 #: src/components/ProfileHoverCard/index.web.tsx:293 @@ -5449,11 +5073,11 @@ msgstr "Afficher l’avatar" #: src/components/LabelingServiceCard/index.tsx:140 msgid "View the labeling service provided by @{0}" -msgstr "" +msgstr "Voir le service d’étiquetage fourni par @{0}" #: src/view/screens/ProfileFeed.tsx:597 msgid "View users who like this feed" -msgstr "" +msgstr "Voir les comptes qui a liké ce fil d’actu" #: src/view/com/modals/LinkWarning.tsx:89 #: src/view/com/modals/LinkWarning.tsx:95 @@ -5469,15 +5093,11 @@ msgstr "Avertir" #: src/lib/moderation/useLabelBehaviorDescription.ts:48 msgid "Warn content" -msgstr "" +msgstr "Avertir du contenu" #: src/lib/moderation/useLabelBehaviorDescription.ts:46 msgid "Warn content and filter from feeds" -msgstr "" - -#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:134 -#~ msgid "We also think you'll like \"For You\" by Skygaze:" -#~ msgstr "Nous pensons également que vous aimerez « For You » de Skygaze :" +msgstr "Avertir du contenu et filtrer des fils d’actu" #: src/screens/Hashtag.tsx:133 msgid "We couldn't find any results for that hashtag." @@ -5505,11 +5125,11 @@ msgstr "Nous vous recommandons notre fil d’actu « Discover » :" #: src/components/dialogs/BirthDateSettings.tsx:52 msgid "We were unable to load your birth date preferences. Please try again." -msgstr "" +msgstr "Nous n’avons pas pu charger vos préférences en matière de date de naissance. Veuillez réessayer." #: src/screens/Moderation/index.tsx:385 msgid "We were unable to load your configured labelers at this time." -msgstr "" +msgstr "Nous n’avons pas pu charger vos étiqueteurs configurés pour le moment." #: src/screens/Onboarding/StepInterests/index.tsx:137 msgid "We weren't able to connect. Please try again to continue setting up your account. If it continues to fail, you can skip this flow." @@ -5519,10 +5139,6 @@ msgstr "Nous n’avons pas pu nous connecter. Veuillez réessayer pour continuer msgid "We will let you know when your account is ready." msgstr "Nous vous informerons lorsque votre compte sera prêt." -#: src/view/com/modals/AppealLabel.tsx:48 -#~ msgid "We'll look into your appeal promptly." -#~ msgstr "Nous examinerons votre appel rapidement." - #: src/screens/Onboarding/StepInterests/index.tsx:142 msgid "We'll use this to help customize your experience." msgstr "Nous utiliserons ces informations pour personnaliser votre expérience." @@ -5550,7 +5166,7 @@ msgstr "Nous sommes désolés ! La page que vous recherchez est introuvable." #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:327 msgid "We're sorry! You can only subscribe to ten labelers, and you've reached your limit of ten." -msgstr "" +msgstr "Nous sommes désolés ! Vous ne pouvez vous abonner qu’à dix étiqueteurs, et vous avez atteint votre limite de dix." #: src/view/com/auth/onboarding/WelcomeMobile.tsx:48 msgid "Welcome to <0>Bluesky" @@ -5560,10 +5176,6 @@ msgstr "Bienvenue sur <0>Bluesky" msgid "What are your interests?" msgstr "Quels sont vos centres d’intérêt ?" -#: src/view/com/modals/report/Modal.tsx:169 -#~ msgid "What is the issue with this {collectionName}?" -#~ msgstr "Quel est le problème avec cette {collectionName} ?" - #: src/view/com/auth/SplashScreen.tsx:40 #: src/view/com/auth/SplashScreen.web.tsx:81 #: src/view/com/composer/Composer.tsx:296 @@ -5585,23 +5197,23 @@ msgstr "Qui peut répondre ?" #: src/components/ReportDialog/SelectReportOptionView.tsx:43 msgid "Why should this content be reviewed?" -msgstr "" +msgstr "Pourquoi ce contenu doit-il être examiné ?" #: src/components/ReportDialog/SelectReportOptionView.tsx:56 msgid "Why should this feed be reviewed?" -msgstr "" +msgstr "Pourquoi ce fil d’actu doit-il être examiné ?" #: src/components/ReportDialog/SelectReportOptionView.tsx:53 msgid "Why should this list be reviewed?" -msgstr "" +msgstr "Pourquoi cette liste devrait-elle être examinée ?" #: src/components/ReportDialog/SelectReportOptionView.tsx:50 msgid "Why should this post be reviewed?" -msgstr "" +msgstr "Pourquoi ce post devrait-il être examiné ?" #: src/components/ReportDialog/SelectReportOptionView.tsx:47 msgid "Why should this user be reviewed?" -msgstr "" +msgstr "Pourquoi ce compte doit-il être examiné ?" #: src/view/com/modals/crop-image/CropImage.web.tsx:103 msgid "Wide" @@ -5636,7 +5248,7 @@ msgstr "Vous êtes dans la file d’attente." #: src/view/com/profile/ProfileFollows.tsx:86 msgid "You are not following anyone." -msgstr "" +msgstr "Vous ne suivez personne." #: src/view/com/posts/FollowingEmptyState.tsx:67 #: src/view/com/posts/FollowingEndOfFeed.tsx:68 @@ -5654,7 +5266,7 @@ msgstr "Vous pouvez maintenant vous connecter avec votre nouveau mot de passe." #: src/view/com/profile/ProfileFollowers.tsx:86 msgid "You do not have any followers." -msgstr "" +msgstr "Vous n’avez pas d’abonné·e·s." #: src/view/com/modals/InviteCodes.tsx:67 msgid "You don't have any invite codes yet! We'll send you some when you've been on Bluesky for a little longer." @@ -5691,24 +5303,20 @@ msgstr "Vous avez introduit un code non valide. Il devrait ressembler à XXXXX-X #: src/lib/moderation/useModerationCauseDescription.ts:109 msgid "You have hidden this post" -msgstr "" +msgstr "Vous avez caché ce post" #: src/components/moderation/ModerationDetailsDialog.tsx:101 msgid "You have hidden this post." -msgstr "" +msgstr "Vous avez caché ce post." #: src/components/moderation/ModerationDetailsDialog.tsx:94 #: src/lib/moderation/useModerationCauseDescription.ts:92 msgid "You have muted this account." -msgstr "" +msgstr "Vous avez masqué ce compte." #: src/lib/moderation/useModerationCauseDescription.ts:86 msgid "You have muted this user" -msgstr "" - -#: src/view/com/modals/ModerationDetails.tsx:87 -#~ msgid "You have muted this user." -#~ msgstr "Vous avez masqué ce compte." +msgstr "Vous avez masqué ce compte" #: src/view/com/feeds/ProfileFeedgens.tsx:144 msgid "You have no feeds." @@ -5721,11 +5329,7 @@ msgstr "Vous n’avez aucune liste." #: src/view/screens/ModerationBlockedAccounts.tsx:138 msgid "You have not blocked any accounts yet. To block an account, go to their profile and select \"Block account\" from the menu on their account." -msgstr "" - -#: src/view/screens/ModerationBlockedAccounts.tsx:132 -#~ msgid "You have not blocked any accounts yet. To block an account, go to their profile and selected \"Block account\" from the menu on their account." -#~ msgstr "Vous n’avez pas encore bloqué de comptes. Pour bloquer un compte, accédez à son profil et sélectionnez « Bloquer le compte » dans le menu de son compte." +msgstr "Vous n’avez pas encore bloqué de comptes. Pour bloquer un compte, allez sur son profil et sélectionnez « Bloquer le compte » dans le menu de son compte." #: src/view/screens/AppPasswords.tsx:89 msgid "You have not created any app passwords yet. You can create one by pressing the button below." @@ -5733,11 +5337,7 @@ msgstr "Vous n’avez encore créé aucun mot de passe pour l’appli. Vous pouv #: src/view/screens/ModerationMutedAccounts.tsx:136 msgid "You have not muted any accounts yet. To mute an account, go to their profile and select \"Mute account\" from the menu on their account." -msgstr "" - -#: src/view/screens/ModerationMutedAccounts.tsx:131 -#~ msgid "You have not muted any accounts yet. To mute an account, go to their profile and selected \"Mute account\" from the menu on their account." -#~ msgstr "Vous n’avez encore masqué aucun compte. Pour désactiver un compte, allez sur son profil et sélectionnez « Masquer le compte » dans le menu de son compte." +msgstr "Vous n’avez encore masqué aucun compte. Pour masquer un compte, allez sur son profil et sélectionnez « Masquer le compte » dans le menu de son compte." #: src/components/dialogs/MutedWords.tsx:249 msgid "You haven't muted any words or tags yet" @@ -5745,15 +5345,11 @@ msgstr "Vous n’avez pas encore masqué de mot ou de mot-clé" #: src/components/moderation/LabelsOnMeDialog.tsx:68 msgid "You may appeal these labels if you feel they were placed in error." -msgstr "" +msgstr "Vous pouvez faire appel de ces étiquettes si vous estimez qu’elles ont été apposées par erreur." #: src/screens/Signup/StepInfo/Policies.tsx:79 msgid "You must be 13 years of age or older to sign up." -msgstr "" - -#: src/view/com/modals/ContentFilteringSettings.tsx:175 -#~ msgid "You must be 18 or older to enable adult content." -#~ msgstr "Vous devez avoir 18 ans ou plus pour activer le contenu pour adultes." +msgstr "Vous devez avoir 13 ans ou plus pour vous inscrire." #: src/screens/Onboarding/StepModeration/AdultContentEnabledPref.tsx:110 msgid "You must be 18 years or older to enable adult content" @@ -5761,7 +5357,7 @@ msgstr "Vous devez avoir 18 ans ou plus pour activer le contenu pour adultes." #: src/components/ReportDialog/SubmitView.tsx:203 msgid "You must select at least one labeler for a report" -msgstr "" +msgstr "Vous devez sélectionner au moins un étiqueteur pour un rapport" #: src/view/com/util/forms/PostDropdownBtn.tsx:150 msgid "You will no longer receive notifications for this thread" @@ -5792,7 +5388,7 @@ msgstr "Vous êtes prêt à partir !" #: src/components/moderation/ModerationDetailsDialog.tsx:98 #: src/lib/moderation/useModerationCauseDescription.ts:101 msgid "You've chosen to hide a word or tag within this post." -msgstr "" +msgstr "Vous avez choisi de masquer un mot ou un mot-clé dans ce post." #: src/view/com/posts/FollowingEndOfFeed.tsx:48 msgid "You've reached the end of your feed! Find some more accounts to follow." From f49d73dd00462528bd2ff73fd51746756c12fe9d Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 22 Apr 2024 22:18:39 +0100 Subject: [PATCH 33/47] [GIFs] Reset scroll on query change (#3642) * scroll list to top on query change * dismiss keyboard on swipe list * don't need an effect --- src/components/Dialog/index.web.tsx | 12 ++++++------ src/components/dialogs/GifSelect.tsx | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx index a086955db6..4cb4e7570c 100644 --- a/src/components/Dialog/index.web.tsx +++ b/src/components/Dialog/index.web.tsx @@ -197,11 +197,10 @@ export function Inner({ export const ScrollableInner = Inner -export function InnerFlatList({ - label, - style, - ...props -}: FlatListProps & {label: string}) { +export const InnerFlatList = React.forwardRef< + FlatList, + FlatListProps & {label: string} +>(function InnerFlatList({label, style, ...props}, ref) { const {gtMobile} = useBreakpoints() return ( ) -} +}) export function Handle() { return null diff --git a/src/components/dialogs/GifSelect.tsx b/src/components/dialogs/GifSelect.tsx index ad4fbeadea..a8fe016d10 100644 --- a/src/components/dialogs/GifSelect.tsx +++ b/src/components/dialogs/GifSelect.tsx @@ -1,6 +1,7 @@ import React, {useCallback, useMemo, useRef, useState} from 'react' -import {TextInput, View} from 'react-native' +import {Keyboard, TextInput, View} from 'react-native' import {Image} from 'expo-image' +import {BottomSheetFlatListMethods} from '@discord/bottom-sheet' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -82,7 +83,8 @@ function GifList({ const {_} = useLingui() const t = useTheme() const {gtMobile} = useBreakpoints() - const ref = useRef(null) + const textInputRef = useRef(null) + const listRef = useRef(null) const [undeferredSearch, setSearch] = useState('') const search = useThrottledValue(undeferredSearch, 500) @@ -133,7 +135,7 @@ function GifList({ const onGoBack = useCallback(() => { if (isSearching) { // clear the input and reset the state - ref.current?.clear() + textInputRef.current?.clear() setSearch('') } else { control.close() @@ -180,10 +182,13 @@ function GifList({ { + setSearch(text) + listRef.current?.scrollToOffset({offset: 0, animated: false}) + }} returnKeyType="search" clearButtonMode="while-editing" - inputRef={ref} + inputRef={textInputRef} maxLength={50} onKeyPress={({nativeEvent}) => { if (nativeEvent.key === 'Escape') { @@ -200,6 +205,7 @@ function GifList({ <> {gtMobile && } item.id} // @ts-expect-error web only style={isWeb && {minHeight: '100vh'}} + onScrollBeginDrag={() => Keyboard.dismiss()} ListFooterComponent={ hasData ? ( Date: Mon, 22 Apr 2024 14:46:05 -0700 Subject: [PATCH 34/47] properly close the switch account dialog (#3558) * properly close the switch account dialog * use it for switch account as well * ensure dialog is closed on unmount Revert "properly check if the ref is null" This reverts commit 8f563808a5d39389b0bc47a31e73cd147d1e7e8b. properly check if the ref is null ensure dialog is closed on unmount * Revert "ensure dialog is closed on unmount" This reverts commit a48548fd8ed53ae3eb08a0e05bb89f641c112b95. --- src/components/dialogs/SwitchAccount.tsx | 17 +++++++++-------- src/lib/hooks/useAccountSwitcher.ts | 18 ++++-------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/components/dialogs/SwitchAccount.tsx b/src/components/dialogs/SwitchAccount.tsx index 645113d4af..55628a790d 100644 --- a/src/components/dialogs/SwitchAccount.tsx +++ b/src/components/dialogs/SwitchAccount.tsx @@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react' import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' import {type SessionAccount, useSession} from '#/state/session' import {useLoggedOutViewControls} from '#/state/shell/logged-out' -import {useCloseAllActiveElements} from '#/state/util' import {atoms as a} from '#/alf' import * as Dialog from '#/components/Dialog' import {AccountList} from '../AccountList' @@ -21,23 +20,25 @@ export function SwitchAccountDialog({ const {currentAccount} = useSession() const {onPressSwitchAccount} = useAccountSwitcher() const {setShowLoggedOut} = useLoggedOutViewControls() - const closeAllActiveElements = useCloseAllActiveElements() const onSelectAccount = useCallback( (account: SessionAccount) => { - if (account.did === currentAccount?.did) { - control.close() + if (account.did !== currentAccount?.did) { + control.close(() => { + onPressSwitchAccount(account, 'SwitchAccount') + }) } else { - onPressSwitchAccount(account, 'SwitchAccount') + control.close() } }, [currentAccount, control, onPressSwitchAccount], ) const onPressAddAccount = useCallback(() => { - setShowLoggedOut(true) - closeAllActiveElements() - }, [setShowLoggedOut, closeAllActiveElements]) + control.close(() => { + setShowLoggedOut(true) + }) + }, [setShowLoggedOut, control]) return ( diff --git a/src/lib/hooks/useAccountSwitcher.ts b/src/lib/hooks/useAccountSwitcher.ts index eb1685a0ae..6a1cea2345 100644 --- a/src/lib/hooks/useAccountSwitcher.ts +++ b/src/lib/hooks/useAccountSwitcher.ts @@ -1,17 +1,15 @@ import {useCallback} from 'react' -import {isWeb} from '#/platform/detection' import {useAnalytics} from '#/lib/analytics/analytics' -import {useSessionApi, SessionAccount} from '#/state/session' -import * as Toast from '#/view/com/util/Toast' -import {useCloseAllActiveElements} from '#/state/util' +import {isWeb} from '#/platform/detection' +import {SessionAccount, useSessionApi} from '#/state/session' import {useLoggedOutViewControls} from '#/state/shell/logged-out' +import * as Toast from '#/view/com/util/Toast' import {LogEvents} from '../statsig/statsig' export function useAccountSwitcher() { const {track} = useAnalytics() const {selectAccount, clearCurrentAccount} = useSessionApi() - const closeAllActiveElements = useCloseAllActiveElements() const {requestSwitchToAccount} = useLoggedOutViewControls() const onPressSwitchAccount = useCallback( @@ -23,7 +21,6 @@ export function useAccountSwitcher() { try { if (account.accessJwt) { - closeAllActiveElements() if (isWeb) { // We're switching accounts, which remounts the entire app. // On mobile, this gets us Home, but on the web we also need reset the URL. @@ -37,7 +34,6 @@ export function useAccountSwitcher() { Toast.show(`Signed in as @${account.handle}`) }, 100) } else { - closeAllActiveElements() requestSwitchToAccount({requestedAccount: account.did}) Toast.show( `Please sign in as @${account.handle}`, @@ -49,13 +45,7 @@ export function useAccountSwitcher() { clearCurrentAccount() // back user out to login } }, - [ - track, - clearCurrentAccount, - selectAccount, - closeAllActiveElements, - requestSwitchToAccount, - ], + [track, clearCurrentAccount, selectAccount, requestSwitchToAccount], ) return {onPressSwitchAccount} From 76449fb6ef9b3eb327b6d059614d0da31c9d8e1f Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 22 Apr 2024 23:39:32 +0100 Subject: [PATCH 35/47] [GIFs] Replace GIPHY with Tenor (#3651) * replace GIPHY with Tenor * remove "directly" wording * replace GIPHY wording * remove log --- src/components/dialogs/GifSelect.tsx | 65 ++-- src/lib/constants.ts | 14 +- src/state/queries/giphy.ts | 280 ------------------ src/state/queries/tenor.ts | 177 +++++++++++ src/view/com/composer/Composer.tsx | 17 +- src/view/com/composer/photos/SelectGifBtn.tsx | 2 +- 6 files changed, 220 insertions(+), 335 deletions(-) delete mode 100644 src/state/queries/giphy.ts create mode 100644 src/state/queries/tenor.ts diff --git a/src/components/dialogs/GifSelect.tsx b/src/components/dialogs/GifSelect.tsx index a8fe016d10..41612aa5d9 100644 --- a/src/components/dialogs/GifSelect.tsx +++ b/src/components/dialogs/GifSelect.tsx @@ -5,7 +5,6 @@ import {BottomSheetFlatListMethods} from '@discord/bottom-sheet' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {GIPHY_PRIVACY_POLICY} from '#/lib/constants' import {logEvent} from '#/lib/statsig/statsig' import {cleanError} from '#/lib/strings/errors' import {isWeb} from '#/platform/detection' @@ -13,7 +12,11 @@ import { useExternalEmbedsPrefs, useSetExternalEmbedPref, } from '#/state/preferences' -import {Gif, useGifphySearch, useGiphyTrending} from '#/state/queries/giphy' +import { + Gif, + useFeaturedGifsQuery, + useGifSearchQuery, +} from '#/state/queries/tenor' import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' import {atoms as a, useBreakpoints, useTheme} from '#/alf' @@ -22,7 +25,6 @@ import * as TextField from '#/components/forms/TextField' import {useThrottledValue} from '#/components/hooks/useThrottledValue' import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow' import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2' -import {InlineLinkText} from '#/components/Link' import {Button, ButtonIcon, ButtonText} from '../Button' import {ListFooter, ListMaybePlaceholder} from '../Lists' import {Text} from '../Typography' @@ -46,14 +48,14 @@ export function GifSelectDialog({ let content = null let snapPoints - switch (externalEmbedsPrefs?.giphy) { + switch (externalEmbedsPrefs?.tenor) { case 'show': content = snapPoints = ['100%'] break case 'hide': default: - content = + content = break } @@ -90,8 +92,8 @@ function GifList({ const isSearching = search.length > 0 - const trendingQuery = useGiphyTrending() - const searchQuery = useGifphySearch(search) + const trendingQuery = useFeaturedGifsQuery() + const searchQuery = useGifSearchQuery(search) const { data, @@ -105,17 +107,7 @@ function GifList({ } = isSearching ? searchQuery : trendingQuery const flattenedData = useMemo(() => { - const uniquenessSet = new Set() - - function filter(gif: Gif) { - if (!gif) return false - if (uniquenessSet.has(gif.id)) { - return false - } - uniquenessSet.add(gif.id) - return true - } - return data?.pages.flatMap(page => page.data.filter(filter)) || [] + return data?.pages.flatMap(page => page.results) || [] }, [data]) const renderItem = useCallback( @@ -181,7 +173,7 @@ function GifList({ { setSearch(text) listRef.current?.scrollToOffset({offset: 0, animated: false}) @@ -223,12 +215,12 @@ function GifList({ emptyType="results" sideBorders={false} errorTitle={_(msg`Failed to load GIFs`)} - errorMessage={_(msg`There was an issue connecting to GIPHY.`)} + errorMessage={_(msg`There was an issue connecting to Tenor.`)} emptyMessage={ isSearching ? _(msg`No search results found for "${search}".`) : _( - msg`No trending GIFs found. There may be an issue with GIPHY.`, + msg`No featured GIFs found. There may be an issue with Tenor.`, ) } /> @@ -287,7 +279,9 @@ function GifPreview({ {aspectRatio: 1, opacity: pressed ? 0.8 : 1}, t.atoms.bg_contrast_25, ]} - source={{uri: gif.images.preview_gif.url}} + source={{ + uri: gif.media_formats.tinygif.url, + }} contentFit="cover" accessibilityLabel={gif.title} accessibilityHint="" @@ -299,61 +293,56 @@ function GifPreview({ ) } -function GiphyConsentPrompt({control}: {control: Dialog.DialogControlProps}) { +function TenorConsentPrompt({control}: {control: Dialog.DialogControlProps}) { const {_} = useLingui() const t = useTheme() const {gtMobile} = useBreakpoints() const setExternalEmbedPref = useSetExternalEmbedPref() const onShowPress = useCallback(() => { - setExternalEmbedPref('giphy', 'show') + setExternalEmbedPref('tenor', 'show') }, [setExternalEmbedPref]) const onHidePress = useCallback(() => { - setExternalEmbedPref('giphy', 'hide') + setExternalEmbedPref('tenor', 'hide') control.close() }, [control, setExternalEmbedPref]) const gtMobileWeb = gtMobile && isWeb return ( - + - Permission to use GIPHY + Permission to use Tenor - Bluesky uses GIPHY to provide the GIF selector feature. + Bluesky uses Tenor to provide the GIF selector feature. - GIPHY may collect information about you and your device. You can - find out more in their{' '} - control.close()}> - privacy policy - - . + Tenor is a third-party service that provides GIFs for use in + Bluesky. By enabling Tenor, requests will be made to Tenor's + servers to retrieve the GIFs. - - - - ) -} - function DialogError({details}: {details?: string}) { const {_} = useLingui() const control = Dialog.useDialogContext() diff --git a/src/view/screens/PreferencesExternalEmbeds.tsx b/src/view/screens/PreferencesExternalEmbeds.tsx index 1e8cedf7e2..5eec7e5077 100644 --- a/src/view/screens/PreferencesExternalEmbeds.tsx +++ b/src/view/screens/PreferencesExternalEmbeds.tsx @@ -1,25 +1,26 @@ import React from 'react' import {StyleSheet, View} from 'react-native' +import {Trans} from '@lingui/macro' import {useFocusEffect} from '@react-navigation/native' -import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' -import {s} from 'lib/styles' -import {Text} from '../com/util/text/Text' -import {usePalette} from 'lib/hooks/usePalette' -import {useAnalytics} from 'lib/analytics/analytics' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' + import { EmbedPlayerSource, externalEmbedLabels, } from '#/lib/strings/embed-player' import {useSetMinimalShellMode} from '#/state/shell' -import {Trans} from '@lingui/macro' -import {ScrollView} from '../com/util/Views' +import {useAnalytics} from 'lib/analytics/analytics' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' +import {s} from 'lib/styles' import { useExternalEmbedsPrefs, useSetExternalEmbedPref, } from 'state/preferences' import {ToggleButton} from 'view/com/util/forms/ToggleButton' import {SimpleViewHeader} from '../com/util/SimpleViewHeader' +import {Text} from '../com/util/text/Text' +import {ScrollView} from '../com/util/Views' type Props = NativeStackScreenProps< CommonNavigatorParams, @@ -74,13 +75,16 @@ export function PreferencesExternalEmbeds({}: Props) { Enable media players for - {Object.entries(externalEmbedLabels).map(([key, label]) => ( - - ))} + {Object.entries(externalEmbedLabels) + // TODO: Remove special case when we disable the old integration. + .filter(([key]) => key !== 'tenor') + .map(([key, label]) => ( + + ))} ) From 49b5d420e6ae7c3c9cfd56f47248b686f5c0128a Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 23 Apr 2024 00:37:46 +0100 Subject: [PATCH 37/47] rm country param (#3653) --- src/state/queries/tenor.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/state/queries/tenor.ts b/src/state/queries/tenor.ts index 66cfcec6af..80c57479e6 100644 --- a/src/state/queries/tenor.ts +++ b/src/state/queries/tenor.ts @@ -65,10 +65,6 @@ function createTenorApi( if (locale) { params.set('locale', locale.languageTag.replace('-', '_')) - - if (locale.regionCode) { - params.set('country', locale.regionCode) - } } for (const [key, value] of Object.entries(input)) { From fe9b3f0432d36fd60e5da4ed16be87cd0470e64b Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 23 Apr 2024 00:54:59 +0100 Subject: [PATCH 38/47] Ungate profile scroll fix (#3655) --- src/lib/statsig/gates.ts | 1 - src/view/screens/Profile.tsx | 24 ++++-------------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 843c14f04d..c41083afba 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -5,7 +5,6 @@ export type Gate = | 'disable_poll_on_discover_v2' | 'hide_vertical_scroll_indicators' | 'new_gif_player' - | 'new_profile_scroll_component' | 'show_follow_back_label_v2' | 'start_session_with_following_v2' | 'use_new_suggestions_endpoint' diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index c7f5a6627a..9cf2352c8a 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -25,7 +25,6 @@ import {useAnalytics} from 'lib/analytics/analytics' import {useSetTitle} from 'lib/hooks/useSetTitle' import {ComposeIcon2} from 'lib/icons' import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' -import {useGate} from 'lib/statsig/statsig' import {combinedDisplayName} from 'lib/strings/display-names' import {isInvalidHandle} from 'lib/strings/handles' import {colors, s} from 'lib/styles' @@ -143,7 +142,6 @@ function ProfileScreenLoaded({ const setMinimalShellMode = useSetMinimalShellMode() const {openComposer} = useComposerControls() const {screen, track} = useAnalytics() - const gate = useGate() const { data: labelerInfo, error: labelerError, @@ -317,21 +315,8 @@ function ProfileScreenLoaded({ // = const renderHeader = React.useCallback(() => { - if (gate('new_profile_scroll_component')) { - return ( - - - - ) - } else { - return ( + return ( + - ) - } + + ) }, [ - gate, scrollViewTag, profile, labelerInfo, From cbb817b5b707042afefbf8ca46a7104d62349492 Mon Sep 17 00:00:00 2001 From: Hailey Date: Mon, 22 Apr 2024 18:54:15 -0700 Subject: [PATCH 39/47] GIF Viewer (#3605) * ios player autoplay after recycle remove all items from AVPlayer queue recurururururursion use managers in the view add prefetch make sure player items stay in order add controller and item managers start of the view create module, ios * android player smoother basic caching prep cache somewhat works backup other files android impl blegh lets go touchup add prefetch to js use caching * bogus testing commit * add dims to type * save * add the dimensions to the embed info * add a new case * add a new case * limit this case to giphy * use gate * Revert "bogus testing commit" This reverts commit b3c8751b71f7108de9aa843b22ded4e0249fa854. * add web player base * flip mp4/webp * basic mp4 player for web * move some stuff into `ExternalLinkEmbed` instead * use a class component for web * remove extra component * add `onPlayerStateChange` event type on web * layer properly * fix tests * add new test * about ready. native portions done, a few touch ups on web needed show placeholder on ios fix type rm log display thumbnail until video is ready to play add oncanplay, playsinline remove unused method add `isLoaded` change event release player when finished apply gc to the view cleanup logs android gc rm log automatic gc for assets make `nativeRef` private remove unnecessary `await` cleanup rev log only play on prepare whenever needed rm unused perfperfperf rm var comment + android width native height calculations rm pressable add event dispatcher on android add event dispatcher on ios * ready to test ios fix autoplay ios clean oops * autoplay on web * normalize across all platforms add check for `ALT:` separate gif embed logic to another file handle permissions requests flatten web styles normalize styles normalize styles prefetch functions pause animatable on foreground android nits one more oops idk where that code went lint rethink the usage wrap up android clear bg update gradle more android rename dir update android namespace web ios add deps use webp rm unused update types use webp on mobile * rm gate from types * remove unused event param * only start placeholder op if doesn't exist in disk cache * fix gifs animating on app resume android * remove comment * add `isLoaded` for ios * add `isLoaded` to Android * onload for web * add visual loading state * rm a log * implement isloaded for android * dialogs * replace `webpSource` with `source` * update prop name * Move to Tenor for GIFs (#3654) * update some urls * right order for dimensions * add GIF coder for ios * remove giphy check * rewrite tenor urls * remove all the unnecessary stuff for consent * rm print * rm log * check if id and filename are strings * full size playback controls * pass tests * add accessibility to gifs * use `onPlay` and `onPause` * rm unused logic for description * add accessibility label to the controls * add gif into to external embed in composer * make it optional * gif dimensions * make the jsx look nicer --------- Co-authored-by: Dan Abramov Co-authored-by: Samuel Newman --- __tests__/lib/string.test.ts | 230 +++++++++--------- .../android/build.gradle | 98 ++++++++ .../android/src/main/AndroidManifest.xml | 2 + .../AppCompatImageViewExtended.kt | 37 +++ .../ExpoBlueskyGifViewModule.kt | 54 ++++ .../expo/modules/blueskygifview/GifView.kt | 180 ++++++++++++++ .../expo-module.config.json | 9 + modules/expo-bluesky-gif-view/index.ts | 1 + .../ios/ExpoBlueskyGifView.podspec | 23 ++ .../ios/ExpoBlueskyGifViewModule.swift | 47 ++++ .../expo-bluesky-gif-view/ios/GifView.swift | 185 ++++++++++++++ modules/expo-bluesky-gif-view/ios/Util.swift | 17 ++ modules/expo-bluesky-gif-view/src/GifView.tsx | 39 +++ .../src/GifView.types.ts | 15 ++ .../expo-bluesky-gif-view/src/GifView.web.tsx | 82 +++++++ src/lib/statsig/gates.ts | 1 - src/lib/strings/embed-player.ts | 46 ++-- src/view/com/composer/Composer.tsx | 10 +- src/view/com/composer/ExternalEmbed.tsx | 70 ++++-- .../util/post-embeds/ExternalLinkEmbed.tsx | 150 +++++++----- src/view/com/util/post-embeds/GifEmbed.tsx | 140 +++++++++++ src/view/com/util/post-embeds/index.tsx | 52 +--- 22 files changed, 1223 insertions(+), 265 deletions(-) create mode 100644 modules/expo-bluesky-gif-view/android/build.gradle create mode 100644 modules/expo-bluesky-gif-view/android/src/main/AndroidManifest.xml create mode 100644 modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/AppCompatImageViewExtended.kt create mode 100644 modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/ExpoBlueskyGifViewModule.kt create mode 100644 modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/GifView.kt create mode 100644 modules/expo-bluesky-gif-view/expo-module.config.json create mode 100644 modules/expo-bluesky-gif-view/index.ts create mode 100644 modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifView.podspec create mode 100644 modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifViewModule.swift create mode 100644 modules/expo-bluesky-gif-view/ios/GifView.swift create mode 100644 modules/expo-bluesky-gif-view/ios/Util.swift create mode 100644 modules/expo-bluesky-gif-view/src/GifView.tsx create mode 100644 modules/expo-bluesky-gif-view/src/GifView.types.ts create mode 100644 modules/expo-bluesky-gif-view/src/GifView.web.tsx create mode 100644 src/view/com/util/post-embeds/GifEmbed.tsx diff --git a/__tests__/lib/string.test.ts b/__tests__/lib/string.test.ts index eeb5ae1572..03d6852494 100644 --- a/__tests__/lib/string.test.ts +++ b/__tests__/lib/string.test.ts @@ -459,6 +459,12 @@ describe('parseEmbedPlayerFromUrl', () => { 'https://tenor.com/view', 'https://tenor.com/view/gifId.gif', 'https://tenor.com/intl/view/gifId.gif', + + 'https://media.tenor.com/someID_AAAAC/someName.gif?hh=100&ww=100', + 'https://media.tenor.com/someID_AAAAC/someName.gif', + 'https://media.tenor.com/someID/someName.gif', + 'https://media.tenor.com/someID', + 'https://media.tenor.com', ] const outputs = [ @@ -628,137 +634,129 @@ describe('parseEmbedPlayerFromUrl', () => { }, undefined, undefined, - { type: 'giphy_gif', source: 'giphy', isGif: true, hideDetails: true, metaUri: 'https://giphy.com/gifs/39248209509382934029', - playerUri: 'https://i.giphy.com/media/39248209509382934029/200.mp4', + playerUri: 'https://i.giphy.com/media/39248209509382934029/200.webp', + }, + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/gifId', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', + }, + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/gifId', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', + }, + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/gifId', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', + }, + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/gifId', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', + }, + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/gifId', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', + }, + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/gifId', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', + }, + undefined, + undefined, + undefined, + + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/gifId', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', + }, + + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/gifId', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', + }, + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/gifId', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', + }, + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/gifId', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', + }, + { + type: 'giphy_gif', + source: 'giphy', + isGif: true, + hideDetails: true, + metaUri: 'https://giphy.com/gifs/gifId', + playerUri: 'https://i.giphy.com/media/gifId/200.webp', + }, + + undefined, + undefined, + undefined, + undefined, + undefined, + + { + type: 'tenor_gif', + source: 'tenor', + isGif: true, + hideDetails: true, + playerUri: 'https://t.gifs.bsky.app/someID_AAAAM/someName.gif', dimensions: { width: 100, height: 100, }, }, - - { - type: 'giphy_gif', - source: 'giphy', - isGif: true, - hideDetails: true, - metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/200.webp', - }, - { - type: 'giphy_gif', - source: 'giphy', - isGif: true, - hideDetails: true, - metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/200.webp', - }, - { - type: 'giphy_gif', - source: 'giphy', - isGif: true, - hideDetails: true, - metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/200.webp', - }, - { - type: 'giphy_gif', - source: 'giphy', - isGif: true, - hideDetails: true, - metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/200.webp', - }, - { - type: 'giphy_gif', - source: 'giphy', - isGif: true, - hideDetails: true, - metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/200.webp', - }, - { - type: 'giphy_gif', - source: 'giphy', - isGif: true, - hideDetails: true, - metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/200.webp', - }, undefined, undefined, undefined, - - { - type: 'giphy_gif', - source: 'giphy', - isGif: true, - hideDetails: true, - metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/200.webp', - }, - - { - type: 'giphy_gif', - source: 'giphy', - isGif: true, - hideDetails: true, - metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/200.webp', - }, - { - type: 'giphy_gif', - source: 'giphy', - isGif: true, - hideDetails: true, - metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/200.webp', - }, - { - type: 'giphy_gif', - source: 'giphy', - isGif: true, - hideDetails: true, - metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/200.webp', - }, - { - type: 'giphy_gif', - source: 'giphy', - isGif: true, - hideDetails: true, - metaUri: 'https://giphy.com/gifs/gifId', - playerUri: 'https://i.giphy.com/media/gifId/200.webp', - }, - - { - type: 'tenor_gif', - source: 'tenor', - isGif: true, - hideDetails: true, - playerUri: 'https://tenor.com/view/gifId.gif', - }, undefined, - undefined, - { - type: 'tenor_gif', - source: 'tenor', - isGif: true, - hideDetails: true, - playerUri: 'https://tenor.com/view/gifId.gif', - }, - { - type: 'tenor_gif', - source: 'tenor', - isGif: true, - hideDetails: true, - playerUri: 'https://tenor.com/intl/view/gifId.gif', - }, ] it('correctly grabs the correct id from uri', () => { diff --git a/modules/expo-bluesky-gif-view/android/build.gradle b/modules/expo-bluesky-gif-view/android/build.gradle new file mode 100644 index 0000000000..c209a35aec --- /dev/null +++ b/modules/expo-bluesky-gif-view/android/build.gradle @@ -0,0 +1,98 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'maven-publish' + +group = 'expo.modules.blueskygifview' +version = '0.5.0' + +buildscript { + def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") + if (expoModulesCorePlugin.exists()) { + apply from: expoModulesCorePlugin + applyKotlinExpoModulesCorePlugin() + } + + // Simple helper that allows the root project to override versions declared by this library. + ext.safeExtGet = { prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + + // Ensures backward compatibility + ext.getKotlinVersion = { + if (ext.has("kotlinVersion")) { + ext.kotlinVersion() + } else { + ext.safeExtGet("kotlinVersion", "1.8.10") + } + } + + repositories { + mavenCentral() + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}") + } +} + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + from components.release + } + } + repositories { + maven { + url = mavenLocal().url + } + } + } +} + +android { + compileSdkVersion safeExtGet("compileSdkVersion", 33) + + def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION + if (agpVersion.tokenize('.')[0].toInteger() < 8) { + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.majorVersion + } + } + + namespace "expo.modules.blueskygifview" + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 21) + targetSdkVersion safeExtGet("targetSdkVersion", 34) + versionCode 1 + versionName "0.5.0" + } + lintOptions { + abortOnError false + } + publishing { + singleVariant("release") { + withSourcesJar() + } + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.6.1' + def GLIDE_VERSION = "4.13.2" + + implementation project(':expo-modules-core') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}" + + // Keep glide version up to date with expo-image so that we don't have duplicate deps + implementation 'com.github.bumptech.glide:glide:4.13.2' +} diff --git a/modules/expo-bluesky-gif-view/android/src/main/AndroidManifest.xml b/modules/expo-bluesky-gif-view/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..bdae66c8f5 --- /dev/null +++ b/modules/expo-bluesky-gif-view/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/AppCompatImageViewExtended.kt b/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/AppCompatImageViewExtended.kt new file mode 100644 index 0000000000..5d20848453 --- /dev/null +++ b/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/AppCompatImageViewExtended.kt @@ -0,0 +1,37 @@ +package expo.modules.blueskygifview + +import android.content.Context +import android.graphics.Canvas +import android.graphics.drawable.Animatable +import androidx.appcompat.widget.AppCompatImageView + +class AppCompatImageViewExtended(context: Context, private val parent: GifView): AppCompatImageView(context) { + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + if (this.drawable is Animatable) { + if (!parent.isLoaded) { + parent.isLoaded = true + parent.firePlayerStateChange() + } + + if (!parent.isPlaying) { + this.pause() + } + } + } + + fun pause() { + val drawable = this.drawable + if (drawable is Animatable) { + drawable.stop() + } + } + + fun play() { + val drawable = this.drawable + if (drawable is Animatable) { + drawable.start() + } + } +} \ No newline at end of file diff --git a/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/ExpoBlueskyGifViewModule.kt b/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/ExpoBlueskyGifViewModule.kt new file mode 100644 index 0000000000..625e1d45f9 --- /dev/null +++ b/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/ExpoBlueskyGifViewModule.kt @@ -0,0 +1,54 @@ +package expo.modules.blueskygifview + +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition + +class ExpoBlueskyGifViewModule : Module() { + override fun definition() = ModuleDefinition { + Name("ExpoBlueskyGifView") + + AsyncFunction("prefetchAsync") { sources: List -> + val activity = appContext.currentActivity ?: return@AsyncFunction + val glide = Glide.with(activity) + + sources.forEach { source -> + glide + .download(source) + .diskCacheStrategy(DiskCacheStrategy.DATA) + .submit() + } + } + + View(GifView::class) { + Events( + "onPlayerStateChange" + ) + + Prop("source") { view: GifView, source: String -> + view.source = source + } + + Prop("placeholderSource") { view: GifView, source: String -> + view.placeholderSource = source + } + + Prop("autoplay") { view: GifView, autoplay: Boolean -> + view.autoplay = autoplay + } + + AsyncFunction("playAsync") { view: GifView -> + view.play() + } + + AsyncFunction("pauseAsync") { view: GifView -> + view.pause() + } + + AsyncFunction("toggleAsync") { view: GifView -> + view.toggle() + } + } + } +} diff --git a/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/GifView.kt b/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/GifView.kt new file mode 100644 index 0000000000..be5830df7a --- /dev/null +++ b/modules/expo-bluesky-gif-view/android/src/main/java/expo/modules/blueskygifview/GifView.kt @@ -0,0 +1,180 @@ +package expo.modules.blueskygifview + + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.Animatable +import android.graphics.drawable.Drawable +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target +import expo.modules.kotlin.AppContext +import expo.modules.kotlin.exception.Exceptions +import expo.modules.kotlin.viewevent.EventDispatcher +import expo.modules.kotlin.views.ExpoView + +class GifView(context: Context, appContext: AppContext) : ExpoView(context, appContext) { + // Events + private val onPlayerStateChange by EventDispatcher() + + // Glide + private val activity = appContext.currentActivity ?: throw Exceptions.MissingActivity() + private val glide = Glide.with(activity) + val imageView = AppCompatImageViewExtended(context, this) + var isPlaying = true + var isLoaded = false + + // Requests + private var placeholderRequest: Target? = null + private var webpRequest: Target? = null + + // Props + var placeholderSource: String? = null + var source: String? = null + var autoplay: Boolean = true + set(value) { + field = value + + if (value) { + this.play() + } else { + this.pause() + } + } + + + // + + init { + this.setBackgroundColor(Color.TRANSPARENT) + + this.imageView.setBackgroundColor(Color.TRANSPARENT) + this.imageView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + + this.addView(this.imageView) + } + + override fun onAttachedToWindow() { + if (this.imageView.drawable == null || this.imageView.drawable !is Animatable) { + this.load() + } else if (this.isPlaying) { + this.imageView.play() + } + super.onAttachedToWindow() + } + + override fun onDetachedFromWindow() { + this.imageView.pause() + super.onDetachedFromWindow() + } + + // + + // + + private fun load() { + if (placeholderSource == null || source == null) { + return + } + + this.webpRequest = glide.load(source) + .diskCacheStrategy(DiskCacheStrategy.DATA) + .skipMemoryCache(false) + .listener(object: RequestListener { + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: com.bumptech.glide.load.DataSource?, + isFirstResource: Boolean + ): Boolean { + if (placeholderRequest != null) { + glide.clear(placeholderRequest) + } + return false + } + + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + return true + } + }) + .into(this.imageView) + + if (this.imageView.drawable == null || this.imageView.drawable !is Animatable) { + this.placeholderRequest = glide.load(placeholderSource) + .diskCacheStrategy(DiskCacheStrategy.DATA) + // Let's not bloat the memory cache with placeholders + .skipMemoryCache(true) + .listener(object: RequestListener { + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: com.bumptech.glide.load.DataSource?, + isFirstResource: Boolean + ): Boolean { + // Incase this request finishes after the webp, let's just not set + // the drawable. This shouldn't happen because the request should get cancelled + if (imageView.drawable == null) { + imageView.setImageDrawable(resource) + } + return true + } + + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + return true + } + }) + .submit() + } + } + + // + + // + + fun play() { + this.imageView.play() + this.isPlaying = true + this.firePlayerStateChange() + } + + fun pause() { + this.imageView.pause() + this.isPlaying = false + this.firePlayerStateChange() + } + + fun toggle() { + if (this.isPlaying) { + this.pause() + } else { + this.play() + } + } + + // + + // + + fun firePlayerStateChange() { + onPlayerStateChange(mapOf( + "isPlaying" to this.isPlaying, + "isLoaded" to this.isLoaded, + )) + } + + // +} diff --git a/modules/expo-bluesky-gif-view/expo-module.config.json b/modules/expo-bluesky-gif-view/expo-module.config.json new file mode 100644 index 0000000000..0756c8e24c --- /dev/null +++ b/modules/expo-bluesky-gif-view/expo-module.config.json @@ -0,0 +1,9 @@ +{ + "platforms": ["ios", "android", "web"], + "ios": { + "modules": ["ExpoBlueskyGifViewModule"] + }, + "android": { + "modules": ["expo.modules.blueskygifview.ExpoBlueskyGifViewModule"] + } +} diff --git a/modules/expo-bluesky-gif-view/index.ts b/modules/expo-bluesky-gif-view/index.ts new file mode 100644 index 0000000000..0244a54914 --- /dev/null +++ b/modules/expo-bluesky-gif-view/index.ts @@ -0,0 +1 @@ +export {GifView} from './src/GifView' diff --git a/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifView.podspec b/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifView.podspec new file mode 100644 index 0000000000..ddd0877b24 --- /dev/null +++ b/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifView.podspec @@ -0,0 +1,23 @@ +Pod::Spec.new do |s| + s.name = 'ExpoBlueskyGifView' + s.version = '1.0.0' + s.summary = 'A simple GIF player for Bluesky' + s.description = 'A simple GIF player for Bluesky' + s.author = '' + s.homepage = 'https://github.com/bluesky-social/social-app' + s.platforms = { :ios => '13.4', :tvos => '13.4' } + s.source = { git: '' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + s.dependency 'SDWebImage', '~> 5.17.0' + s.dependency 'SDWebImageWebPCoder', '~> 0.13.0' + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'SWIFT_COMPILATION_MODE' => 'wholemodule' + } + + s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" +end diff --git a/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifViewModule.swift b/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifViewModule.swift new file mode 100644 index 0000000000..7c7132290d --- /dev/null +++ b/modules/expo-bluesky-gif-view/ios/ExpoBlueskyGifViewModule.swift @@ -0,0 +1,47 @@ +import ExpoModulesCore +import SDWebImage +import SDWebImageWebPCoder + +public class ExpoBlueskyGifViewModule: Module { + public func definition() -> ModuleDefinition { + Name("ExpoBlueskyGifView") + + OnCreate { + SDImageCodersManager.shared.addCoder(SDImageGIFCoder.shared) + } + + AsyncFunction("prefetchAsync") { (sources: [URL]) in + SDWebImagePrefetcher.shared.prefetchURLs(sources, context: Util.createContext(), progress: nil) + } + + View(GifView.self) { + Events( + "onPlayerStateChange" + ) + + Prop("source") { (view: GifView, prop: String) in + view.source = prop + } + + Prop("placeholderSource") { (view: GifView, prop: String) in + view.placeholderSource = prop + } + + Prop("autoplay") { (view: GifView, prop: Bool) in + view.autoplay = prop + } + + AsyncFunction("toggleAsync") { (view: GifView) in + view.toggle() + } + + AsyncFunction("playAsync") { (view: GifView) in + view.play() + } + + AsyncFunction("pauseAsync") { (view: GifView) in + view.pause() + } + } + } +} diff --git a/modules/expo-bluesky-gif-view/ios/GifView.swift b/modules/expo-bluesky-gif-view/ios/GifView.swift new file mode 100644 index 0000000000..de722d7a63 --- /dev/null +++ b/modules/expo-bluesky-gif-view/ios/GifView.swift @@ -0,0 +1,185 @@ +import ExpoModulesCore +import SDWebImage +import SDWebImageWebPCoder + +typealias SDWebImageContext = [SDWebImageContextOption: Any] + +public class GifView: ExpoView, AVPlayerViewControllerDelegate { + // Events + private let onPlayerStateChange = EventDispatcher() + + // SDWebImage + private let imageView = SDAnimatedImageView(frame: .zero) + private let imageManager = SDWebImageManager( + cache: SDImageCache.shared, + loader: SDImageLoadersManager.shared + ) + private var isPlaying = true + private var isLoaded = false + + // Requests + private var webpOperation: SDWebImageCombinedOperation? + private var placeholderOperation: SDWebImageCombinedOperation? + + // Props + var source: String? = nil + var placeholderSource: String? = nil + var autoplay = true { + didSet { + if !autoplay { + self.pause() + } else { + self.play() + } + } + } + + // MARK: - Lifecycle + + public required init(appContext: AppContext? = nil) { + super.init(appContext: appContext) + self.clipsToBounds = true + + self.imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.imageView.layer.masksToBounds = false + self.imageView.backgroundColor = .clear + self.imageView.contentMode = .scaleToFill + + // We have to explicitly set this to false. If we don't, every time + // the view comes into the viewport, it will start animating again + self.imageView.autoPlayAnimatedImage = false + + self.addSubview(self.imageView) + } + + public override func willMove(toWindow newWindow: UIWindow?) { + if newWindow == nil { + // Don't cancel the placeholder operation, because we really want that to complete for + // when we scroll back up + self.webpOperation?.cancel() + self.placeholderOperation?.cancel() + } else if self.imageView.image == nil { + self.load() + } + } + + // MARK: - Loading + + private func load() { + guard let source = self.source, let placeholderSource = self.placeholderSource else { + return + } + + self.webpOperation?.cancel() + self.placeholderOperation?.cancel() + + // We only need to start an operation for the placeholder if it doesn't exist + // in the cache already. Cache key is by default the absolute URL of the image. + // See: + // https://github.com/SDWebImage/SDWebImage/blob/master/Docs/HowToUse.md#using-asynchronous-image-caching-independently + if !SDImageCache.shared.diskImageDataExists(withKey: source), + let url = URL(string: placeholderSource) + { + self.placeholderOperation = imageManager.loadImage( + with: url, + options: [.retryFailed], + context: Util.createContext(), + progress: onProgress(_:_:_:), + completed: onLoaded(_:_:_:_:_:_:) + ) + } + + if let url = URL(string: source) { + self.webpOperation = imageManager.loadImage( + with: url, + options: [.retryFailed], + context: Util.createContext(), + progress: onProgress(_:_:_:), + completed: onLoaded(_:_:_:_:_:_:) + ) + } + } + + private func setImage(_ image: UIImage) { + if self.imageView.image == nil || image.sd_isAnimated { + self.imageView.image = image + } + + if image.sd_isAnimated { + self.firePlayerStateChange() + if isPlaying { + self.imageView.startAnimating() + } + } + } + + // MARK: - Loading blocks + + private func onProgress(_ receivedSize: Int, _ expectedSize: Int, _ imageUrl: URL?) {} + + private func onLoaded( + _ image: UIImage?, + _ data: Data?, + _ error: Error?, + _ cacheType: SDImageCacheType, + _ finished: Bool, + _ imageUrl: URL? + ) { + guard finished else { + return + } + + if let placeholderSource = self.placeholderSource, + imageUrl?.absoluteString == placeholderSource, + self.imageView.image == nil, + let image = image + { + self.setImage(image) + return + } + + if let source = self.source, + imageUrl?.absoluteString == source, + // UIImage perf suckssss if the image is animated + let data = data, + let animatedImage = SDAnimatedImage(data: data) + { + self.placeholderOperation?.cancel() + self.isPlaying = self.autoplay + self.isLoaded = true + self.setImage(animatedImage) + self.firePlayerStateChange() + } + } + + // MARK: - Playback Controls + + func play() { + self.imageView.startAnimating() + self.isPlaying = true + self.firePlayerStateChange() + } + + func pause() { + self.imageView.stopAnimating() + self.isPlaying = false + self.firePlayerStateChange() + } + + func toggle() { + if self.isPlaying { + self.pause() + } else { + self.play() + } + } + + // MARK: - Util + + private func firePlayerStateChange() { + onPlayerStateChange([ + "isPlaying": self.isPlaying, + "isLoaded": self.isLoaded + ]) + } +} diff --git a/modules/expo-bluesky-gif-view/ios/Util.swift b/modules/expo-bluesky-gif-view/ios/Util.swift new file mode 100644 index 0000000000..55ed4152aa --- /dev/null +++ b/modules/expo-bluesky-gif-view/ios/Util.swift @@ -0,0 +1,17 @@ +import SDWebImage + +class Util { + static func createContext() -> SDWebImageContext { + var context = SDWebImageContext() + + // SDAnimatedImage for some reason has issues whenever loaded from memory. Instead, we + // will just use the disk. SDWebImage will manage this cache for us, so we don't need + // to worry about clearing it. + context[.originalQueryCacheType] = SDImageCacheType.disk.rawValue + context[.originalStoreCacheType] = SDImageCacheType.disk.rawValue + context[.queryCacheType] = SDImageCacheType.disk.rawValue + context[.storeCacheType] = SDImageCacheType.disk.rawValue + + return context + } +} diff --git a/modules/expo-bluesky-gif-view/src/GifView.tsx b/modules/expo-bluesky-gif-view/src/GifView.tsx new file mode 100644 index 0000000000..87258de17b --- /dev/null +++ b/modules/expo-bluesky-gif-view/src/GifView.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import {requireNativeModule} from 'expo' +import {requireNativeViewManager} from 'expo-modules-core' + +import {GifViewProps} from './GifView.types' + +const NativeModule = requireNativeModule('ExpoBlueskyGifView') +const NativeView: React.ComponentType< + GifViewProps & {ref: React.RefObject} +> = requireNativeViewManager('ExpoBlueskyGifView') + +export class GifView extends React.PureComponent { + // TODO native types, should all be the same as those in this class + private nativeRef: React.RefObject = React.createRef() + + constructor(props: GifViewProps | Readonly) { + super(props) + } + + static async prefetchAsync(sources: string[]): Promise { + return await NativeModule.prefetchAsync(sources) + } + + async playAsync(): Promise { + await this.nativeRef.current.playAsync() + } + + async pauseAsync(): Promise { + await this.nativeRef.current.pauseAsync() + } + + async toggleAsync(): Promise { + await this.nativeRef.current.toggleAsync() + } + + render() { + return + } +} diff --git a/modules/expo-bluesky-gif-view/src/GifView.types.ts b/modules/expo-bluesky-gif-view/src/GifView.types.ts new file mode 100644 index 0000000000..29ec277f2b --- /dev/null +++ b/modules/expo-bluesky-gif-view/src/GifView.types.ts @@ -0,0 +1,15 @@ +import {ViewProps} from 'react-native' + +export interface GifViewStateChangeEvent { + nativeEvent: { + isPlaying: boolean + isLoaded: boolean + } +} + +export interface GifViewProps extends ViewProps { + autoplay?: boolean + source?: string + placeholderSource?: string + onPlayerStateChange?: (event: GifViewStateChangeEvent) => void +} diff --git a/modules/expo-bluesky-gif-view/src/GifView.web.tsx b/modules/expo-bluesky-gif-view/src/GifView.web.tsx new file mode 100644 index 0000000000..c197e01a1d --- /dev/null +++ b/modules/expo-bluesky-gif-view/src/GifView.web.tsx @@ -0,0 +1,82 @@ +import * as React from 'react' +import {StyleSheet} from 'react-native' + +import {GifViewProps} from './GifView.types' + +export class GifView extends React.PureComponent { + private readonly videoPlayerRef: React.RefObject = + React.createRef() + private isLoaded = false + + constructor(props: GifViewProps | Readonly) { + super(props) + } + + componentDidUpdate(prevProps: Readonly) { + if (prevProps.autoplay !== this.props.autoplay) { + if (this.props.autoplay) { + this.playAsync() + } else { + this.pauseAsync() + } + } + } + + static async prefetchAsync(_: string[]): Promise { + console.warn('prefetchAsync is not supported on web') + } + + private firePlayerStateChangeEvent = () => { + this.props.onPlayerStateChange?.({ + nativeEvent: { + isPlaying: !this.videoPlayerRef.current?.paused, + isLoaded: this.isLoaded, + }, + }) + } + + private onLoad = () => { + // Prevent multiple calls to onLoad because onCanPlay will fire after each loop + if (this.isLoaded) { + return + } + + this.isLoaded = true + this.firePlayerStateChangeEvent() + } + + async playAsync(): Promise { + this.videoPlayerRef.current?.play() + } + + async pauseAsync(): Promise { + this.videoPlayerRef.current?.pause() + } + + async toggleAsync(): Promise { + if (this.videoPlayerRef.current?.paused) { + await this.playAsync() + } else { + await this.pauseAsync() + } + } + + render() { + return ( +